〔支付接入〕微信的 h5 支付和 jsapi 支付

本文涉及的产品
.cn 域名,1个 12个月
简介: 学会微信支付,打开你的财富之门

🎈 申请商户号

  • 申请地址:https://pay.weixin.qq.com/
  • 如果你还没有微信商户号,请点击上面的链接进行申请,如果已经有了,可以跳过这一步

🎈 申请商户证书

🎈 设置APIv3密钥

  • 首先点击 账户中心API安全设置APIv3密钥设置
  • 会看到有两个密钥,分别是 APIv2密钥APIv3密钥,由于 APIv2密钥 已经逐渐废弃了,所以只需要申请 APIv3密钥 即可
  • 密钥可由数字大小写字母组合,输入任意的 32 位字符,该密钥需要保存好,供后面使用

<?php// 生成32位的APIv3随机密钥$chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
echosubstr(str_shuffle($chars), 0, $length);

🎈 下载 SDK 开发包

# 初始化文件夹composer init
# 推荐使用 PHP 包管理工具 Composer 安装 SDKcomposer require wechatpay/wechatpay

🎈 下载平台证书

  • 平台证书跟上面申请的商户证书不是同一个东西,在后期请求中,平台证书和商户证书都要带上
  • 上面命令执行完之后,会有一个 vendor/bin/CertificateDownloader.php 文件
  • 如果你是第一次申请平台证书,需要执行命令:php CertificateDownloader.php -k ${apiV3key} -m ${mchId} -f ${mchPrivateKeyFilePath} -s ${mchSerialNo} -o ${outputFilePath}
  • -k:apiv3 秘钥,上面自己设置的32位数的密钥
  • -m: 商户号,微信商户平台可以查询
  • -f: 微信商户API私钥文件目录,也就是第二步申请商户证书里面生成的 apiclient_key.pem 路径
  • -s: 证书序列号,在 账户中心API安全管理证书 中可以看见,如果有多个证书,找到自己正在使用的证书序列号
  • -o: 生成后的证书保存地址
cd vendor/bin/
php CertificateDownloader.php -k 241xxxxxxxxxxxxxxxxx44 -m 1xxxxxxx1 -f ../../cert/merchant/apiclient_key.pem -s Wxxxxxxxxxxxxxxxx4 -o  ../../cert/wechatpay/

🎈 关联 AppID 账号

  • 因为使用的是微信支付,所以用户支付后,需要通过微信号通知用户支付的一些信息,所以需要在商户号下至少关联一个公众号

🎈 开通 H5 支付

  • 点击 产品中心我的产品H5支付点击开通
  • 开通后,选择 开发配置H5支付域名 申请添加 H5支付域名
  • 申请支付域名需要先做好产品的页面,申请的时候需要有页面的截图,截图中还要 截取到域名,支付的审核算是很严格的,如果申请不过,驳回后再申请,审核通过的时间会越来越长,所以最好一次性就把材料收集好,另外还要域名的备案的 IPC 截图
  • IPC 备案查询地址:https://beian.miit.gov.cn/
  • 关于域名的填写,如果只填写域名不填写具体域名路径,微信在支付的时候就只会校验域名,这也是最方便的,因为域名下有多个项目有支付功能的话,就不需要重复添加了

🎈 H5支付流程

  • H5支付是在微信以外的浏览器使用的,如果是微信内的话,使用的是 jsapi 支付
  • 所以一般用户进入页面的第一件事,就是检测用户使用的环境是微信浏览器还是其他浏览器
  • 前端传一些用户挑选商品后的参数,并请求后端处理接口,后端应该将一些参数进行入库,顺便请求 H5 支付接口
  • 接口应该返回跳转链接 h5_url,如果你想用户付款之后到结果页面,需要添加 redirect_url 参数,这个参数一定要用 encodeURIComponent 进行处理
  • 由于官方在 jssapi 支付中说明,不要相信前端的 success 结果,所以需要在结果页中,让用户自动触发查询结果,因此需要返回后端生成的订单号,用作在结果页的用户手动点击查询
