<?php
class JsApiPay
{
public $data = null;
public $serial_no = '******'; //商户证书序列号 和平台证书序列号是两回事
public $mchid='******'; //商户号
public $mch_key_v3='***************'; //v3秘钥
public function Unite($url,$http_method='GET',$body){
$Authorization=$this->getSign($url,$http_method,$body);
return $this->curl_request($url, $body, $http_method, $Authorization);
}
public function JsapiSign($prepay_id,$appid,$timestamp,$nonce){
$mch_private_key = file_get_contents('../vendor/wxpay3/cacert/apiclient_key.pem'); //获取密钥文件内容
$message = $appid."\n".
$timestamp."\n".
$nonce."\n".
$prepay_id."\n";
openssl_sign($message, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption');
$sign = base64_encode($raw_sign);
return $sign;
}
public function getSign($url,$http_method='GET',$body){
$serial_no=$this->serial_no;
$merchant_id=$this->mchid;
$timestamp=time();
$nonce=$this->getNonceStr();
$mch_private_key = file_get_contents('../vendor/wxpay3/cacert/apiclient_key.pem'); //获取密钥文件内容
$url_parts = parse_url($url);
$canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
$message = $http_method."\n".
$canonical_url."\n".
$timestamp."\n".
$nonce."\n".
$body."\n";
openssl_sign($message, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption');
$sign = base64_encode($raw_sign);
$schema = 'WECHATPAY2-SHA256-RSA2048';
$token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
$merchant_id, $nonce, $timestamp, $serial_no, $sign);
return $schema.' '.$token;
}
/*验证签名*/
public function verifysign($serial_no,$TIMESTAMP,$NONCE,$BODY,$SIGNATURE){
$SIGNATURE=base64_decode($SIGNATURE);
$message = $TIMESTAMP."\n".
$NONCE."\n".
$BODY."\n";
$mch_private_key=$this->wx_ciphertext($serial_no);
$pubkeyid = openssl_pkey_get_public($mch_private_key);
$ok=openssl_verify($message, $SIGNATURE, $pubkeyid,OPENSSL_ALGO_SHA256);
openssl_free_key($pubkeyid);
if($ok==1){
return true;
}else{
return false;
}
}
/*获取微信支付平台证书
serial_no:平台证书序列号
*/
public function wx_ciphertext(){
$url='https://api.mch.weixin.qq.com/v3/certificates';
$certificates=$this->Unite($url,'GET','');
$certificates=json_decode($certificates,true);
//此处 dump(certificates); serilal_no 为平台序列号
// foreach ($certificates['data'] as $key => $value) {
// if($value['serial_no']==$this->serial_no){
// $associatedData=$value['encrypt_certificate']['associated_data'];
// $nonceStr=$value['encrypt_certificate']['nonce'];
// $ciphertext=$value['encrypt_certificate']['ciphertext'];
// break;
// }
// }
$data=$certificates['data'][0]['encrypt_certificate'];
$associatedData=$data['associated_data'];
$nonceStr=$data['nonce'];
$ciphertext=$data['ciphertext'];
$AesUtil = new \AesUtil($this->mch_key_v3);
return $AesUtil->decryptToString($associatedData, $nonceStr, $ciphertext); //此处获取平台证书。复制到文本里改为pem后缀
}
/**
*
* 产生随机字符串,不长于32位
* @param int $length
* @return 产生的随机字符串
*/
public static function getNonceStr($length = 32)
{
$chars = "abcdefghijklmnopqrstuvwxyz0123456789";
$str ="";
for ( $i = 0; $i < $length; $i++ ) {
$str .= substr($chars, mt_rand(0, strlen($chars)-1), 1);
}
return $str;
}
/**
* @Description: curl请求
* @Author: Yang
* @param $url
* @param null $data
* @param string $method
* @param array $header
* @param bool $https
* @param int $timeout
* @return mixed
*/
function curl_request($url, $data=null, $method='get', $Authorization, $https=true, $timeout = 5){
$header = array(
'Content-Type: application/json',
'Accept: application/json',
'Wechatpay-Serial:******',//平台序列号
'Authorization: '.$Authorization
);
$user_agent = "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36";
$method = strtoupper($method);
$ch = curl_init();//初始化
curl_setopt($ch, CURLOPT_URL, $url);//访问的URL
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);//只获取页面内容,但不输出
if($https){
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);//https请求 不验证证书
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);//https请求 不验证HOST
}
if ($method != "GET") {
if($method == 'POST'){
curl_setopt($ch, CURLOPT_POST, true);//请求方式为post请求
}
if ($method == 'PUT' || strtoupper($method) == 'DELETE') {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); //设置请求方式
}
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);//请求数据
}
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header); //模拟的header头
curl_setopt($ch, CURLOPT_USERAGENT,$user_agent);
//curl_setopt($ch, CURLOPT_HEADER, false);//设置不需要头信息
$result = curl_exec($ch);//执行请求
$httpCode = curl_getinfo($ch,CURLINFO_HTTP_CODE);
if($result){
// file_put_contents('./text.txt', '请求返回:'.$result.PHP_EOL, FILE_APPEND);
$result=json_decode($result,true);
$result['httpCode']=$httpCode;
$result=json_encode($result,JSON_UNESCAPED_UNICODE);
}else{
$result=json_encode(['httpCode'=>$httpCode]);
}
curl_close($ch);//关闭curl,释放资源
return $result;
}
/**
* 敏感信息加密算法
* @param $str
* @return string
*/
public function getEncrypt($str)
{
//$str是待加密字符串
$public_key = file_get_contents('../vendor/wxpay3/cacert/pingtai_cert.pem'); //此处证书文件为平台证书
$encrypted = '';
openssl_public_encrypt($str, $encrypted, $public_key, OPENSSL_PKCS1_OAEP_PADDING);//这里 类型要设置为OPENSSL_PKCS1_OAEP_PADDING
//base64编码
$sign = base64_encode($encrypted);
return $sign;
}
}
class AesUtil{
/**
* AES key
*
* @var string
*/
private $aesKey;
const KEY_LENGTH_BYTE = 32;
const AUTH_TAG_LENGTH_BYTE = 16;
/**
* Constructor
*/
public function __construct($aesKey)
{
if (strlen($aesKey) != self::KEY_LENGTH_BYTE) {
throw new InvalidArgumentException('无效的ApiV3Key,长度应为32个字节');
}
$this->aesKey = $aesKey;
}
/**
* Decrypt AEAD_AES_256_GCM ciphertext
*
* @param string $associatedData AES GCM additional authentication data
* @param string $nonceStr AES GCM nonce
* @param string $ciphertext AES GCM cipher text
*
* @return string|bool Decrypted string on success or FALSE on failure
*/
public function decryptToString($associatedData, $nonceStr, $ciphertext)
{
$ciphertext = \base64_decode($ciphertext);
if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) {
return false;
}
// ext-sodium (default installed on >= PHP 7.2)
if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') && \sodium_crypto_aead_aes256gcm_is_available()) {
return \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->aesKey);
}
// ext-libsodium (need install libsodium-php 1.x via pecl)
if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') && \Sodium\crypto_aead_aes256gcm_is_available()) {
return \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->aesKey);
}
// openssl (PHP >= 7.1 support AEAD)
if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) {
$ctext = substr($ciphertext, 0, -self::AUTH_TAG_LENGTH_BYTE);
$authTag = substr($ciphertext, -self::AUTH_TAG_LENGTH_BYTE);
return \openssl_decrypt($ctext, 'aes-256-gcm', $this->aesKey, \OPENSSL_RAW_DATA, $nonceStr,$authTag, $associatedData);
}
throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
}
}
其中wx_ciphertext方法是获取平台证书和平台证书序列号使用,获取平台证书需要安装php7.1以上
public function tixian($openid,$out_trade_no,$money,$desc,$user_name){
//查询用户授权记录
Vendor('wxpay3.WxPayJsApiPay');
$input = new \JsApiPay();
$url='https://api.mch.weixin.qq.com/v3/transfer/batches';
$body['appid']='yourappid';
$body['out_batch_no']='Y'.date('Ymdhis',time());
$body['batch_name']='商户提现';
$body['batch_remark']='合作分成';
$body['total_amount']=$money*100;
$body['total_num']=1;
//订单风险金
$detail=[
'out_detail_no'=>$out_trade_no,
'transfer_amount'=>$money*100,
'transfer_remark'=>'商户提现',
'openid'=>$openid,
'user_name'=>$input->getEncrypt($user_name), //敏感信息加密
];
$body['transfer_detail_list'][]=$detail;
$responseData=$input->Unite($url,'POST',json_encode($body));
}