php/src/Kernel/EasySDKKernel.php
<?php


namespace Alipay\EasySDK\Kernel;

use Alipay\EasySDK\Kernel\Util\AES;
use Alipay\EasySDK\Kernel\Util\JsonUtil;
use Alipay\EasySDK\Kernel\Util\PageUtil;
use Alipay\EasySDK\Kernel\Util\SignContentExtractor;
use Alipay\EasySDK\Kernel\Util\Signer;
use AlibabaCloud\Tea\FileForm\FileForm;
use AlibabaCloud\Tea\FileForm\FileForm\FileField;
use GuzzleHttp\Psr7\Stream;
use Psr\Http\Message\ResponseInterface;

class EasySDKKernel
{

    private $config;

    private $optionalTextParams;

    private $optionalBizParams;

    private $textParams;

    private $bizParams;


    public function __construct($config)
    {
        $this->config = $config;
    }

    public function injectTextParam($key, $value)
    {
        if ($key != null) {
            $this->optionalTextParams[$key] = $value;
        }
    }

    public function injectBizParam($key, $value)
    {
        if ($key != null) {
            $this->optionalBizParams[$key] = $value;
        }
    }

    /**
     * 获取时间戳，格式yyyy-MM-dd HH:mm:ss
     * @return false|string 当前时间戳
     */
    public function getTimestamp()
    {
        return date("Y-m-d H:i:s");
    }

    public function getConfig($key)
    {
        return $this->config->$key;
    }

    public function getSdkVersion()
    {
        return AlipayConstants::SDK_VERSION;
    }

    /**
     * 将业务参数和其他额外文本参数按www-form-urlencoded格式转换成HTTP Body中的字节数组，注意要做URL Encode
     *
     * @param $bizParams array 业务参数
     * @return false|string|null
     */
    public function toUrlEncodedRequestBody($bizParams)
    {
        $sortedMap = $this->getSortedMap(null, $bizParams, null);
        if (empty($sortedMap)) {
            return null;
        }
        return $this->buildQueryString($sortedMap);
    }

    /**
     * 解析网关响应内容，同时将API的接口名称和响应原文插入到响应数组的method和body字段中
     *
     * @param $response ResponseInterface HTTP响应
     * @param $method   string 调用的OpenAPI的接口名称
     * @return array    响应的结果
     */
    public function readAsJson($response, $method)
    {
        $responseBody = (string)$response->getBody();
        $map = [];
        $map[AlipayConstants::BODY_FIELD] = $responseBody;
        $map[AlipayConstants::METHOD_FIELD] = $method;
        return $map;
    }

    /**
     * 生成随机分界符，用于multipart格式的HTTP请求Body的多个字段间的分隔
     *
     * @return string 随机分界符
     */
    public function getRandomBoundary()
    {
        return date("Y-m-d H:i:s") . '';
    }

    /**
     * 将其他额外文本参数和文件参数按multipart/form-data格式转换成HTTP Body中
     * @param $textParams
     * @param $fileParams
     * @param $boundary
     * @return false|string
     */
    public function toMultipartRequestBody($textParams, $fileParams, $boundary)
    {
        $this->textParams = $textParams;
        if ($textParams != null && $this->optionalTextParams != null) {
            $this->textParams = array_merge($textParams, $this->optionalTextParams);
        } else if ($textParams == null) {
            $this->textParams = $this->optionalTextParams;
        }
        if (count($fileParams) > 0) {

            foreach ($fileParams as $key => $value) {
                $fileField = new FileField();
                $fileField->filename = $value;
                $fileField->contentType = 'multipart/form-data;charset=utf-8;boundary=' . $boundary;
                $fileField->content = new Stream(fopen($value, 'r'));
                $this->textParams[$key] = $fileField;
            }
        }
        $stream = FileForm::toFileForm($this->textParams, $boundary);

//        do {
//            $readLength = $stream->read(1024);
//        } while (0 != $readLength);
        return $stream;
    }

    /**
     * 生成页面类请求所需URL或Form表单
     * @param $method
     * @param $systemParams
     * @param $bizParams
     * @param $textParams
     * @param $sign
     * @return string
     * @throws \Exception
     */
    public function generatePage($method, $systemParams, $bizParams, $textParams, $sign)
    {
        if ($method == AlipayConstants::GET) {
            //采集并排序所有参数
            $sortedMap = $this->getSortedMap($systemParams, $bizParams, $textParams);
            $sortedMap[AlipayConstants::SIGN_FIELD] = $sign;
            return $this->getGatewayServerUrl() . '?' . $this->buildQueryString($sortedMap);
        } elseif ($method == AlipayConstants::POST) {
            //采集并排序所有参数
            $sortedMap = $this->getSortedMap($systemParams, $this->bizParams, $this->textParams);
            $sortedMap[AlipayConstants::SIGN_FIELD] = $sign;
            $pageUtil = new PageUtil();
            return $pageUtil->buildForm($this->getGatewayServerUrl(), $sortedMap);
        } else {
            throw new \Exception("不支持" . $method);
        }
    }