// 判断是否微信浏览器functionisWeChat() {
varua=navigator.userAgent.toLowerCase();
if (ua.match(/MicroMessenger/i) =='micromessenger') {
returntrue;
    } else {
returnfalse;
    }
}
if(isWeChat()) {
// 是微信中打开的产品页面alert('微信内不支持h5支付,请在外部浏览器打开页面');
} else {
// 非微信内打开的产品页面,请求接口,获取支付的跳转链接// 前端用户选的产品,以及产品的金额,传一些参数过去letparams= {
total: 2, // 单位:元description: 'Image形象店-深圳腾大-QQ公仔'// 产品的介绍// ....更多入库参数    };
$.getJSON('后端接口地址/h5?'+$.param(params) +'&callback=?', function(res) {
// 拉起微信支付界面,成功后会跳转到redirect_url链接$(location).attr("href", res.data.h5_url+"&redirect_url="+encodeURIComponent(`https://xxxxxx/finish?out_trade_no=${res.data.out_trade_no}`))
    });
}
<?php// 仅仅用作展示,不可直接复制使用require_once('../vendor/autoload.php');
useWeChatPay\Builder;
useWeChatPay\Crypto\Rsa;
useWeChatPay\Util\PemUtil;
// 接受参数,相当于原生的$_GET$input=$request->only(['name', 'total', 'description', 'phone']);
// 生成商户订单号$out_trade_no=getOutTradeNo();
// 处理金额// 由于微信使用的是分作为单位,所以前端传的是元的话,需要转换一下$total=$input['total'] *100;
// 商户号$merchantId='1xxxxxx1';
// 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名$merchantPrivateKeyFilePath='file://../cert/merchant/apiclient_key.pem';
$merchantPrivateKeyInstance=Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
// 「商户API证书」的「证书序列号」$merchantCertificateSerial='1xxxxxxxxxxxxxxxxxxxxx91';
// 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名$platformCertificateFilePath='file://../cert/wechatpay/wechatpay_4xxxxxxxxxxxxxxxxxxx9.pem';
$platformPublicKeyInstance=Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
// 从「微信支付平台证书」中获取「证书序列号」$platformCertificateSerial=PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
// 构造一个 APIv3 客户端实例$instance=Builder::factory([
'mchid'=>$merchantId,
'serial'=>$merchantCertificateSerial,
'privateKey'=>$merchantPrivateKeyInstance,
'certs'=> [
$platformCertificateSerial=>$platformPublicKeyInstance,
    ],
]);
try {
$resp=$instance->chain('v3/pay/transactions/h5')
->post(['json'=> [
'mchid'=>$merchantId, // 商户号'out_trade_no'=>$out_trade_no, // 商户订单号'appid'=>'********换成跟商户号绑定的公众号APPID**********',
'description'=>$input['description'], //商品描述'notify_url'=>'https://xxxxx/notify', // 用户支付后的回调地址,在这里修改订单的状态'amount'=> [
'total'=>$total, // 微信处理的单位是分'currency'=>'CNY'            ],
'scene_info'=> [
'payer_client_ip'=>getClientIP(), // 有些框架有自带获取获取客户端IP'h5_info'=> [
'type'=>'Wap'                ]
            ]
        ]]);
// 如果请求成功,需要将一些参数进行入库,这里仅作演示,非正式数据入库$response=Db::table('order')->insert([
'name'=>$input['name'],
'description'=>$input['description'],
'total'=>$input['total'],
'phone'=>$input['phone'],
'trade_state'=>'START',
   ]);
