话不多说,直接上代码。
以获取签约短信为例
/**
* 签约短信
* @param $data
* @return array
* @throws BaseException
*/
public function bindMsg($data)
{
$nonceStr = $this->nonceStr();//流水号
// API参数
$params = [
'INFO'=>[
'TRX_CODE' => '310001',
'VERSION' => '04',
'DATA_TYPE' => '2',
'LEVEL' => '5',
'MERCHANT_ID' => $this->config['mch_id'],
'USER_NAME' => $this->config['username'],
'USER_PASS' => $this->config['userpass'],
'REQ_SN' => $nonceStr,
],
'FAGRA'=>[
'MERCHANT_ID' => $this->config['mch_id'],
'ACCOUNT_TYPE' => $data['account_type'],
'ACCOUNT_NO' => $data['account_no'],
'ACCOUNT_NAME' => $data['account_name'],
'ACCOUNT_PROP' => $data['account_prop'],
'ID_TYPE' => $data['id_type'],
'ID' => $data['id'],
'TEL' => $data['tel']
]
];
// 生成签名
$params['INFO']['SIGNED_MSG'] = $this->makeSign($params);
$this->doLogs($params);
// 请求API
$url = $this->config['url'].'?MERCHANT_ID='.$this->config['mch_id'].'&REQ_SN='.$nonceStr;
$postData = iconv("UTF-8//IGNORE", "GBK", $this->toXml($params));
$ret_xml = $this->post($url, $postData);
$ret_json = $this->fromXml($ret_xml);
//验签
if(!$this->verifySign($ret_xml)){
throw new BaseException(['msg' => "验签失败!",'code'=>0]);
}
return $ret_json;
}
第一步:生成签名,通过私钥文件生成签名
/**
* 生成签名
* @param $data
* @return string
*/
private function makeSign($data)
{
$data = iconv("UTF-8//IGNORE", "GBK", $this->toXml($data));
$pwd = $this->config['pravate_cert_password'];
$certPath = $this->config['pravate_cert_path'];//私钥文件地址
$privateKey = file_get_contents($certPath);
if (!$privateKey) {
throw new BaseException(['msg' => "读取密钥文件失败",'code'=>0]);
}
openssl_pkcs12_read($privateKey, $certs, $pwd);
if(!openssl_sign($data, $signature, $certs['pkey'])){
return false;
}
$sign = bin2hex($signature); //strtoupper
return $sign;
}
第二步:输出xml字符
/**
* 输出xml字符
* @param $values
* @return bool|string
*/
private function toXml($values)
{
if (!is_array($values) || count($values) <= 0) {
return false;
}
$xml = '<?xml version="1.0" encoding="GBK"?><AIPG>';
foreach ($values as $key => $val) {
$xml .= "<" . $key . ">";
foreach ($val as $k => $v) {
$xml .= "<" . $k . ">" . $v . "</" . $k . ">";
}
$xml .="</" . $key . ">";
}
$xml .= "</AIPG>";
return $xml;
}
第三步:发起post请求
/**
* 模拟POST请求
* @param $url
* @param array $data
* @param bool $useCert
* @param array $sslCert
* @return mixed
*/
protected function post($url, $data = [])
{
$header = [
'Content-Type:text/html; charset=GBK;'
];
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_POST, TRUE);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
$result = curl_exec($curl);
curl_close($curl);
return $result;
}
第四步:将返回数据xml转为数组
/**
* 将xml转为array
* @param $xml
* @return mixed
*/
private function fromXml($xml)
{
// 禁止引用外部xml实体
libxml_disable_entity_loader(true);
return json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
}
第五步:回调数据验签
/**
* 校验签名
* @param array 参数
*/
private function verifySign($xmlString){
preg_match('/\<SIGNED_MSG\>([\s\S]*?)<\/SIGNED_MSG\>/', $xmlString, $matches);
$sign = $matches[1];
$data = str_replace($matches[0], '', $xmlString);
$sign = hex2bin($sign);
$filePath = $this->config['public_cert_path'];//公钥地址
$cert = file_get_contents($filePath);
if (!$cert) {
throw new BaseException(['msg' => "读取密钥文件失败",'code'=>0]);
}
$publickey = '-----BEGIN CERTIFICATE-----'.PHP_EOL
.chunk_split(base64_encode($cert), 64, PHP_EOL)
.'-----END CERTIFICATE-----'.PHP_EOL;
$key = openssl_get_publickey($publickey);
$result = openssl_verify($data, $sign, $key, OPENSSL_ALGO_SHA1) === 1;
openssl_free_key($key);
return $result;
}