    /**
     *  获取商户应用公钥证书序列号，从证书模式运行时环境对象中直接读取
     *
     * @return mixed 商户应用公钥证书序列号
     */
    public function getMerchantCertSN()
    {
        return $this->config->merchantCertSN;
    }

    /**
     * 从响应Map中提取支付宝公钥证书序列号
     *
     * @param array $respMap string 响应Map
     * @return mixed   支付宝公钥证书序列号
     */
    public function getAlipayCertSN(array $respMap)
    {
        if (!empty($this->config->merchantCertSN)) {
            $body = json_decode($respMap[AlipayConstants::BODY_FIELD]);
            $alipayCertSN = $body->alipay_cert_sn;
            return $alipayCertSN;
        }
    }

    /**
     * 获取支付宝根证书序列号，从证书模式运行时环境对象中直接读取
     *
     * @return mixed 支付宝根证书序列号
     */
    public function getAlipayRootCertSN()
    {
        return $this->config->alipayRootCertSN;
    }

    /**
     * 是否是证书模式
     * @return mixed true：是；false：不是
     */
    public function isCertMode()
    {
        return $this->config->merchantCertSN;
    }

    public function extractAlipayPublicKey($alipayCertSN)
    {
        // PHP 版本只存储一个版本支付宝公钥
        return $this->config->alipayPublicKey;
    }

    /**
     * 验证签名
     *
     * @param $respMap  string 响应内容，可以从中提取出sign和body
     * @param $alipayPublicKey string 支付宝公钥
     * @return bool  true：验签通过；false：验签不通过
     * @throws \Exception
     */
    public function verify($respMap, $alipayPublicKey)
    {
        $resp = json_decode($respMap[AlipayConstants::BODY_FIELD], true);
        $sign = $resp[AlipayConstants::SIGN_FIELD];
        $signContentExtractor = new SignContentExtractor();
        $content = $signContentExtractor->getSignSourceData($respMap[AlipayConstants::BODY_FIELD],
            $respMap[AlipayConstants::METHOD_FIELD]);
        $signer = new Signer();
        return $signer->verify($content, $sign, $alipayPublicKey);
    }

    /**
     * 计算签名，注意要去除key或value为null的键值对
     *
     * @param $systemParams   array 系统参数集合
     * @param $bizParams      array 业务参数集合
     * @param $textParams     array 其他额外文本参数集合
     * @param $privateKey     string 私钥
     * @return string 签名值的Base64串
     */
    public function sign($systemParams, $bizParams, $textParams, $privateKey)
    {
        $sortedMap = $this->getSortedMap($systemParams, $bizParams, $textParams);
        $data = $this->getSignContent($sortedMap);
        $sign = new Signer();
        return $sign->sign($data, $privateKey);
    }

    /**
     * AES加密
     * @param $content
     * @param $encryptKey
     * @return string
     * @throws \Exception
     */
    public function aesEncrypt($content, $encryptKey)
    {
        $aes = new AES();
        return $aes->aesEncrypt($content, $encryptKey);
    }

    /**
     * AES解密
     * @param $content
     * @param $encryptKey
     * @return false|string
     * @throws \Exception
     */
    public function aesDecrypt($content, $encryptKey)
    {
        $aes = new AES();
        return $aes->aesDecrypt($content, $encryptKey);
    }

    /**
     * 生成sdkExecute类请求所需URL
     *
     * @param $systemParams
     * @param $bizParams
     * @param $textParams
     * @param $sign
     * @return string
     */
    public function generateOrderString($systemParams, $bizParams, $textParams, $sign)
    {
        //采集并排序所有参数
        $sortedMap = $this->getSortedMap($systemParams, $bizParams, $textParams);
        $sortedMap[AlipayConstants::SIGN_FIELD] = $sign;
        return http_build_query($sortedMap);
    }

    public function sortMap($randomMap)
    {
        return $randomMap;
    }


    /**
     *  从响应Map中提取返回值对象的Map，并将响应原文插入到body字段中
     *
     * @param $respMap  string 响应内容
     * @return mixed
     */
    public function toRespModel($respMap)
    {
        $body = $respMap[AlipayConstants::BODY_FIELD];
        $methodName = $respMap[AlipayConstants::METHOD_FIELD];
        $responseNodeName = str_replace(".", "_", $methodName) . AlipayConstants::RESPONSE_SUFFIX;

        $model = json_decode($body, true);
        if (strpos($body, AlipayConstants::ERROR_RESPONSE)) {
            $result = $model[AlipayConstants::ERROR_RESPONSE];
            $result[AlipayConstants::BODY_FIELD] = $body;
        } else {
            $result = $model[$responseNodeName];
            $result[AlipayConstants::BODY_FIELD] = $body;
        }
        return $result;
    }