// 入库成功后,将跳转链接和订单号传给前端,前端拿到跳转地址跳转即可if($response) {
returnjsonp([
'code'=>200,
'msg'=>'操作成功',
'data'=> [
'out_trade_no'=>$out_trade_no,
'h5_url'=>json_decode($resp->getBody(), true)['h5_url']
           ]
        ]);
   } else {
returnjsonp([
'code'=>100,
'msg'=>'操作失败'       ]);
   }
} catch (\Exception$e) {
// 进行错误处理if ($einstanceof\GuzzleHttp\Exception\RequestException&&$e->hasResponse()) {
$r=$e->getResponse();
echo$r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
    }
}
// 生成唯一商户订单号,订单号不能超过32位,并且在同一个商户下订单号不能重复// 如果并发不高,基本这样生成就可以,不会有重复的情况出现的functiongetOutTradeNo()
{
$out_trade_no=date('ymdHis') . mt_rand(1000, 9999) . uniqid();
returnmb_substr($out_trade_no, 0, 32);
}
// 获取客户端的IPfunctiongetClientIP()
{
if (@$_SERVER["HTTP_ALI_CDN_REAL_IP"]) {
$ip=$_SERVER["HTTP_ALI_CDN_REAL_IP"];
    } elseif (@$_SERVER["HTTP_X_FORWARDED_FOR"] ?: false) {
$ips=explode(',', $_SERVER["HTTP_X_FORWARDED_FOR"]);
$ip=$ips[0];
    } elseif (@$_SERVER["HTTP_CDN_SRC_IP"] ?: false) {
$ip=$_SERVER["HTTP_CDN_SRC_IP"];
    } elseif (getenv('HTTP_CLIENT_IP')) {
$ip=getenv('HTTP_CLIENT_IP');
    } elseif (getenv('HTTP_X_FORWARDED')) {
$ip=getenv('HTTP_X_FORWARDED');
    } elseif (getenv('HTTP_FORWARDED_FOR')) {
$ip=getenv('HTTP_FORWARDED_FOR');
    } elseif (getenv('HTTP_FORWARDED')) {
$ip=getenv('HTTP_FORWARDED');
    } else {
$ip=$_SERVER['REMOTE_ADDR'];
    }
$ip=str_replace(['::ffff:', '[', ']'], ['', '', ''], $ip);
return$ip;
}
<?php// 回调处理,当用户支付订单后,微信会请求该接口,也就是上面在notify_url中填写的接口// 在这里我们可以修改订单的状态啥的publicfunctionnotify()
{
// 获取参数$inBody=file_get_contents('php://input');
// APIv3密钥$apiv3Key='xxxxxxxxxxxx';
// 转换通知的JSON文本消息为PHP Array数组$inBodyArray= (array)json_decode($inBody, true);
// 加密文本消息解密$inBodyResource=AesGcm::decrypt(
$inBodyArray['resource']['ciphertext'],
$apiv3Key,
$inBodyArray['resource']['nonce'],
$inBodyArray['resource']['associated_data']
    );
// 把解密后的文本转换为PHP Array数组$inBodyResourceArray= (array)json_decode($inBodyResource, true);
try {
// 获取订单信息$order=Db::table('order')->where('out_trade_no', $inBodyResourceArray['out_trade_no'])->first();
Db::startTrans();
if ($order) {
// 修改order订单的状态Db::table('order')->where('id', $order['id'])->update([
'openid'=>$inBodyResourceArray['payer']['openid'],
'trade_state'=>$inBodyResourceArray['trade_state']
            ]);
Db::table('payment')->insert([
'out_trade_no'=>$inBodyResourceArray['out_trade_no'],
'transaction_id'=>$inBodyResourceArray['transaction_id'],
'trade_type'=>$inBodyResourceArray['trade_type'],
'trade_state'=>$inBodyResourceArray['trade_state'],
'trade_state_desc'=>$inBodyResourceArray['trade_state_desc'],
'total_amount'=>$inBodyResourceArray['amount']['total'],
'bank_type'=>$inBodyResourceArray['bank_type'],
'success_time'=>strtotime($inBodyResourceArray['success_time'])
            ]);
