|
@@ -0,0 +1,160 @@
|
|
|
+<?php
|
|
|
+namespace app\service;
|
|
|
+use GuzzleHttp\Client;
|
|
|
+use GuzzleHttp\HandlerStack;
|
|
|
+use WechatPay\GuzzleMiddleware\WechatPayMiddleware;
|
|
|
+use WeChatPay\Util\PemUtil;
|
|
|
+
|
|
|
+class WechatTransferSvc
|
|
|
+{
|
|
|
+ public $mchid = '你的商户id';
|
|
|
+ public $appid = '小程序appid';
|
|
|
+ public $serial_no = '6B66AFDAF3FC7D4AFDA8A51F61194B0DC8432BE1'; // 证书序列号,不知道怎么获取的看上面的图
|
|
|
+
|
|
|
+ private static function filePath($path){
|
|
|
+ return RUNTIME_PATH.$path;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function __construct()
|
|
|
+ {
|
|
|
+ $this->mchid=config('site.mch_id');
|
|
|
+ $this->appid=config('site.sender_appid');
|
|
|
+ $this->serial_no=file_get_contents(self::filePath('serial_no'));
|
|
|
+ }
|
|
|
+
|
|
|
+ public static function client(){
|
|
|
+ static $client;
|
|
|
+ if(!$client) {
|
|
|
+ $merchantId = config('pay.wechat.mch_id'); // 商户号
|
|
|
+ $merchantSerialNumber = config('pay.wechat.serial_no'); // 商户API证书序列号
|
|
|
+ $merchantPrivateKey = PemUtil::loadPrivateKey(self::filePath('key')); // 商户私钥文件路径
|
|
|
+ $wechatpayCertificate = PemUtil::loadCertificate(self::filePath('platform.pem')); // 微信支付平台证书文件路径
|
|
|
+ $wechatpayMiddleware = WechatPayMiddleware::builder()
|
|
|
+ ->withMerchant($merchantId, $merchantSerialNumber, $merchantPrivateKey) // 传入商户相关配置
|
|
|
+ ->withWechatPay([$wechatpayCertificate]) // 可传入多个微信支付平台证书,参数类型为array
|
|
|
+ ->build();
|
|
|
+ $stack = HandlerStack::create();
|
|
|
+ $stack->push($wechatpayMiddleware, 'wechatpay');
|
|
|
+ $client = new Client(['handler' => $stack]);
|
|
|
+ }
|
|
|
+ return $client;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function transfer($openid, $trade_no,$detail_no, $money, $desc='提现')
|
|
|
+ {
|
|
|
+ $money=$money*100;
|
|
|
+ $res=self::client()->post($url='https://api.mch.weixin.qq.com/v3/transfer/batches',[
|
|
|
+ 'json'=>$body=[
|
|
|
+ "appid" => $this->appid,//appid
|
|
|
+ "out_batch_no" => $trade_no,//商家批次单号
|
|
|
+ "batch_name" => $desc,//批次名称
|
|
|
+ "batch_remark" => $desc,//批次备注
|
|
|
+ "total_amount" => $money,// 转账金额单位为“分”
|
|
|
+ "total_num" => 1, // 转账总笔数
|
|
|
+ "transfer_detail_list" => [
|
|
|
+ [
|
|
|
+ 'out_detail_no' => $detail_no,
|
|
|
+ 'transfer_amount' => $money,
|
|
|
+ 'transfer_remark' => $desc,
|
|
|
+ 'openid' => $openid,
|
|
|
+ ]
|
|
|
+ ]
|
|
|
+ ],
|
|
|
+ 'headers'=>[
|
|
|
+ 'Accept'=>'application/json',
|
|
|
+ 'Authorization'=>$this->token($url,$body),
|
|
|
+ 'Wechatpay-Serial'=>config('pay.wechat.serial_no')
|
|
|
+ ]
|
|
|
+ ]);
|
|
|
+ if($res->getStatusCode()!==200){
|
|
|
+ throw new \Exception($res->getBody()->getContents());
|
|
|
+ }
|
|
|
+ return [
|
|
|
+ json_decode($res->getBody()->getContents(),true),
|
|
|
+ [$trade_no,$detail_no],
|
|
|
+ $money
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ public function checkDetail($trade_no,$detail_no){
|
|
|
+ $response=self::client()
|
|
|
+ ->get($url="https://api.mch.weixin.qq.com/v3/transfer/batches/out-batch-no/$trade_no/details/out-detail-no/$detail_no",[
|
|
|
+ 'headers'=>[
|
|
|
+ 'Accept'=>'application/json',
|
|
|
+ 'Authorization'=>$this->token($url,''),
|
|
|
+ 'Wechatpay-Serial'=>config('pay.wechat.serial_no')
|
|
|
+ ]
|
|
|
+ ]);
|
|
|
+ if($response->getStatusCode()!==200){
|
|
|
+ throw new \Exception($response->getBody()->getContents());
|
|
|
+ }
|
|
|
+ $res=json_decode($response->getBody()->getContents(),true);
|
|
|
+ return [
|
|
|
+ $res['detail_status'],
|
|
|
+ $res
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ public function getCert(){
|
|
|
+ $client=(new Client)
|
|
|
+ ->get($url='https://api.mch.weixin.qq.com/v3/certificates',[
|
|
|
+ 'headers'=>[
|
|
|
+ 'Authorization'=>$this->token($url,'','GET'),
|
|
|
+ 'Accept'=>'application/json',
|
|
|
+ ]
|
|
|
+ ]);
|
|
|
+ $arr=json_decode($client->getBody()->getContents(),true);
|
|
|
+ $cert=$arr['data'][0]??null;
|
|
|
+ dd($cert);
|
|
|
+ }
|
|
|
+
|
|
|
+ public function token($url,$body,$http_method="POST"){
|
|
|
+ if(is_array($body)){
|
|
|
+ $body=json_encode($body,JSON_UNESCAPED_UNICODE);
|
|
|
+ }
|
|
|
+ $url_parts = parse_url($url);
|
|
|
+ $timestamp=time();
|
|
|
+ $nonce = strtoupper($this->createNonceStr(32));
|
|
|
+ $canonical_url = $url_parts['path'];
|
|
|
+ $message = $http_method."\n".
|
|
|
+ $canonical_url."\n".
|
|
|
+ $timestamp."\n".
|
|
|
+ $nonce."\n".
|
|
|
+ $body."\n";
|
|
|
+
|
|
|
+ openssl_sign($message, $raw_sign, $this->getPrivateKey(), 'sha256WithRSAEncryption');
|
|
|
+ $sign = base64_encode($raw_sign);
|
|
|
+
|
|
|
+ $schema = 'WECHATPAY2-SHA256-RSA2048';
|
|
|
+ $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
|
|
|
+ $this->mchid, $nonce, $timestamp, $this->serial_no, $sign);
|
|
|
+ return "$schema $token";
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 生成随机32位字符串
|
|
|
+ * @param $length
|
|
|
+ * @return string
|
|
|
+ */
|
|
|
+ public function createNonceStr($length = 16) { //生成随机16个字符的字符串
|
|
|
+ $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
|
+ $str = "";
|
|
|
+ for ($i = 0; $i < $length; $i++) {
|
|
|
+ $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
|
|
|
+ }
|
|
|
+ return $str;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取私钥
|
|
|
+ * @param $filepath
|
|
|
+ * @return false|resource
|
|
|
+ */
|
|
|
+ private function getPrivateKey($filepath = '')
|
|
|
+ {
|
|
|
+ if (empty($filepath)) {
|
|
|
+ //私钥位置
|
|
|
+ $filepath = self::filePath('key');
|
|
|
+ }
|
|
|
+ return openssl_get_privatekey(file_get_contents($filepath));
|
|
|
+ }
|
|
|
+}
|