    public function verifyParams($parameters, $publicKey)
    {
        $sign = new Signer();
        return $sign->verifyParams($parameters, $publicKey);
    }

    /**
     * 字符串拼接
     *
     * @param $a
     * @param $b
     * @return string 字符串a和b拼接后的字符串
     */
    public function concatStr($a, $b)
    {
        return $a . $b;
    }


    private function buildQueryString(array $sortedMap)
    {
        $requestUrl = null;
        foreach ($sortedMap as $sysParamKey => $sysParamValue) {
            $requestUrl .= "$sysParamKey=" . urlencode($this->characet($sysParamValue, AlipayConstants::DEFAULT_CHARSET)) . "&";
        }
        $requestUrl = substr($requestUrl, 0, -1);
        return $requestUrl;

    }

    private function getSortedMap($systemParams, $bizParams, $textParams)
    {
        $this->textParams = $textParams;
        $this->bizParams = $bizParams;
        if ($textParams != null && $this->optionalTextParams != null) {
            $this->textParams = array_merge($textParams, $this->optionalTextParams);
        } else if ($textParams == null) {
            $this->textParams = $this->optionalTextParams;
        }
        if ($bizParams != null && $this->optionalBizParams != null) {
            $this->bizParams = array_merge($bizParams, $this->optionalBizParams);
        } else if ($bizParams == null) {
            $this->bizParams = $this->optionalBizParams;
        }
        $json = new JsonUtil();
        if ($this->bizParams != null) {
            $bizParams = $json->toJsonString($this->bizParams);
        }
        $sortedMap = $systemParams;
        if (!empty($bizParams)) {
            $sortedMap[AlipayConstants::BIZ_CONTENT_FIELD] = json_encode($bizParams, JSON_UNESCAPED_UNICODE);
        }
        if (!empty($this->textParams)) {
            if (!empty($sortedMap)) {
                $sortedMap = array_merge($sortedMap, $this->textParams);
            } else {
                $sortedMap = $this->textParams;
            }
        }
        if ($this->getConfig(AlipayConstants::NOTIFY_URL_CONFIG_KEY) != null) {
            $sortedMap[AlipayConstants::NOTIFY_URL_FIELD] = $this->getConfig(AlipayConstants::NOTIFY_URL_CONFIG_KEY);
        }
        return $sortedMap;
    }

    /**
     * 获取签名字符串
     *
     * @param $params
     * @return string
     */
    private function getSignContent($params)
    {
        ksort($params);
        $stringToBeSigned = "";
        $i = 0;
        foreach ($params as $k => $v) {
            if (false === $this->checkEmpty($v) && "@" != substr($v, 0, 1)) {
                // 转换成目标字符集
                $v = $this->characet($v, AlipayConstants::DEFAULT_CHARSET);
                if ($i == 0) {
                    $stringToBeSigned .= "$k" . "=" . "$v";
                } else {
                    $stringToBeSigned .= "&" . "$k" . "=" . "$v";
                }
                $i++;
            }
        }
        unset ($k, $v);
        return $stringToBeSigned;
    }

    private function setNotifyUrl($params)
    {
        if ($this->config(AlipayConstants::NOTIFY_URL_CONFIG_KEY) != null && $params(AlipayConstants::NOTIFY_URL_CONFIG_KEY) == null) {
            $params[AlipayConstants::NOTIFY_URL_CONFIG_KEY] = $this->config(AlipayConstants::NOTIFY_URL_CONFIG_KEY);
        }
    }

    private function getGatewayServerUrl()
    {
        return $this->getConfig(AlipayConstants::PROTOCOL_CONFIG_KEY) . '://' . $this->getConfig(AlipayConstants::HOST_CONFIG_KEY) . '/gateway.do';
    }

    /**
     * 校验$value是否非空
     *
     * @param $value
     * @return bool if not set ,return true;if is null , return true;
     */
    function checkEmpty($value)
    {
        if (!isset($value))
            return true;
        if ($value === null)
            return true;
        if (trim($value) === "")
            return true;
        return false;
    }

    /**
     * 转换字符集编码
     * @param $data
     * @param $targetCharset
     * @return string
     */
    function characet($data, $targetCharset)
    {
        if (!empty($data)) {
            $fileType = AlipayConstants::DEFAULT_CHARSET;
            if (strcasecmp($fileType, $targetCharset) != 0) {
                $data = mb_convert_encoding($data, $targetCharset, $fileType);
            }
        }
        return $data;
    }

}