Db::commit();
        } else {
Db::rollback();
        }
    } catch (\Exception$e) {
Db::rollback();
    }
}

🎈 开通 JSAPI 支付

  • 点击 产品中心我的产品JSAPI支付点击开通
  • 开通后,选择 开发配置JSAPI支付域名 申请添加 JSAPI支付域名
  • 关于申请支付域名的流程基本都差不多要求也差不多,看上面的 H5支付域名 申请就行,这里就不过多赘述了

🎈 JSAPI 支付流程

  • JSAPI支付是在微信内的浏览器使用的,如果用户是在微信外打开的话,需要提醒去微信内打开页面
  • JSAPI支付需要使用微信内置的 WeixinJSBridge.invoke 方法
  • 由于 JSAPI 调用支付需要用到用户的 openid,所以需要想方设法在用户调用 JSAPI 之前获取到 openid点击查看获取 openid 的官方文档
  • 获取用户 openid,需要先获取 code,这个经常做微信业务的人都知道,那么如何在用户无感知的情况下就获取到 openid
  • 思路就是,一般支付最少会有3个页面,这里标注为abc 三个页面,通常是在 a 页面挑选商品,在 b页面确认商品,也就是付款页面,c 页面查询支付状态
  • 由于 code 的存在时间只有5分钟,所以注定 code 获得后不能长时间不使用,也就是说用户一旦在某个页面超过5分钟,这个 code 就失效了,因此最好的方法就是获取 code 后,立马获取 openid
  • 那么就应该设计成从a 页面先跳转到获取 code 页面再跳转到 b 页面,而在 b 页面的一开始就去请求接口,获取用户的 openid 即可
  • 跳转到 b 页面后,链接后自动带上 code参数,链接应该是 https://xxxx/b.html?code=xxxxxxxx
