|
@@ -7,154 +7,104 @@ namespace payment\wechat;
|
|
|
|
|
|
class WechatTransfers
|
|
|
{
|
|
|
- const TRANSFERS_URL = '/mmpaymkttransfers/promotion/transfers';
|
|
|
+ const TRANSFERS_URL = 'https://api.mch.weixin.qq.com/v3/transfer/batches';
|
|
|
|
|
|
- private $app_id = '';
|
|
|
- private $mchid = '';
|
|
|
- private $secret_key = '';
|
|
|
- private $ip = '';
|
|
|
-
|
|
|
- public function __construct($app_id, $mchid, $secret_key, $ip)
|
|
|
- {
|
|
|
- $this->app_id = $app_id;
|
|
|
- $this->mchid = $mchid;
|
|
|
- $this->secret_key = $secret_key;
|
|
|
- $this->ip = $ip;
|
|
|
- }
|
|
|
-
|
|
|
- public function xmltoarray($xml)
|
|
|
+ public function transfers($batch_name, $detail_list)
|
|
|
{
|
|
|
- //禁止引用外部xml实体
|
|
|
- libxml_disable_entity_loader(true);
|
|
|
- $xmlstring = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
|
|
|
- $val = json_decode(json_encode($xmlstring), true);
|
|
|
- return $val;
|
|
|
+ $pars = [];
|
|
|
+ $pars['appid'] = config('wxconfig.appId');//直连商户的appid
|
|
|
+ $pars['out_batch_no'] = getUniId();//商户系统内部的商家批次单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一
|
|
|
+ $pars['batch_name'] = $batch_name;//该笔批量转账的名称
|
|
|
+ $pars['batch_remark'] = $batch_name;//转账说明,UTF8编码,最多允许32个字符
|
|
|
+ $pars['total_amount'] = intval(array_sum(array_column($detail_list,'transfer_amount')) * 100);//转账总金额 单位为“分”
|
|
|
+ $pars['total_num'] = count($detail_list);//转账总笔数
|
|
|
+ $pars['transfer_detail_list'] = $detail_list;
|
|
|
+ $token = $this->getToken($pars);//获取token
|
|
|
+ $res = $this->https_request(self::TRANSFERS_URL, json_encode($pars), $token);//发送请求
|
|
|
+ $resArr = json_decode($res, true);
|
|
|
+ return $resArr;
|
|
|
+ //成功返回
|
|
|
+ // array(3) {
|
|
|
+ // ["batch_id"] => string(40) "1030001016101247194272022062900873000000"
|
|
|
+ // ["create_time"] => string(25) "2022-06-29T10:21:30+08:00"
|
|
|
+ // ["out_batch_no"] => string(16) "sjzz202206291647001"
|
|
|
+ // }
|
|
|
}
|
|
|
|
|
|
- public function arraytoxml($data)
|
|
|
+ private function https_request($url, $data = null, $token)
|
|
|
{
|
|
|
- $str = '<xml>';
|
|
|
- foreach ($data as $k => $v) {
|
|
|
- $str .= '<' . $k . '>' . $v . '</' . $k . '>';
|
|
|
+ $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);
|
|
|
}
|
|
|
- $str .= '</xml>';
|
|
|
- return $str;
|
|
|
- }
|
|
|
-
|
|
|
- public function createNoncestr($length = 32)
|
|
|
- {
|
|
|
- $chars = "ABCDEFGHIJKLMNOPQRSTUVWXYabcdefghijklmnopqrstuvwxyz0123456789";
|
|
|
- $str = "";
|
|
|
-
|
|
|
- for ($i = 0; $i < $length; $i++) {
|
|
|
- $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
|
|
|
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
|
|
|
+ //添加请求头
|
|
|
+ $headers = [
|
|
|
+ 'Authorization:WECHATPAY2-SHA256-RSA2048 ' . $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($headers)) {
|
|
|
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
|
|
|
}
|
|
|
- return $str;
|
|
|
+ $output = curl_exec($curl);
|
|
|
+ curl_close($curl);
|
|
|
+ return $output;
|
|
|
}
|
|
|
|
|
|
- public function curl_post_ssl($url, $xmldata, $second = 30, $aHeader = [])
|
|
|
+ private function getToken($pars)
|
|
|
{
|
|
|
- $isdir = root_path('extend/payment/cert');//证书位置;绝对路径
|
|
|
-
|
|
|
- $ch = curl_init();//初始化curl
|
|
|
-
|
|
|
- curl_setopt($ch, CURLOPT_TIMEOUT, $second);//设置执行最长秒数
|
|
|
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);//要求结果为字符串且输出到屏幕上
|
|
|
- curl_setopt($ch, CURLOPT_URL, $url);//抓取指定网页
|
|
|
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);// 终止从服务端进行验证
|
|
|
- curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);//
|
|
|
- curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');//证书类型
|
|
|
- curl_setopt($ch, CURLOPT_SSLCERT, $isdir . 'apiclient_cert.pem');//证书位置
|
|
|
- curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');//CURLOPT_SSLKEY中规定的私钥的加密类型
|
|
|
- curl_setopt($ch, CURLOPT_SSLKEY, $isdir . 'apiclient_key.pem');//证书位置
|
|
|
- curl_setopt($ch, CURLOPT_CAINFO, 'PEM');
|
|
|
- curl_setopt($ch, CURLOPT_CAINFO, $isdir . 'rootca.pem');
|
|
|
- if (($aHeader) >= 1) {
|
|
|
- curl_setopt($ch, CURLOPT_HTTPHEADER, $aHeader);//设置头部
|
|
|
- }
|
|
|
- curl_setopt($ch, CURLOPT_POST, 1);//post提交方式
|
|
|
- curl_setopt($ch, CURLOPT_POSTFIELDS, $xmldata);//全部数据使用HTTP协议中的"POST"操作来发送
|
|
|
+ // $url = 'https://api.mch.weixin.qq.com/v3/certificates';
|
|
|
+ $url = 'https://api.mch.weixin.qq.com/v3/transfer/batches';
|
|
|
+ $http_method = 'POST';//请求方法(GET,POST,PUT)
|
|
|
+ $timestamp = time();//请求时间戳
|
|
|
+ $url_parts = parse_url($url);//获取请求的绝对URL
|
|
|
+ $nonce = $timestamp . rand('10000', '99999');//请求随机串
|
|
|
+ $body = json_encode((object)$pars);//请求报文主体
|
|
|
+ $stream_opts = [
|
|
|
+ "ssl" => [
|
|
|
+ "verify_peer" => false,
|
|
|
+ "verify_peer_name" => false,
|
|
|
+ ],
|
|
|
+ ];
|
|
|
|
|
|
- $data = curl_exec($ch);//执行回话
|
|
|
- if ($data) {
|
|
|
- curl_close($ch);
|
|
|
- return $data;
|
|
|
- } else {
|
|
|
- $error = curl_errno($ch);
|
|
|
- echo "call faild, errorCode:$error";
|
|
|
- curl_close($ch);
|
|
|
- return false;
|
|
|
- }
|
|
|
+// $apiclient_cert_path = 'https://' . $_SERVER['HTTP_HOST'] . '/uploads/apiclient/apiclient_cert.pem';
|
|
|
+// $apiclient_key_path = 'https://' . $_SERVER['HTTP_HOST'] . '/uploads/apiclient/apiclient_key.pem';
|
|
|
+ $apiclient_key_path = root_path('extend/payment/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'];//证书序列号
|
|
|
+ $serial_no = config('wxconfig.serial');//证书序列号
|
|
|
+ $mch_private_key = file_get_contents($apiclient_key_path, false, stream_context_create($stream_opts));//密钥
|
|
|
+ $merchant_id = config('wxconfig.payMchId');//商户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 = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
|
|
|
+ $merchant_id, $nonce, $timestamp, $serial_no, $sign);//微信返回token
|
|
|
+ return $token;
|
|
|
}
|
|
|
|
|
|
- function sendMoney($amount, $re_openid, $desc = '测试', $check_name = '')
|
|
|
+ /**
|
|
|
+ * 转账明细列表
|
|
|
+ */
|
|
|
+ public function getDetailList($out_trade_no, $money, $batch_name, $openid)
|
|
|
{
|
|
|
-
|
|
|
- $total_amount = (100) * $amount;
|
|
|
-
|
|
|
- $data = [
|
|
|
- 'mch_appid' => $this->app_id,//商户账号appid
|
|
|
- 'mchid' => $this->mchid,//商户号
|
|
|
- 'nonce_str' => $this->createNoncestr(),//随机字符串
|
|
|
- 'partner_trade_no' => date('YmdHis') . rand(1000, 9999),//商户订单号
|
|
|
- 'openid' => $re_openid,//用户openid
|
|
|
- 'check_name' => 'NO_CHECK',//校验用户姓名选项,
|
|
|
- 're_user_name' => $check_name,//收款用户姓名
|
|
|
- 'amount' => $total_amount,//金额
|
|
|
- 'desc' => $desc,//企业付款描述信息
|
|
|
- 'spbill_create_ip' => $this->ip,//Ip地址
|
|
|
+ return [
|
|
|
+ 'out_detail_no' => $out_trade_no,
|
|
|
+ 'transfer_amount' => $money,
|
|
|
+ 'transfer_remark' => $batch_name,
|
|
|
+ 'openid' => $openid,
|
|
|
];
|
|
|
-
|
|
|
- //生成签名算法
|
|
|
- $secrect_key = $this->secret_key;///这个就是个API密码。MD5 32位。
|
|
|
- $data = array_filter($data);
|
|
|
- ksort($data);
|
|
|
- $str = '';
|
|
|
- foreach ($data as $k => $v) {
|
|
|
- $str .= $k . '=' . $v . '&';
|
|
|
- }
|
|
|
- $str .= 'key=' . $secrect_key;
|
|
|
- $data['sign'] = md5($str);
|
|
|
- //生成签名算法
|
|
|
-
|
|
|
-
|
|
|
- $xml = $this->arraytoxml($data);
|
|
|
-
|
|
|
- $url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers'; //调用接口
|
|
|
- $res = $this->curl_post_ssl($url, $xml);
|
|
|
- $return = $this->xmltoarray($res);
|
|
|
-
|
|
|
-
|
|
|
- print_r($return);
|
|
|
- //返回来的结果是xml,最后转换成数组
|
|
|
- /*
|
|
|
- array(9) {
|
|
|
- ["return_code"]=>
|
|
|
- string(7) "SUCCESS"
|
|
|
- ["return_msg"]=>
|
|
|
- array(0) {
|
|
|
- }
|
|
|
- ["mch_appid"]=>
|
|
|
- string(18) "wx57676786465544b2a5"
|
|
|
- ["mchid"]=>
|
|
|
- string(10) "143345612"
|
|
|
- ["nonce_str"]=>
|
|
|
- string(32) "iw6TtHdOySMAfS81qcnqXojwUMn8l8mY"
|
|
|
- ["result_code"]=>
|
|
|
- string(7) "SUCCESS"
|
|
|
- ["partner_trade_no"]=>
|
|
|
- string(18) "201807011410504098"
|
|
|
- ["payment_no"]=>
|
|
|
- string(28) "1000018301201807019357038738"
|
|
|
- ["payment_time"]=>
|
|
|
- string(19) "2018-07-01 14:56:35"
|
|
|
- }
|
|
|
- */
|
|
|
-
|
|
|
-
|
|
|
- $responseObj = simplexml_load_string($res, 'SimpleXMLElement', LIBXML_NOCDATA);
|
|
|
- echo $res = $responseObj->return_code; //SUCCESS 如果返回来SUCCESS,则发生成功,处理自己的逻辑
|
|
|
-
|
|
|
- return $res;
|
|
|
}
|
|
|
}
|