首先在微信商户开通此功能(开通可能不易)
拿到 商户号 证书 还有证书序列号 关联商户的appid(如果是app在开放平台申请appid小程序的话那就是小程序的appid) 注意一定要关联appid
在微信商户号里 开通 api 权限 以及添加服务器的ip
/**生成认证信息
* @param $url
* @param $pars
* @param $http_method
* @param $cert_path
* @param $key_path
* @param $mch_id
* @return string
*/
public static function getToken($url, $pars, $http_method, $cert_path, $key_path, $mch_id)
{
$timestamp = time(); // 请求时间戳
$url_parts = parse_url($url); // 获取请求的绝对URL
$nonce = $timestamp . rand('10000', '99999'); // 请求随机串
$body = empty($pars) ? '' : json_encode((object)$pars); // 请求报文主体
$stream_opts = [
"ssl" => [
"verify_peer" => false,
"verify_peer_name" => false,
]
];
$apiclient_cert_arr = openssl_x509_parse(file_get_contents($cert_path, false, stream_context_create($stream_opts)));
// 证书序列号
$serial_no = $apiclient_cert_arr['serialNumberHex'];
// 密钥
$mch_private_key = file_get_contents($key_path, false, stream_context_create($stream_opts));
// 商户id:文档顶部定义
$merchant_id = $mch_id;
$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);
// 微信返回token
return sprintf('WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"', $merchant_id, $nonce, $timestamp, $serial_no, $sign);
}
/**
* @param $url
* @param $token
* @param null $serial_no
* @param null $data
* @return bool|string
*/
public static function https_request($url, $token, $serial_no = null, $data = null)
{
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, (string)$url);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
if (!empty($data)) {
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
}
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
// 添加请求头
$headers = [
'Authorization:' . $token,
'Accept: application/json',
'Content-Type: application/json; charset=utf-8',
'User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36',
];
if (!empty($serial_no)) {
array_push($headers, 'Wechatpay-Serial:' . $serial_no);
}
if (!empty($headers)) {
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
}
$output = curl_exec($curl);
curl_close($curl);
return $output;
}
/**微信敏感信息加密
* @param $str
* @param $platform_path //平台证书路径
* @return string
* @throws Exception
*/
private static function getEncrypt($str, $platform_path)
{
//$str是待加密字符串
$public_key = file_get_contents($platform_path);
$encrypted = '';
if (openssl_public_encrypt($str, $encrypted, $public_key, OPENSSL_PKCS1_OAEP_PADDING)) {
//base64编码
$sign = base64_encode($encrypted);
} else {
throw new Exception('encrypt failed');
}
return $sign;
}
/**
* @param $sOpenid
* @param $sName
* @param $sOrderId
* @param $sDetailOrderId
* @param $nMoney
* @param $appid
* @param $cert_path
* @param $key_path
* @param $mch_id
* @param $serial_no
* @param $platform_path
* @return mixed
* @throws Exception
*/
public static function transfer_batches($sOpenid, $sName, $sOrderId, $sDetailOrderId, $nMoney, $appid, $cert_path, $key_path, $mch_id, $serial_no, $platform_path)
{
// 付款到零钱方法url
$url = 'https://api.mch.weixin.qq.com/v3/transfer/batches';
// 转账备注 (微信用户会收到该备注)
$tRemark = '提现到账';
// 转账金额:微信是分为单位 *100 转换
$transfer_amount = $nMoney * 100;
// 转账接收列表设置
$transfer_detail_list = array(
[
'out_detail_no' => $sDetailOrderId, // 明细单号
'transfer_amount' => intval($transfer_amount), // 转账总金额
'transfer_remark' => $tRemark, // 单条转账备注
'openid' => $sOpenid, // 收款方openid
'user_name' => self::getEncrypt($sName, $platform_path), // 转账金额 >= 2,000元,收款用户姓名必填
],
);
// 请求参数设置
$params = [ // 请求参数设置
'appid' => $appid, // 文档顶部定义
'out_batch_no' => $sOrderId, // 商家批次单号
'batch_name' => '提现到账', // 转账的名称
'batch_remark' => '提现到账', // 转账的备注
'total_amount' => intval($transfer_amount), // 转账总金额
'total_num' => 1, // 转账总笔数
'transfer_detail_list' => $transfer_detail_list, // 转账接收列表
];
// 获取token
$token = self::getToken($url, $params, 'POST', $cert_path, $key_path, $mch_id);
// 发送请求
$res = self::https_request($url, $token, $serial_no, json_encode($params));
// 反馈数组化
return json_decode($res, true);
}
/**获取平台证书列表
* @param $cert_path//商户证书路径(绝对路径)
* @param $key_path //商户私钥路径(绝对路径)
* @param $mch_id //商户号
* @param $key //商户秘钥
* @return array
*/
public static function certificates($cert_path, $key_path, $mch_id, $key): array
{
$url = 'https://api.mch.weixin.qq.com/v3/certificates';
$token = self::getToken($url, '', 'GET', $cert_path, $key_path, $mch_id);
$res = self::https_request($url, $token);
$certificate = json_decode($res, true);
if (!isset($certificate['data'])) {
$error_msg = $certificate['message'] ?? '平台证书下载失败!';
throw new HttpException('500', $error_msg);
}
$certificate_serial_no = $certificate['data'][0]['serial_no'];//平台证书序列号
$ciphertext = $certificate['data'][0]['encrypt_certificate']['ciphertext'];
$associatedData = $certificate['data'][0]['encrypt_certificate']['associated_data'];
$nonceStr = $certificate['data'][0]['encrypt_certificate']['nonce'];
$data = self::decryptToString($ciphertext, $associatedData, $nonceStr, $key);
if (!$data) {
throw new HttpException(500, '获取证书解密失败');
}
$dir = ROOT_PATH . '/public/public_cert';
!is_dir($dir) && @mkdir($dir, 0755, true);
if (!file_exists($dir . '/wx_public_cert.pem')) {
//保存平台证书 (https://myssl.com/cert_decode.html)获取证书序列号
file_put_contents(ROOT_PATH . '/public/public_cert/wx_public_cert.pem', $data);
}
return [$certificate_serial_no, $dir . '/wx_public_cert.pem'];
}
public static function decryptToString($ciphertext, $associatedData, $nonceStr, $apiV3Key)
{
$str = base64_decode($ciphertext);
if (strlen($str) <= 16) {
return '';
}
// ext-sodium (default installed on >= PHP 7.2) 如果没有该函数需要安装sodium扩展
return sodium_crypto_aead_aes256gcm_decrypt($str, $associatedData, $nonceStr, $apiV3Key);
}