// a页面,仅做逻辑演示,更加具体的逻辑需要自己完善// 判断是否微信浏览器functionisWeChat() {
varua=navigator.userAgent.toLowerCase();
if (ua.match(/MicroMessenger/i) =='micromessenger') {
returntrue;
    } else {
returnfalse;
    }
}
if(!isWeChat()) {
// 非微信内打开的产品页面alert('微信外不支持JSAPI支付,请在微信中打开页面');
returnfalse;
}
// 用户挑选完商品后跳转,这里appid需要上面跟商户绑定的公众号appid// 微信授权分为静默授权和非静默授权,其中非静默授权,需要用户点击确认授权后,才可以获取code,// 因为这里主打一个用户无感知,而且我们只需要openid即可,所以我们只需要使用静默授权即可// 静默授权可以获取用户更多的信息,比如头像、昵称等,而静默授权只能获取openid,这点需要注意,具体情况选择不同// 非静默授权// $(location).attr('href', `https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxxxxxxxxxx&redirect_uri=${encodeURIComponent('https://xxxx/b.html')}&response_type=code&scope=snsapi_userinfo#wechat_redirect`)// 静默授权$(location).attr('href', `https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxxxxxxxxxx&redirect_uri=${encodeURIComponent('https://xxxx/b.html')}&response_type=code&scope=snsapi_base#wechat_redirect`)
// b页面,仅做逻辑演示,更加具体的逻辑需要自己完善letopenid='';
// 获取code, 请求接口获取openidfunctiongetParamUrl(name, url) {
if (!url) url=location.href;
if (url.indexOf('?') ==-1) return'';
try {
varre=newRegExp(""+name+"=([^&?]*)", "ig");
return ((url.match(re)) ? (decodeURIComponent(url.match(re)[0].substr(name.length+1))) : '');
  } catch (_e) {
return'';
  }
}
letcode=getParamUrl('code');
$.getJSON('后端接口地址/openid?callback=?', function(res) {
if(res.code==200) {
openid=res.data;
    } else {
console.error(res.msg);
    }
})
// 用户确定订单后,拉起支付letparams= {
total: 2, // 单位:元description: 'Image形象店-深圳腾大-QQ公仔', // 产品的介绍openid: openid//用户的openid// ....更多入库参数};
$.getJSON('后端接口地址/jssapi?'+$.param(params) +'&callback=?', function(res) {
WeixinJSBridge.invoke('getBrandWCPayRequest', {
'appId': res.data.sign.appId,
'timeStamp': res.data.sign.timeStamp,
'nonceStr': res.data.sign.nonceStr,
'package': res.data.sign.package,
'signType': res.data.sign.signType,
'paySign': res.data.sign.paySign    }, function (response) {
if (response.err_msg=="get_brand_wcpay_request:ok") {
$(location).attr("href", `https://xxxxxx/finish?out_trade_no=${res.data.out_trade_no}`)
      } else {
// 有些用户调起了支付,但是未付款取消的处理方式,你可以给他简单简单提示toast('支付异常取消')
// 当然有些用户是误操作,你可以提醒二次支付if(confirm('检测到你操作有误,是否重新支付?')) {
WeixinJSBridge.invoke('getBrandWCPayRequest', {
'appId': res.data.sign.appId,
'timeStamp': res.data.sign.timeStamp,
'nonceStr': res.data.sign.nonceStr,
'package': res.data.sign.package,
'signType': res.data.sign.signType,
'paySign': res.data.sign.paySign                }, function (response) {
if (response.err_msg=="get_brand_wcpay_request:ok") {
$(location).attr("href", `https://xxxxxx/finish?out_trade_no=${res.data.out_trade_no}`)
                }
            })
        }
      }
    });
});
<?php// 获取用户的openid$input=$request->only(['code']);
$response=getCurl("https://api.weixin.qq.com/sns/oauth2/access_token?appid={$this->appid}&secret={$this->secret}&code={$input['code']}&grant_type=authorization_code");
$openid=json_decode($response, true)['openid'];
// 返回openidreturnjsonp([
'code'=>200,
'msg'=>'获取成功',
'data'=>$openid]);
// 封装的GET请求functiongetCurl($url, $timeout=5)
{
$ch=curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
$result=curl_exec($ch);
curl_close($ch);
return$result;
}
<?php// 仅仅用作展示,不可直接复制使用require_once('../vendor/autoload.php');
useWeChatPay\Builder;
useWeChatPay\Formatter;
useWeChatPay\Crypto\Rsa;
useWeChatPay\Util\PemUtil;
// 接受参数,相当于原生的$_GET,这里会比h5支付多一个openid$input=$request->only(['openid', 'name', 'total', 'description', 'phone']);
// 生成商户订单号$out_trade_no=getOutTradeNo();
// 处理金额// 由于微信使用的是分作为单位,所以前端传的是元的话,需要转换一下$total=$input['total'] *100;
// 商户号$merchantId='1xxxxxx1';
// 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名$merchantPrivateKeyFilePath='file://../cert/merchant/apiclient_key.pem';
$merchantPrivateKeyInstance=Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
// 「商户API证书」的「证书序列号」$merchantCertificateSerial='1xxxxxxxxxxxxxxxxxxxxx91';
// 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名$platformCertificateFilePath='file://../cert/wechatpay/wechatpay_4xxxxxxxxxxxxxxxxxxx9.pem';
$platformPublicKeyInstance=Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
// 从「微信支付平台证书」中获取「证书序列号」$platformCertificateSerial=PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
// 构造一个 APIv3 客户端实例$instance=Builder::factory([
'mchid'=>$merchantId,
'serial'=>$merchantCertificateSerial,
'privateKey'=>$merchantPrivateKeyInstance,
'certs'=> [
$platformCertificateSerial=>$platformPublicKeyInstance,
    ],
]);
try {
// 调用 transactions/jsapi 接口后会生成prepay_id$resp=$this->instance()
->chain('v3/pay/transactions/jsapi')
->post(['json'=> [
'mchid'=>$merchantId, // 商户号'out_trade_no'=>$out_trade_no, // 商户订单号'appid'=>'********换成跟商户号绑定的公众号APPID**********',
'description'=>$input['description'], //商品描述'notify_url'=>'https://xxxxx/notify', // 用户支付后的回调地址,在这里修改订单的状态'amount'=> [
'total'=>$total,
'currency'=>'CNY'            ],
'payer'=> [
'openid'=>$input['openid']
            ]
        ]]);
