官方文档
https://pay.weixin.qq.com/docs/merchant/apis/batch-transfer-to-balance/transfer-detail/get-transfer-detail-by-out-no.html
参考文章
https://blog.csdn.net/weixin_48337755/article/details/127449073
https://blog.csdn.net/u010481239/article/details/128028494
发起转账
// 转账到零钱
// $withdrawApply = [
// 'left_money'=>1,
// 'batch_no'=>'plfk2020042013', //商户系统内部的商家批次单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一
// 'desc'=>'测试接口',
// 'openid'=>'dsadsadasd',
// 'sn'=>'plfk2023',
// 'real_name'=> '姓名'
// ];
/**
* @notes 商家转账到零钱
*/
public static function transfer($withdrawApply,$openid,$config)
{
//请求URL
$url = 'https://api.mch.weixin.qq.com/v3/transfer/batches'; //微信提现接口
//请求方式
$http_method = 'POST';
//请求参数
$data = [
'appid' => config("weixin.app_id"),//申请商户号的appid或商户号绑定的appid(企业号corpid即为此appid)
'out_batch_no' => $withdrawApply['batch_no'],//商户系统内部的商家批次单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一
'batch_name' => '提现至微信零钱',//该笔批量转账的名称
'batch_remark' => '提现至微信零钱',//转账说明,UTF8编码,最多允许32个字符
'total_amount' => $withdrawApply['left_money'] * 100,//转账金额单位为“分”。转账总金额必须与批次内所有明细转账金额之和保持一致,否则无法发起转账操作
'total_num' => 1,//一个转账批次单最多发起三千笔转账。转账总笔数必须与批次内所有明细之和保持一致,否则无法发起转账操作
'transfer_detail_list' => [
[//发起批量转账的明细列表,最多三千笔
'out_detail_no' => $withdrawApply['sn'],//商户系统内部区分转账批次单下不同转账明细单的唯一标识,要求此参数只能由数字、大小写字母组成
'transfer_amount' => $withdrawApply['left_money'] * 100,//转账金额单位为分
'transfer_remark' => '提现至微信零钱',//单条转账备注(微信用户会收到该备注),UTF8编码,最多允许32个字符
'openid' => $openid,//openid是微信用户在公众号appid下的唯一用户标识(appid不同,则获取到的openid就不同),可用于永久标记一个用户
]]
];
if ($withdrawApply['left_money'] >= 2000) {
if (empty($withdrawApply['real_name'])) {
throw new \Exception('转账金额 >= 2000元,收款用户真实姓名必填');
}
$data['transfer_detail_list'][0]['user_name'] = self::getEncrypt($withdrawApply['real_name'],$config);
}
$xml_datas = json_encode($data);
$token = self::getToken($url,$xml_datas); //获取header Token认证
$schema = config("weixin.schema");
$res = requestPost($url,$xml_datas,[
'Authorization:' . $schema . ' ' . $token, // 注意这里
'Accept: application/json',
"Content-type: application/json;charset='utf-8'",
'User-Agent: ' . $_SERVER['HTTP_USER_AGENT']
]);
if(!isset($res['create_time'])) { //批次受理失败
return reply($res,1);
}else{ // 支付成功
return reply();
}
}
/**
* 获取签名
* @param $url
* @param $body
* @param string $http_method
* @return string
*/
public static function getToken($url,$body,$http_method = "POST")
{
$timestamp = time();
$nonce = uniqid();
$merchant_id = config("weixin.mchid");
$serial_no = config("weixin.serial_no");
$mch_private_key = config("weixin.mch_private_key");
$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);
return sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
$merchant_id, $nonce, $timestamp, $serial_no, $sign);
}
结果查询
/**
* @notes 商家明细单号查询
*/
public static function detailsQuery($withdrawApply)
{
// $withdrawApply['batch_no'] = 'C8yFKtxcY';
// $withdrawApply['out_detail_no'] = 'plfk20231700099905';
$url = 'https://api.mch.weixin.qq.com/v3/transfer/batches/out-batch-no/'.$withdrawApply['batch_no'].'/details/out-detail-no/'.$withdrawApply['out_detail_no'];
// $url = 'https://api.mch.weixin.qq.com/v3/transfer/batches/out-batch-no/'.$withdrawApply['batch_no'].'?need_query_detail=true&detail_status=ALL';
//请求方式
$http_method = 'GET';
//请求参数
$data = [];
$xml_datas = json_encode($data);
$token = self::token1($url,$http_method,'');//获取token
$result =self::https_request1($url,'',$token);//发送请求
$result_arr = json_decode($result,true);
if($result_arr['detail_status']== 'SUCCESS'){
//付款成功
return reply($result_arr,1);
}else{
return reply($result_arr,0);
}
}
public static function token1($url,$http_method,$data)
{
$timestamp = time();//请求时间戳
$url_parts = parse_url($url);//获取请求的绝对URL
$nonce = $timestamp.rand('10000','99999');//请求随机串
$body = empty($data) ? '' : json_encode((object)$data);//请求报文主体
$stream_opts = [
"ssl" => [
"verify_peer"=>false,
"verify_peer_name"=>false,
]
];
$apiclient_cert_path = public_path("wx_pay_cert") . '/apiclient_cert.pem'; // 地址
$apiclient_key_path = public_path("wx_pay_cert") . '/apiclient_key.pem';
$apiclient_cert_arr = openssl_x509_parse(file_get_contents($apiclient_cert_path,false, stream_context_create($stream_opts)));
// $serial_no = $apiclient_cert_arr['serialNumberHex'];//证书序列号
$serialNo = '';
if (\strtolower(\substr($apiclient_cert_arr['serialNumber'], 0, 2)) == '0x') { // HEX format
$serialNo = \substr($apiclient_cert_arr['serialNumber'], 2);
} else { // DEC format
$value = $apiclient_cert_arr['serialNumber'];
$hexvalues = ['0','1','2','3','4','5','6','7',
'8','9','A','B','C','D','E','F'];
while ($value != '0') {
$serialNo = $hexvalues[\bcmod($value, '16')].$serialNo;
$value = \bcdiv($value, '16', 0);
}
}
$serial_no = $serialNo;//证书序列号这里要serialNumberHex
// //sjc serialNumber处理为serialNumberHex 2022年11月17日21:58:59
$mch_private_key = file_get_contents($apiclient_key_path,false, stream_context_create($stream_opts));//密钥
// $merchant_id = '*********';//商户id
$merchant_id = config("weixin.mchid");
$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);//微信返回token
// return $schema.' '.$token; // 注意这里,request 传参
return $token; // 注意这里,request 传参
}
public static function https_request1($url,$data,$token)
{
$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);
$schema = config("weixin.schema");
//添加请求头
$headers = [
// 'Authorization:'.$token, // 注意这里,request 传参
'Authorization:' . $schema . ' ' . $token, // 注意这里,request 传参
'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($headers)){
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
}
$output = curl_exec($curl);
curl_close($curl);
return $output;
}