linwu 2 vuotta sitten
vanhempi
commit
ee721f821f

+ 12 - 6
app/admin/controller/Demo.php

@@ -2,6 +2,7 @@
 
 namespace app\admin\controller;
 
+use payment\wechat\WechatTransfers;
 use think\Response;
 use think\Config;
 /**
@@ -25,24 +26,29 @@ class Demo
      */
     public function test()
     {
-        $batch_name   = '余额提现';//转账的名称
-        $out_trade_no = time().rand(1000,9999);//单号
+        $batch_name   = '测试提现功能';//转账的名称
+        $out_trade_no = 'lgd'.getUniId();//单号
         $money        = 1;//金额
-        $openid       = 'o4a9g5cDIEVzS7zVzE-_VEK8y9Yg';//用户openid
-        $this->tixian($batch_name,$out_trade_no,$money,$openid);
+        $openid       = 'o4a9g5ffjsMcXS9wCtTyONqMTdEc';//用户openid
+        $detail = [];
+        $transfers = new WechatTransfers();
+        $detail[] = $transfers->getDetailList($out_trade_no,$money,$batch_name,$openid);
+        $res = $transfers->transfers($batch_name,$detail);
+        halt($res);
+//        $this->tixian($batch_name,$out_trade_no,$money,$openid);
     }
 
     public function tixian($batch_name,$out_trade_no,$money,$openid){
         $url = 'https://api.mch.weixin.qq.com/v3/transfer/batches';
         $pars = [];
         $pars['appid'] = config('wxconfig.appId');//直连商户的appid
-        $pars['out_batch_no'] = 'sjzz'.date('Ymd').mt_rand(1000, 9999);//商户系统内部的商家批次单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一
+        $pars['out_batch_no'] = 'lg'.date('Ymd').mt_rand(1000, 9999);//商户系统内部的商家批次单号,要求此参数只能由数字、大小写字母组成,在商户系统内部唯一
         $pars['batch_name']   = $batch_name;//该笔批量转账的名称
         $pars['batch_remark'] = $batch_name;//转账说明,UTF8编码,最多允许32个字符
         $pars['total_amount'] = intval($money * 100);//转账总金额 单位为“分”
         $pars['total_num']    = 1;//转账总笔数
         $pars['transfer_detail_list'][0]  = [
-            'out_detail_no'=>'Dh'.$out_trade_no,
+            'out_detail_no'=>'lgd'.$out_trade_no,
             'transfer_amount'=>$pars['total_amount'],
             'transfer_remark'=>$batch_name,
             'openid'=>$openid

+ 2 - 1
app/admin/controller/Index.php

@@ -54,8 +54,9 @@ class Index
 
     public function t2()
     {
+        halt(getUniId());
         $server = new WechatTransfers(config('wxconfig.appId'),config('wxconfig.payMchId'),config('wxconfig.appSecret'),$_SERVER['SERVER_ADDR']);
         $res = $server->sendMoney(1,'o4a9g5cDIEVzS7zVzE-_VEK8y9Yg');
-        halt($res);
+
     }
 }

+ 16 - 0
app/common.php

@@ -555,4 +555,20 @@ function importExecl($file = '', $cell = [], $crop = 0, $sheet = 0)
 
     return array_values($data);
 
+}
+
+/**
+ * 获取唯一单号
+ */
+function getUniId()
+{
+    $order_id_main = date('YmdHis') . rand(10000000,99999999);
+    $order_id_len = strlen($order_id_main);
+    $order_id_sum = 0;
+    for($i=0; $i<$order_id_len; $i++){
+        $order_id_sum += (int)(substr($order_id_main,$i,1));
+    }
+    $osn = $order_id_main . str_pad((100 - $order_id_sum % 100) % 100,2,'0',STR_PAD_LEFT);
+
+    return $osn;
 }

+ 84 - 134
extend/payment/wechat/WechatTransfers.php

@@ -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;
     }
 }