// 需要根据prepay_id去生成加密的信息$prepay_id=json_decode($resp->getBody(), true)['prepay_id'];
$sign=getSign($prepay_id);
// 如果请求成功,需要将一些参数进行入库,这里仅作演示,非正式数据入库$response=Db::table('order')->insert([
'openid'=>$input['openid'],
'name'=>$input['name'],
'description'=>$input['description'],
'total'=>$input['total'],
'phone'=>$input['phone'],
'trade_state'=>'START',
   ]);
// 入库成功后,将跳转链接和订单号传给前端,前端拿到跳转地址跳转即可if($response) {
returnjsonp([
'code'=>200,
'msg'=>'操作成功',
'data'=> [
'out_trade_no'=>$out_trade_no,
'sign'=>$sign           ]
        ]);
   } else {
returnjsonp([
'code'=>100,
'msg'=>'操作失败'       ]);
   }
} catch (\Exception$e) {
// 进行错误处理if ($einstanceof\GuzzleHttp\Exception\RequestException&&$e->hasResponse()) {
$r=$e->getResponse();
echo$r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
    }
}
// 获取加密参数functiongetSign($prepay_id)
{
$merchantPrivateKeyInstance=Rsa::from($this->merchantPrivateKeyFilePath);
$params= [
'appId'=>$this->appid,
'timeStamp'=> (string)Formatter::timestamp(),
'nonceStr'=>Formatter::nonce(),
'package'=>'prepay_id=' . $prepay_id,
    ];
$params+= ['paySign'=>Rsa::sign(
Formatter::joinedByLineFeed(...array_values($params)),
$merchantPrivateKeyInstance    ), 'signType'=>'RSA'];
return$params;
}

🎈 通用微信支付库封装

  • 由于直接使用微信的支付库,代码非常的匀余,所以封装了一个微信支付库
  • 由于只针对一些业务的 api封装,所以肯定不全,需要的可以自己添加需要的api
  • 微信支付API接口列表:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/index.shtml
