<?php

namespace App\Libs\Pay;

use Exception;
use GuzzleHttp\Client;

class SandPay
{
    const API_HOST = 'https://caspay.sandpay.com.cn/agent-main/openapi/';
    const API_TEST_HOST = 'http://61.129.71.103:7970/agent-main/openapi/';
    const PUB_KEY_PATH = 'cert/sandpay/sand.cer'; //公钥文件
    const PRI_KEY_PATH = 'cert/sandpay/vPvWnHjBeQhHLebn.pfx'; //私钥文件
    const CERT_PWD = 'vPvWnHjBeQhHLebn'; //私钥证书密码
    const MER_ID = '6888804025954';
    const NOTIFY_HOST = 'http://116.62.175.234/';
    const NOTIFY_TEST_HOST = 'http://managepre.aizhuishu.com/';
    const PUB_PRODUCT_ID = '00000003'; //代付对公：00000003
    const PRI_PRODUCT_ID = '00000004'; //代付对私：00000004

    private $notify_url;
    private $agentpay_url;
    private $query_balance_url;
    private $query_order_url;
    private $private_key;
    private $public_key;
    private $client;
    private $product_id;

    public function __construct(bool $is_private = true)
    {
        $this->agentpay_url =  (self::API_HOST) . 'agentpay';
        $this->query_balance_url =  (self::API_HOST) . 'queryBalance';
        $this->query_order_url =  (self::API_HOST) . 'queryOrder';
        $this->private_key = $this->loadPk12Cert(storage_path(self::PRI_KEY_PATH), self::CERT_PWD);
        $this->public_key = $this->loadX509Cert(storage_path(self::PUB_KEY_PATH));
        $this->notify_url =   (env('APP_ENV') == 'online' ? self::NOTIFY_HOST : self::NOTIFY_TEST_HOST) . 'api/tradepayapi/sandPayNotify';
        $this->product_id = $is_private ? self::PRI_PRODUCT_ID : self::PUB_PRODUCT_ID;
        $this->client = new Client();
    }

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

    /**
     * 反格式化金额
     */
    private function formatAmount(string $amount)
    {
        return (float) (((int) ltrim($amount, '\0')) / 100);
    }

    /**
     * 实时代付
     * @param array $data
     * @return array
     */
    public function agentPay(array $data)
    {
        $params = [
            'transCode' => 'RTPM', // 实时代付
            'url' => $this->agentpay_url,
            'params' => [
                'version' => '01',
                'productId' => $this->product_id,
                'tranTime' => date('YmdHis', strtotime($data['pay_time'])),
                'orderCode' => $data['order_no'],
                'tranAmt' => $this->amountFormat($data['amount']),
                'currencyCode' => '156',
                'accAttr' => $this->product_id == self::PRI_PRODUCT_ID ? '0' : '1',
                'accType' => $this->product_id == self::PRI_PRODUCT_ID ? '4' : '3',
                'accNo' => $data['bank_account'],
                'accName' => $data['account_name'],
                'bankName' => $data['account_bank'],
                'remark' => $data['remark'],
                'payMode' => '1',
                'channelType' => '07',
                'noticeUrl' => $this->notify_url,
            ],
        ];
        if ($this->product_id == self::PUB_PRODUCT_ID) {
            $params['params']['bankType'] = $data['bank_number'];
        }
        $result = $this->send($params);
        if ($result->resultFlag == 0) {
            return [
                'result' => true,
                'content' => $result,
            ];
        } else {
            return [
                'result' => false,
                'content' => $result,
            ];
        }
    }

    /**
     * 查询订单
     * @param array $data
     * @return array
     */
    public function queryOrder(array $data, bool $is_private)
    {
        $this->product_id = $is_private ? self::PRI_PRODUCT_ID : self::PUB_PRODUCT_ID;
        $params = [
            'transCode' => 'ODQU', // 订单查询
            'url' => $this->query_order_url,
            'params' => [
                'orderCode' => $data['order_no'],
                'version' => '01',
                'productId' => $this->product_id,
                'tranTime' => date('YmdHis', strtotime($data['pay_time'])),
            ],
        ];
        $result = $this->send($params);
        if (isset($result->resultFlag) && $result->resultFlag == 0) {
            return [
                'result' => 'success',
                'content' => $result,
            ];
        } else if (isset($result->resultFlag) && $result->resultFlag == 2) {
            return [
                'result' => 'waitting',
                'content' => $result,
            ];
        } else {
            return [
                'result' => 'failure',
                'content' => $result,
            ];
        }
    }

    /**
     * 查询余额
     * @param array $data
     * @return float
     */
    public function queryBalance(array $data)
    {
        $params = [
            'transCode' => 'MBQU', // 商户余额查询
            'url' => $this->query_balance_url,
            'params' => array(
                'orderCode' => $data['order_no'],
                'version' => '01',
                'productId' => $this->product_id,
                'tranTime' => date('YmdHis', strtotime($data['pay_time'])),
            )
        ];
        $result = $this->send($params);
        if (isset($result->balance)) {
            return ((int) ltrim($result->balance, '+')) / 100;
        } else {
            return 0;
        }
    }

    /**
     * 发送请求
     * @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();
            parse_str($content, $result);
            return $result;
        } catch (Exception $e) {
            throw $e;
        }
    }

    /**
     * 请求接口
     * @param array $data
     * @return object
     */
    private function send(array $data)
    {
        // step1: 生成AESKey并使用公钥加密
        $AESKey = $this->aes_generate(16);
        $encryptKey = $this->RSAEncryptByPub($AESKey, $this->public_key);
        $params = $data['params'];
        // step2: 使用AESKey加密报文
        $encryptData = $this->AESEncrypt($params, $AESKey);

        // step3: 使用私钥签名报文
        $sign = $this->sign($params, $this->private_key);
        // step4: 拼接post数据
        $post = [
            'transCode' => $data['transCode'],
            'accessType' => '0',
            'merId' => self::MER_ID,
            'encryptKey' => $encryptKey,
            'encryptData' => $encryptData,
            'sign' => $sign
        ];
        try {
            // step5: post请求
            $encrypt_data = $this->http_post_json($data['url'], $post);

            // step6: 使用私钥解密AESKey
            $decryptAESKey = $this->RSADecryptByPri($encrypt_data['encryptKey'], $this->private_key);

            // step7: 使用解密后的AESKey解密报文
            $decryptPlainText = $this->AESDecrypt($encrypt_data['encryptData'], $decryptAESKey);

            // step8: 使用公钥验签报文
            $this->verify($decryptPlainText, $encrypt_data['sign'], $this->public_key);


            $result = json_decode($decryptPlainText);
            if ($result->respCode != '0000') {
                myLog('sand_pay')->error($params['orderCode'] . ': ' . $decryptPlainText);
            } else {
                myLog('sand_pay')->info($decryptPlainText);
            }
            return $result;
        } catch (Exception $e) {
            myLog('sand_pay')->error($params['orderCode'] . ': ' . $e->getMessage());
        }
        return [];
    }

    /**
     * 获取公钥
     * @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);
    }
}
