jh-admin/addon/cypay/sdk/AopClient.php

347 lines
16 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace addon\cypay\sdk;
require_once 'AopEncrypt.php';
class AopClient
{
//网关
public $gatewayUrl = "https://pay.chanpay.com/ugtw/gateway";
//返回数据格式
public $format = "json";
//api版本
public $apiVersion = "1.0";
// 表单提交字符集编码
public $postCharset = "UTF-8";
public $fileCharset = "UTF-8";
//签名类型
public $signType = "RSA2";
public $acq_sp_id = '49072480';//合作机构号
public $productCode = '1000';//营销产品编码,由畅捷支付分配
//商户公钥
public $publicKey = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCQedhgpTUxuWDof5uiHWszPP/462Q/ORos8uV11g8nYmchcyE3x8jOXE5ZKRAua5HUDNkLQG/8wu4+tPVicKe3r9DOefZf+zxZgHB/uddA+Sl54h9BzjPB8RscBtDM2DWRZhdnVu4LMRb9Lp9eRYTLbviPazCEm13FqttHT35oAQIDAQAB';
public $rsaPrivateKey = 'MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCpJyDZDMqNmLRKfXclpFu4WbB93ZBmISg1FLrcaIqGRqRttCE/yHxfLTJl7xMxeYXjBqjkiGvnfLbgkocAYWPNER5Urh5aRRORgJIPRr5sTpVMHKRrok7yfaA/t7D/7AHhrPVxoE/nv5aMT4oGtTJhIxyLdnuK5pbHoQR9lODx+kOLgZ1xlJmVMD+ewZVrF4sllIGoOJRkjkUOb+xRD11PF2Lk4w3u+AUiLukV1vy98rVsBmL09DCNsG4d4DZkSPtGbGDFwoMh07+SR02cIb2WiRtQnPaYKSYdkMHDqOH3XUawFgHHoYLugL/57c1y5HG6yx2zZUVQ4yPspfX2zSZrAgMBAAECggEACoSHU1erRJCpLTSN8wY8OcNB6SGct+z53gsS71+EtYKw+K1Jn/isWxKpXpM2A06GF66zU7pz0yn7CQ2zXT+w//A/jY5iDsTayaJP8qk4b+2W9OuXAaZO+F79VtjqJY+cMlcZz93i+gr8pm7Pq0ka/9U6EiXk2qcp2vHVIKXgMst9xJLzFamhXiWbBaIENigzXV/uqCC4w48KVCUDOtpQTkaoSLvZ0uChuYPLDSEsuKp0wM2BLlJ0cZ/bdhfII2qWyvIcdTM0If+d/phigg4OUX/tZqfLH/k0NGK3k1TQhMbqHu0TwbjdJ7c023wwmzxIRsl0yl5FtJBA1x1/phZWUQKBgQDvKZqXBUiULZM0et9Y5AtaTtrgbB3c6ZIV4dcF8beeHFhL4QsSq33k44YdAS3qWaa+eOWfrDOd+G2Pf6F3sNhgLGphlrVIByoP0r0FCfpmSpozhO+pLJBgXhnrZ+psJ0yaMWrz5J5POc8iPNWlVIllucv9tnUnTYkKIU2Cllf/eQKBgQC1D7+4S/YkM5kzZOb1NqwaYAkZCGDFKHL856j1dUvdEIK+2d67M74v8Op5OSVX4g4vKMMIhmdRLia4TL/kgePvH7sLml+BxCwrHhBGyMYBkoeh1ohwjW/tSe3TJsKtwASuY7R/Wyb0mJbxkAxHECozs/5VtkJYwxZqf19QKNxoAwKBgD4kvFODfvFpyjc3ujM5xi1oEf2Ael39nwTqktmrjj+aM+M7jYoDX4oLCL0eolSjiO0zMs9DioIAnE9OJaGZJRAQWnATHfWiTu6fnpfhmNvdhKXgY/m8Z6NysB939/S0XXYvYxAOlogViFnoHsd/6Ney6Gt7boOQ5QvpzV8iO6lZAoGAUIJJOJSmRSCgbYbfX4fI7Q1o3jWoeeJrhuMncMWQTyLpUB2meU0fs0eHqxFq9nHw5q1UU7UXubQwyWBvLxdGl+xfCmDBOP1WunFqwV7DFK3oG2E+V/W8ICHwWyRwCjxImJaDCuIoJzi2XYE0xGB+s3DEla4uQDO3AvHSGt2ga6sCgYEA3rm1Q/NFBI+QPLsFIKOmsWhdKrJOwe74rqeovXfI8dfOs1s2G3a+Ygu7vT67BB+8mqiPxRRcs+P+2eTQ9IMhn7gjQRq/iGY5IJO7Cbm9hpiSoBkRpSOEFev6210+ERtUDva/W+C6E6INTjgtwaOVHqnfK0qB8ZWTv1iteXNPOV0=';
// 畅捷公钥 RSA2
public $cjRsaPublicKey = 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsNU7PbTNvy16IF5sjzMxS3wQo0kvkq3iNmIIbcLnhatlbdeqdTG+P4wnSM+28Eums/izjL2TKdshoPcuvZ18/rGOUprCeeL7VfTtKu/mlS5H+FCXkdG0a0kstvjloqwRcksV2IiAiFttdB85zI4stu4NBMh4oFd5+nO8Jt7JMPI1J/4zd6VGn4bJ6DEcKVfXn+xpZ9fp0LRRREB5AbqJKpSLS7G+cQE1YT9kRHnAvCZcE+WPwwv24lIOiEXcNacz1vuDC4kZf3ahweuHAGlWve00/O9iIzSkB0FazUGLjOf6WcQiugEz1DtL8wKW2OlAwuk8VU3sSuLbDmzLdYG84QIDAQAB';
public $is_isp = false;
public function generateSign($params, $signType = "RSA2")
{
$params = array_filter($params);
$params['sign_type'] = $signType;
return $this->sign($this->getSignContent($params), $signType);
}
public function getSignContent($params)
{
// 请求参数转字符串
$signStr = 'method' . '=' . $params['method'] . '&';
if ($this->is_isp) {
$signStr .= 'acq_sp_id' . '=' . $params['acq_sp_id'] . '&';
}
$signStr .= 'function_code' . '=' . $params['function_code'] . '&';
$signStr .= 'product_code' . '=' . $params['product_code'] . '&';
$signStr .= 'biz_content' . '=' . $params['biz_content'];
$signStr = trim($signStr, '&');
return $signStr;
}
protected function sign($data, $signType = "RSA")
{
if (!$this->checkEmpty($this->rsaPrivateKey)) {
$priKey = $this->rsaPrivateKey;
$res = "-----BEGIN RSA PRIVATE KEY-----\n" .
wordwrap($priKey, 64, "\n", true) .
"\n-----END RSA PRIVATE KEY-----";
} else {
if (!$this->rsaPrivateKey || empty($priKey)) throw new \Exception("您使用的私钥格式错误请检查RSA私钥配置");
}
if ("RSA2" == $signType) {
openssl_sign($data, $sign, $res, OPENSSL_ALGO_SHA256);
} else {
openssl_sign($data, $sign, $res);
}
$sign = base64_encode($sign);
return $sign;
}
/**
* RSA单独签名方法未做字符串处理,字符串处理见getSignContent()
* @param $data 待签名字符串
* @param $privatekey 商户私钥根据keyfromfile来判断是读取字符串还是读取文件false:填写私钥字符串去回车和空格 true:填写私钥文件路径
* @param $signType 签名方式RSA:SHA1 RSA2:SHA256
* @param $keyfromfile 私钥获取方式,读取字符串还是读文件
* @return string
*/
protected function curl($url, $post_data = array(), $timeout = 5, $header = "", $data_type = "")
{
$header = empty($header) ? '' : $header;
//支持json数据数据提交
if ($data_type == 'json') {
$post_string = json_encode($post_data);
} elseif ($data_type == 'array') {
$post_string = $post_data;
} elseif (is_array($post_data)) {
$post_string = http_build_query($post_data, '', '&');
}
$ch = curl_init(); // 启动一个CURL会话
curl_setopt($ch, CURLOPT_URL, $url); // 要访问的地址
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 对认证证书来源的检查 // https请求 不验证证书和hosts
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 从证书中检查SSL加密算法是否存在
curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT'] ?? ''); // 模拟用户使用的浏览器
//curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); // 使用自动跳转
//curl_setopt($curl, CURLOPT_AUTOREFERER, 1); // 自动设置Referer
curl_setopt($ch, CURLOPT_POST, true); // 发送一个常规的Post请求
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_string); // Post提交的数据包
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout); // 设置超时限制防止死循环
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
//curl_setopt($curl, CURLOPT_HEADER, 0); // 显示返回的Header区域内容
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 获取的信息以文件流的形式返回
if ($header) curl_setopt($ch, CURLOPT_HTTPHEADER, $header); //模拟的header头
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
public function execute($request)
{
$this->setupCharsets($request);
//如果两者编码不一致,会出现签名验签或者乱码
if (strcasecmp($this->fileCharset, $this->postCharset)) {
// writeLog("本地文件字符集编码与表单提交编码不一致请务必设置成一样属性名分别为postCharset!");
throw new \Exception("文件编码:[" . $this->fileCharset . "] 与表单提交编码:[" . $this->postCharset . "]两者不一致!");
}
$iv = null;
if (!$this->checkEmpty($request->getApiVersion())) {
$iv = $request->getApiVersion();
} else {
$iv = $this->apiVersion;
}
//组装系统参数
$sysParams["method"] = $request->getApiMethodName();
if ($this->is_isp) {
$sysParams["acq_sp_id"] = $this->acq_sp_id;
if (!$this->checkEmpty($request->getSpId())) {
$sysParams["acq_sp_id"] = $request->getSpId();
}
}
$sysParams["product_code"] = $this->productCode;
if (!$this->checkEmpty($request->getProdCode())) {
$sysParams["product_code"] = $request->getProdCode();
}
$sysParams["function_code"] = $request->getFunctionCode();
$sysParams["format"] = $this->format;
$sysParams["charset"] = $this->postCharset;
$sysParams["sign_type"] = $this->signType;
$sysParams["timestamp"] = date("YmdHis");
$sysParams["version"] = $iv;
$apiParams = $request->getApiParas();
// 执行加密
if (empty($apiParams['biz_content'])) {
throw new \Exception(" api request Fail! The reason : encrypt request is not supperted!");
}
if (!$this->checkEmpty($request->getNotifyUrl())) {
if (is_array($apiParams['biz_content'])) {
$apiParams['biz_content']["notify_url"] = $request->getNotifyUrl();
} else {
$apiParams['biz_content'] = json_decode($apiParams['biz_content'], true);
$apiParams['biz_content']["notify_url"] = $request->getNotifyUrl();
}
}
if (is_array($apiParams['biz_content'])) {
$apiParams['biz_content'] = json_encode($apiParams['biz_content']);
}
$enCryptContent = $this->rsaEncrypt($apiParams['biz_content']);
if (empty($enCryptContent)) {
throw new \Exception("加密失败!");
}
$sysParams['biz_content'] = $enCryptContent;
//签名
$sysParams["sign"] = $this->generateSign($sysParams, $this->signType);
//发起HTTP请求
try {
$resp = $this->curl($this->gatewayUrl, $sysParams);
} catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
//解析AOP返回结果
$respObject = '';
// 将返回结果转换本地文件编码
$r = iconv($this->postCharset, $this->fileCharset . "//IGNORE", $resp);
if ("json" == strtolower($this->format)) {
$respObject = json_decode($r, true);
}
// 验签
$CheckSign = $this->rsaCheckResp($respObject);
if ($CheckSign) {
$content = $respObject['content'] ?? '';
if (!empty($content)) {
$respObject['content'] = $this->rsaDecrypt($content);
}
} else {
throw new \Exception($respObject['sub_msg']);
}
return $respObject;
}
/**
* 校验$value是否非空
* if not set ,return true;
* if is null , return true;
**/
protected function checkEmpty($value)
{
if (!isset($value))
return true;
if ($value === null)
return true;
if (trim($value) === "")
return true;
return false;
}
/***
* 获取签名
* @param $params
* @return string
*/
public function getVerSignContent($params)
{
$signStr = '';
$signStr .= 'merchant_id' . '=' . $params['merchant_id'] . '&';
$signStr .= 'product_code' . '=' . $params['product_code'] . '&';
$signStr .= 'function_code' . '=' . $params['function_code'] . '&';
$signStr .= 'biz_content' . '=' . $params['biz_content'];
$signStr = trim($signStr, '&');
return $signStr;
}
/** rsaCheckV1 & rsaCheckV2
* 验证签名
* 在使用本方法前必须初始化AopClient且传入公钥参数。
* 公钥是否是读取字符串还是读取文件,是根据初始化传入的值判断的。
**/
public function rsaCheckV1($params, $rsaPublicKey = '', $signType = 'RSA2')
{
$sign = $params['sign'];
unset($params['sign']);
unset($params['sign_type']);
return $this->verify($this->getVerSignContent($params), $sign, $rsaPublicKey, $signType);
}
/***
* 请求返回结果验签
* @param $params
* @param $rsaPublicKey
* @param $signType
* @return bool
* @throws \Exception
*/
public function rsaCheckResp($params, $rsaPublicKey = '', $signType = 'RSA2')
{
$sign = $params['sign'];
$signStr = '';
$signStr .= 'code' . '=' . $params['code'] . '&';
$signStr .= 'msg' . '=' . $params['msg'] . '&';
$signStr .= 'sub_code' . '=' . $params['sub_code'] . '&';
$signStr .= 'sub_msg' . '=' . $params['sub_msg'] . '&';
$signStr .= 'content' . '=' . $params['content'];
$signStr = trim($signStr, '&');
return $this->verify($signStr, $sign, $rsaPublicKey, $signType);
}
function verify($data, $sign, $pubKeyFile = '', $signType = 'RSA')
{
if ($this->checkEmpty($pubKeyFile)) {
$pubKey = $this->cjRsaPublicKey;
$res = "-----BEGIN PUBLIC KEY-----\n" .
wordwrap($pubKey, 64, "\n", true) .
"\n-----END PUBLIC KEY-----";
} else {
$pubKey = file_get_contents($pubKeyFile);
$res = "-----BEGIN PUBLIC KEY-----\n" .
wordwrap($pubKey, 64, "\n", true) .
"\n-----END PUBLIC KEY-----";
}
if (!$this->cjRsaPublicKey || empty($pubKey)) throw new \Exception("支付宝RSA公钥错误。请检查公钥文件格式是否正确");
//调用openssl内置方法验签返回bool值
$result = FALSE;
if ("RSA2" == $signType) {
$result = (openssl_verify($data, base64_decode($sign), $res, OPENSSL_ALGO_SHA256) === 1);
} else {
$result = (openssl_verify($data, base64_decode($sign), $res) === 1);
}
return $result;
}
/**
* 在使用本方法前必须初始化AopClient且传入公私钥参数。
* 公钥是否是读取字符串还是读取文件,是根据初始化传入的值判断的。
**/
public function rsaEncrypt($bizContentJson)
{
if (!$this->checkEmpty($this->cjRsaPublicKey)) {
//读字符串
$priKey = $this->cjRsaPublicKey;
$publicKey = "-----BEGIN PUBLIC KEY-----\n"
. wordwrap($priKey, 64, "\n", true) .
"\n-----END PUBLIC KEY-----";
} else {
throw new \Exception("支付宝RSA私钥错误。请检查公钥文件格式是否正确");
}
// 加密每段最大245解密每段最大256
$bizContentArray = mb_str_split($bizContentJson, 245, 'utf-8');
$useBizContent = '';
foreach ($bizContentArray as $bizContentString) {
openssl_public_encrypt($bizContentString, $encryptedChunk, $publicKey);
$useBizContent .= $encryptedChunk;
}
return base64_encode($useBizContent);
}
/**
* 在使用本方法前必须初始化AopClient且传入公私钥参数。
* 公钥是否是读取字符串还是读取文件,是根据初始化传入的值判断的。
**/
public function rsaDecrypt($content)
{
if (!$this->checkEmpty($this->rsaPrivateKey)) {
//读字符串
$priKey = $this->rsaPrivateKey;
$res = "-----BEGIN RSA PRIVATE KEY-----\n" .
wordwrap($priKey, 64, "\n", true) .
"\n-----END RSA PRIVATE KEY-----";
} else {
throw new \Exception("RSA私钥错误。请检查公钥文件格式是否正确");
}
$privateKey = openssl_get_privatekey($res);
// 信息解密
$bizContentArray = str_split(base64_decode($content), 256);
$content = '';
foreach ($bizContentArray as $bizContentString) {
openssl_private_decrypt($bizContentString, $decrypted, $privateKey, OPENSSL_PKCS1_PADDING);
$content .= $decrypted;
}
return is_json($content) ? json_decode($content, true) : [];
}
private function setupCharsets($request)
{
if ($this->checkEmpty($this->postCharset)) {
$this->postCharset = 'UTF-8';
}
$str = print_r($request, true);
$this->fileCharset = mb_detect_encoding($str, "UTF-8, GBK") == 'UTF-8' ? 'UTF-8' : 'GBK';
}
}