<?php/*** User: tinygeeker* Desc: 微信支付库封装* Date: 2023/08/10*/namespaceApp;
useApp\Helper;
useWeChatPay\Builder;
useWeChatPay\Formatter;
useWeChatPay\Crypto\Rsa;
useWeChatPay\Util\PemUtil;
classWxPay{
// appidprivate$appid;
// 商户号private$merchantId;
// 商户API私钥private$merchantPrivateKeyFilePath;
// 证书序列号private$merchantCertificateSerial;
// 微信支付平台证书private$platformCertificateFilePath;
/*** @param $appid* @param $merchantId* @param $merchantCertificateSerial*/publicfunction__construct($appid='', $merchantId='', $merchantCertificateSerial='')
    {
$this->appid=$appid?: '换成自己的APPID';
$this->merchantId=$merchantId?: '换成自己的商户号';
$this->merchantCertificateSerial=$merchantCertificateSerial?: '换成自己的证书序列号';
$this->merchantPrivateKeyFilePath='file:///common/cert/merchant/apiclient_key.pem'; // 换成自己的$this->platformCertificateFilePath='file:///common/cert/wechatpay/wechatpay_xxx.pem'; // 换成自己的    }
/*** @return \WeChatPay\BuilderChainable*/protectedfunctioninstance()
    {
$merchantPrivateKeyInstance=Rsa::from($this->merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
$platformPublicKeyInstance=Rsa::from($this->platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
$platformCertificateSerial=PemUtil::parseCertificateSerialNo($this->platformCertificateFilePath);
$instance=Builder::factory([
'mchid'=>$this->merchantId,
'serial'=>$this->merchantCertificateSerial,
'privateKey'=>$merchantPrivateKeyInstance,
'certs'=> [
$platformCertificateSerial=>$platformPublicKeyInstance,
            ],
        ]);
return$instance;
    }
publicfunctiongetSign($prepay_id)
    {
$merchantPrivateKeyInstance=Rsa::from($this->merchantPrivateKeyFilePath);
$params= [
'appId'=>$this->appid,
'timeStamp'=> (string)Formatter::timestamp(),
'nonceStr'=>Formatter::nonce(),
'package'=>'prepay_id=' . $prepay_id,
        ];
$params+= ['paySign'=>Rsa::sign(
Formatter::joinedByLineFeed(...array_values($params)),
$merchantPrivateKeyInstance        ), 'signType'=>'RSA'];
return$params;
    }
publicfunctioncheckOutTradeNo($out_trade_no)
    {
try {
$resp=$this->instance()
->v3->pay->transactions->outTradeNo->_out_trade_no_->get([
// Query 参数'query'=> ['mchid'=>$this->merchantId],
// 变量名 => 变量值'out_trade_no'=>$out_trade_no,
                ]);
return$resp->getBody();
        } catch (\Exception$e) {
// 进行错误处理echo$e->getMessage(), PHP_EOL;
if ($einstanceof\GuzzleHttp\Exception\RequestException&&$e->hasResponse()) {
$r=$e->getResponse();
echo$r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
echo$r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
            }
echo$e->getTraceAsString(), PHP_EOL;
        }
    }
// h5下单publicfunctionh5($total, $out_trade_no, $description, $notify_url)
    {
try {
$resp=$this->instance()
->chain('v3/pay/transactions/h5')
->post(['json'=> [
'mchid'=>$this->merchantId,
'out_trade_no'=>$out_trade_no,
'appid'=>$this->appid,
'description'=>$description,
'notify_url'=>$notify_url,
'amount'=> [
'total'=>$total,
'currency'=>'CNY'                    ],
'scene_info'=> [
'payer_client_ip'=>Helper::getClientIp(),
'h5_info'=> [
'type'=>'Wap'                        ]
                    ]
                ]]);
return$resp->getBody();
        } catch (\Exception$e) {
// 进行错误处理echo$e->getMessage(), PHP_EOL;
if ($einstanceof\GuzzleHttp\Exception\RequestException&&$e->hasResponse()) {
$r=$e->getResponse();
echo$r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
echo$r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
            }
echo$e->getTraceAsString(), PHP_EOL;
        }
    }
// jsapi下单publicfunctionjsapi($openid, $total, $out_trade_no, $description, $notify_url)
    {
try {
$resp=$this->instance()
->chain('v3/pay/transactions/jsapi')
->post(['json'=> [
'mchid'=>$this->merchantId,
'out_trade_no'=>$out_trade_no,
'appid'=>$this->appid,
'description'=>$description,
'notify_url'=>$notify_url,
'amount'=> [
'total'=>$total,
'currency'=>'CNY'                    ],
'payer'=> [
'openid'=>$openid                    ]
                ]]);
return$resp->getBody();
        } catch (\Exception$e) {
// 进行错误处理echo$e->getMessage(), PHP_EOL;
if ($einstanceof\GuzzleHttp\Exception\RequestException&&$e->hasResponse()) {
$r=$e->getResponse();
echo$r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
echo$r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
            }
echo$e->getTraceAsString(), PHP_EOL;
        }
    }
// todo... 更多接口可根据官方文档列表自行添加}
<?php/*** User: tinygeeker* Desc: 工具库* Date: 2023/08/10*/namespaceApp;
classHelper{
/*** @return array|mixed|string|string[]*/staticpublicfunctiongetClientIP()
    {
if (@$_SERVER["HTTP_ALI_CDN_REAL_IP"]) {
$ip=$_SERVER["HTTP_ALI_CDN_REAL_IP"];
        } elseif (@$_SERVER["HTTP_X_FORWARDED_FOR"] ?: false) {
$ips=explode(',', $_SERVER["HTTP_X_FORWARDED_FOR"]);
$ip=$ips[0];
        } elseif (@$_SERVER["HTTP_CDN_SRC_IP"] ?: false) {
$ip=$_SERVER["HTTP_CDN_SRC_IP"];
        } elseif (getenv('HTTP_CLIENT_IP')) {
$ip=getenv('HTTP_CLIENT_IP');
        } elseif (getenv('HTTP_X_FORWARDED')) {
$ip=getenv('HTTP_X_FORWARDED');
        } elseif (getenv('HTTP_FORWARDED_FOR')) {
$ip=getenv('HTTP_FORWARDED_FOR');
        } elseif (getenv('HTTP_FORWARDED')) {
$ip=getenv('HTTP_FORWARDED');
        } else {
$ip=$_SERVER['REMOTE_ADDR'];
        }
$ip=str_replace(['::ffff:', '[', ']'], ['', '', ''], $ip);
return$ip;
    }
/*** @param $length* @param $type* @return false|string*/staticpublicfunctioncreateRandomStr($length=32, $type=0)
    {
switch ($type) {
case1:
$chars='0123456789';
break;
case2:
$chars='abcdefghijklmnopqrstuvwxyz';
break;
case3:
$chars='ABCDEFGHIJKLMNOPQRSTUVWXYZ';
break;
case4:
$chars='abcdefghijklmnopqrstuvwxyz0123456789';
break;
case5:
$chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
break;
default:
$chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
break;
        }
returnsubstr(str_shuffle($chars), 0, $length);
    }
/*** @param $url* @param $timeout* @return bool|string*/staticpublicfunctiongetCurl($url, $timeout=5)
    {
$ch=curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
$result=curl_exec($ch);
curl_close($ch);
return$result;
    }
/*** @param $url* @param $data* @param $header* @param $timeout* @return bool|string*/staticpublicfunctionpostCurl($url, $data, $header= [], $timeout=5)
    {
$ch=curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
if ($header) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        }
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
$result=curl_exec($ch);
curl_close($ch);
return$result;
    }
}
相关实践学习
Serverless极速搭建Hexo博客
本场景介绍如何使用阿里云函数计算服务命令行工具快速搭建一个Hexo博客。
相关文章
|
23天前
|
移动开发 JavaScript
Vue——H5微信内支付(四)
10月更文挑战第11天
48 2
|
21天前
|
移动开发 前端开发
|
22天前
|
移动开发 JavaScript
Vue——H5微信内支付(五)
10月更文挑战第12天
30 2
|
24天前
|
移动开发 JavaScript
Vue——H5微信内支付(三)
10月更文挑战第10天
46 1
|
27天前
|
移动开发 JavaScript
Vue——H5微信内支付(二)
10月更文挑战第9天
30 1
|
17天前
|
移动开发
|
20天前
|
Web App开发 移动开发 Android开发
|
30天前
|
JSON 小程序 JavaScript
uni-app开发微信小程序的报错[渲染层错误]排查及解决
uni-app开发微信小程序的报错[渲染层错误]排查及解决
452 7
|
30天前
|
小程序 JavaScript 前端开发
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
504 1
|
1月前
|
小程序 前端开发 测试技术
微信小程序的开发完整流程是什么?
微信小程序的开发完整流程是什么?
110 7