申请商户号

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

申请商户证书

申请API证书

设置APIv3密钥

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

申请APIv3密钥

设置APIv3密钥
  1. // 生成32位的APIv3随机密钥
  2. $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  3. echo substr(str_shuffle($chars), 0, $length);

下载 SDK 开发包

  1. # 初始化文件夹
  2. composer init
  3. # 推荐使用 PHP 包管理工具 Composer 安装 SDK
  4. composer 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: 生成后的证书保存地址
  1. cd vendor/bin/
  2. 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支付流程

  • H5支付是在微信以外的浏览器使用的,如果是微信内的话,使用的是 jsapi 支付
  • 所以一般用户进入页面的第一件事,就是检测用户使用的环境是微信浏览器还是其他浏览器
  • 前端传一些用户挑选商品后的参数,并请求后端处理接口,后端应该将一些参数进行入库,顺便请求 H5 支付接口
  • 接口应该返回跳转链接 h5_url,如果你想用户付款之后到结果页面,需要添加 redirect_url 参数,这个参数一定要用 encodeURIComponent 进行处理
  • 由于官方在 jssapi 支付中说明,不要相信前端的 success 结果,所以需要在结果页中,让用户自动触发查询结果,因此需要返回后端生成的订单号,用作在结果页的用户手动点击查询
  1. // 判断是否微信浏览器
  2. function isWeChat() {
  3. var ua = navigator.userAgent.toLowerCase();
  4. if (ua.match(/MicroMessenger/i) == 'micromessenger') {
  5. return true;
  6. } else {
  7. return false;
  8. }
  9. }
  10. if(isWeChat()) {
  11. // 是微信中打开的产品页面
  12. alert('微信内不支持h5支付,请在外部浏览器打开页面');
  13. } else {
  14. // 非微信内打开的产品页面,请求接口,获取支付的跳转链接
  15. // 前端用户选的产品,以及产品的金额,传一些参数过去
  16. let params = {
  17. total: 2, // 单位:元
  18. description: 'Image形象店-深圳腾大-QQ公仔' // 产品的介绍
  19. // ....更多入库参数
  20. };
  21. $.getJSON('后端接口地址/h5?' + $.param(params) + '&callback=?', function(res) {
  22. // 拉起微信支付界面,成功后会跳转到redirect_url链接
  23. $(location).attr("href", res.data.h5_url + "&redirect_url=" + encodeURIComponent(`https://xxxxxx/finish?out_trade_no=${res.data.out_trade_no}`))
  24. });
  25. }
  1. <?php
  2. // 仅仅用作展示,不可直接复制使用
  3. require_once('../vendor/autoload.php');
  4. use WeChatPay\Builder;
  5. use WeChatPay\Crypto\Rsa;
  6. use WeChatPay\Util\PemUtil;
  7. // 接受参数,相当于原生的$_GET
  8. $input = $request->only(['name', 'total', 'description', 'phone']);
  9. // 生成商户订单号
  10. $out_trade_no = getOutTradeNo();
  11. // 处理金额
  12. // 由于微信使用的是分作为单位,所以前端传的是元的话,需要转换一下
  13. $total = $input['total'] * 100;
  14. // 商户号
  15. $merchantId = '1xxxxxx1';
  16. // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
  17. $merchantPrivateKeyFilePath = 'file://../cert/merchant/apiclient_key.pem';
  18. $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
  19. // 「商户API证书」的「证书序列号」
  20. $merchantCertificateSerial = '1xxxxxxxxxxxxxxxxxxxxx91';
  21. // 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名
  22. $platformCertificateFilePath = 'file://../cert/wechatpay/wechatpay_4xxxxxxxxxxxxxxxxxxx9.pem';
  23. $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
  24. // 从「微信支付平台证书」中获取「证书序列号」
  25. $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
  26. // 构造一个 APIv3 客户端实例
  27. $instance = Builder::factory([
  28. 'mchid' => $merchantId,
  29. 'serial' => $merchantCertificateSerial,
  30. 'privateKey' => $merchantPrivateKeyInstance,
  31. 'certs' => [
  32. $platformCertificateSerial => $platformPublicKeyInstance,
  33. ],
  34. ]);
  35. try {
  36. $resp = $instance
  37. ->chain('v3/pay/transactions/h5')
  38. ->post(['json' => [
  39. 'mchid' => $merchantId, // 商户号
  40. 'out_trade_no' => $out_trade_no, // 商户订单号
  41. 'appid' => '********换成跟商户号绑定的公众号APPID**********',
  42. 'description' => $input['description'], //商品描述
  43. 'notify_url' => 'https://xxxxx/notify', // 用户支付后的回调地址,在这里修改订单的状态
  44. 'amount' => [
  45. 'total' => $total, // 微信处理的单位是分
  46. 'currency' => 'CNY'
  47. ],
  48. 'scene_info' => [
  49. 'payer_client_ip' => getClientIP(), // 有些框架有自带获取获取客户端IP
  50. 'h5_info' => [
  51. 'type' => 'Wap'
  52. ]
  53. ]
  54. ]]);
  55. // 如果请求成功,需要将一些参数进行入库,这里仅作演示,非正式数据入库
  56. $response = Db::table('order')->insert([
  57. 'name' => $input['name'],
  58. 'description' => $input['description'],
  59. 'total' => $input['total'],
  60. 'phone' => $input['phone'],
  61. 'trade_state' => 'START',
  62. ]);
  63. // 入库成功后,将跳转链接和订单号传给前端,前端拿到跳转地址跳转即可
  64. if($response) {
  65. return jsonp([
  66. 'code' => 200,
  67. 'msg' => '操作成功',
  68. 'data' => [
  69. 'out_trade_no' => $out_trade_no,
  70. 'h5_url' => json_decode($resp->getBody(), true)['h5_url']
  71. ]
  72. ]);
  73. } else {
  74. return jsonp([
  75. 'code' => 100,
  76. 'msg' => '操作失败'
  77. ]);
  78. }
  79. } catch (\Exception $e) {
  80. // 进行错误处理
  81. if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
  82. $r = $e->getResponse();
  83. echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
  84. }
  85. }
  86. // 生成唯一商户订单号,订单号不能超过32位,并且在同一个商户下订单号不能重复
  87. // 如果并发不高,基本这样生成就可以,不会有重复的情况出现的
  88. function getOutTradeNo()
  89. {
  90. $out_trade_no = date('ymdHis') . mt_rand(1000, 9999) . uniqid();
  91. return mb_substr($out_trade_no, 0, 32);
  92. }
  93. // 获取客户端的IP
  94. function getClientIP()
  95. {
  96. if (@$_SERVER["HTTP_ALI_CDN_REAL_IP"]) {
  97. $ip = $_SERVER["HTTP_ALI_CDN_REAL_IP"];
  98. } elseif (@$_SERVER["HTTP_X_FORWARDED_FOR"] ?: false) {
  99. $ips = explode(',', $_SERVER["HTTP_X_FORWARDED_FOR"]);
  100. $ip = $ips[0];
  101. } elseif (@$_SERVER["HTTP_CDN_SRC_IP"] ?: false) {
  102. $ip = $_SERVER["HTTP_CDN_SRC_IP"];
  103. } elseif (getenv('HTTP_CLIENT_IP')) {
  104. $ip = getenv('HTTP_CLIENT_IP');
  105. } elseif (getenv('HTTP_X_FORWARDED')) {
  106. $ip = getenv('HTTP_X_FORWARDED');
  107. } elseif (getenv('HTTP_FORWARDED_FOR')) {
  108. $ip = getenv('HTTP_FORWARDED_FOR');
  109. } elseif (getenv('HTTP_FORWARDED')) {
  110. $ip = getenv('HTTP_FORWARDED');
  111. } else {
  112. $ip = $_SERVER['REMOTE_ADDR'];
  113. }
  114. $ip = str_replace(['::ffff:', '[', ']'], ['', '', ''], $ip);
  115. return $ip;
  116. }
  1. <?php
  2. // 回调处理,当用户支付订单后,微信会请求该接口,也就是上面在notify_url中填写的接口
  3. // 在这里我们可以修改订单的状态啥的
  4. public function notify()
  5. {
  6. // 获取参数
  7. $inBody = file_get_contents('php://input');
  8. // APIv3密钥
  9. $apiv3Key = 'xxxxxxxxxxxx';
  10. // 转换通知的JSON文本消息为PHP Array数组
  11. $inBodyArray = (array)json_decode($inBody, true);
  12. // 加密文本消息解密
  13. $inBodyResource = AesGcm::decrypt(
  14. $inBodyArray['resource']['ciphertext'],
  15. $apiv3Key,
  16. $inBodyArray['resource']['nonce'],
  17. $inBodyArray['resource']['associated_data']
  18. );
  19. // 把解密后的文本转换为PHP Array数组
  20. $inBodyResourceArray = (array)json_decode($inBodyResource, true);
  21. try {
  22. // 获取订单信息
  23. $order = Db::table('order')->where('out_trade_no', $inBodyResourceArray['out_trade_no'])->first();
  24. Db::startTrans();
  25. if ($order) {
  26. // 修改order订单的状态
  27. Db::table('order')->where('id', $order['id'])->update([
  28. 'openid' => $inBodyResourceArray['payer']['openid'],
  29. 'trade_state' => $inBodyResourceArray['trade_state']
  30. ]);
  31. Db::table('payment')->insert([
  32. 'out_trade_no' => $inBodyResourceArray['out_trade_no'],
  33. 'transaction_id' => $inBodyResourceArray['transaction_id'],
  34. 'trade_type' => $inBodyResourceArray['trade_type'],
  35. 'trade_state' => $inBodyResourceArray['trade_state'],
  36. 'trade_state_desc' => $inBodyResourceArray['trade_state_desc'],
  37. 'total_amount' => $inBodyResourceArray['amount']['total'],
  38. 'bank_type' => $inBodyResourceArray['bank_type'],
  39. 'success_time' => strtotime($inBodyResourceArray['success_time'])
  40. ]);
  41. Db::commit();
  42. } else {
  43. Db::rollback();
  44. }
  45. } catch (\Exception $e) {
  46. Db::rollback();
  47. }
  48. }

开通 JSAPI 支付

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

开通 JSAPI 支付

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
  1. // a页面,仅做逻辑演示,更加具体的逻辑需要自己完善
  2. // 判断是否微信浏览器
  3. function isWeChat() {
  4. var ua = navigator.userAgent.toLowerCase();
  5. if (ua.match(/MicroMessenger/i) == 'micromessenger') {
  6. return true;
  7. } else {
  8. return false;
  9. }
  10. }
  11. if(!isWeChat()) {
  12. // 非微信内打开的产品页面
  13. alert('微信外不支持JSAPI支付,请在微信中打开页面');
  14. return false;
  15. }
  16. // 用户挑选完商品后跳转,这里appid需要上面跟商户绑定的公众号appid
  17. // 微信授权分为静默授权和非静默授权,其中非静默授权,需要用户点击确认授权后,才可以获取code,
  18. // 因为这里主打一个用户无感知,而且我们只需要openid即可,所以我们只需要使用静默授权即可
  19. // 静默授权可以获取用户更多的信息,比如头像、昵称等,而静默授权只能获取openid,这点需要注意,具体情况选择不同
  20. // 非静默授权
  21. // $(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`)
  22. // 静默授权
  23. $(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`)
  1. // b页面,仅做逻辑演示,更加具体的逻辑需要自己完善
  2. let openid = '';
  3. // 获取code, 请求接口获取openid
  4. function getParamUrl(name, url) {
  5. if (!url) url = location.href;
  6. if (url.indexOf('?') == -1) return '';
  7. try {
  8. var re = new RegExp("" + name + "=([^&?]*)", "ig");
  9. return ((url.match(re)) ? (decodeURIComponent(url.match(re)[0].substr(name.length + 1))) : '');
  10. } catch (_e) {
  11. return '';
  12. }
  13. }
  14. let code = getParamUrl('code');
  15. $.getJSON('后端接口地址/openid?callback=?', function(res) {
  16. if(res.code == 200) {
  17. openid = res.data;
  18. } else {
  19. console.error(res.msg);
  20. }
  21. })
  22. // 用户确定订单后,拉起支付
  23. let params = {
  24. total: 2, // 单位:元
  25. description: 'Image形象店-深圳腾大-QQ公仔', // 产品的介绍
  26. openid: openid //用户的openid
  27. // ....更多入库参数
  28. };
  29. $.getJSON('后端接口地址/jssapi?' + $.param(params) + '&callback=?', function(res) {
  30. WeixinJSBridge.invoke('getBrandWCPayRequest', {
  31. 'appId': res.data.sign.appId,
  32. 'timeStamp': res.data.sign.timeStamp,
  33. 'nonceStr': res.data.sign.nonceStr,
  34. 'package': res.data.sign.package,
  35. 'signType': res.data.sign.signType,
  36. 'paySign': res.data.sign.paySign
  37. }, function (response) {
  38. if (response.err_msg == "get_brand_wcpay_request:ok") {
  39. $(location).attr("href", `https://xxxxxx/finish?out_trade_no=${res.data.out_trade_no}`)
  40. } else {
  41. // 有些用户调起了支付,但是未付款取消的处理方式,你可以给他简单简单提示
  42. toast('支付异常取消')
  43. // 当然有些用户是误操作,你可以提醒二次支付
  44. if(confirm('检测到你操作有误,是否重新支付?')) {
  45. WeixinJSBridge.invoke('getBrandWCPayRequest', {
  46. 'appId': res.data.sign.appId,
  47. 'timeStamp': res.data.sign.timeStamp,
  48. 'nonceStr': res.data.sign.nonceStr,
  49. 'package': res.data.sign.package,
  50. 'signType': res.data.sign.signType,
  51. 'paySign': res.data.sign.paySign
  52. }, function (response) {
  53. if (response.err_msg == "get_brand_wcpay_request:ok") {
  54. $(location).attr("href", `https://xxxxxx/finish?out_trade_no=${res.data.out_trade_no}`)
  55. }
  56. })
  57. }
  58. }
  59. });
  60. });
  1. <?php
  2. // 获取用户的openid
  3. $input = $request->only(['code']);
  4. $response = getCurl("https://api.weixin.qq.com/sns/oauth2/access_token?appid={$this->appid}&secret={$this->secret}&code={$input['code']}&grant_type=authorization_code");
  5. $openid = json_decode($response, true)['openid'];
  6. // 返回openid
  7. return jsonp([
  8. 'code' => 200,
  9. 'msg' => '获取成功',
  10. 'data' => $openid
  11. ]);
  12. // 封装的GET请求
  13. function getCurl($url, $timeout = 5)
  14. {
  15. $ch = curl_init();
  16. curl_setopt($ch, CURLOPT_URL, $url);
  17. curl_setopt($ch, CURLOPT_HEADER, 0);
  18. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  19. curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
  20. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
  21. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
  22. $result = curl_exec($ch);
  23. curl_close($ch);
  24. return $result;
  25. }
  1. <?php
  2. // 仅仅用作展示,不可直接复制使用
  3. require_once('../vendor/autoload.php');
  4. use WeChatPay\Builder;
  5. use WeChatPay\Formatter;
  6. use WeChatPay\Crypto\Rsa;
  7. use WeChatPay\Util\PemUtil;
  8. // 接受参数,相当于原生的$_GET,这里会比h5支付多一个openid
  9. $input = $request->only(['openid', 'name', 'total', 'description', 'phone']);
  10. // 生成商户订单号
  11. $out_trade_no = getOutTradeNo();
  12. // 处理金额
  13. // 由于微信使用的是分作为单位,所以前端传的是元的话,需要转换一下
  14. $total = $input['total'] * 100;
  15. // 商户号
  16. $merchantId = '1xxxxxx1';
  17. // 从本地文件中加载「商户API私钥」,「商户API私钥」会用来生成请求的签名
  18. $merchantPrivateKeyFilePath = 'file://../cert/merchant/apiclient_key.pem';
  19. $merchantPrivateKeyInstance = Rsa::from($merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
  20. // 「商户API证书」的「证书序列号」
  21. $merchantCertificateSerial = '1xxxxxxxxxxxxxxxxxxxxx91';
  22. // 从本地文件中加载「微信支付平台证书」,用来验证微信支付应答的签名
  23. $platformCertificateFilePath = 'file://../cert/wechatpay/wechatpay_4xxxxxxxxxxxxxxxxxxx9.pem';
  24. $platformPublicKeyInstance = Rsa::from($platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
  25. // 从「微信支付平台证书」中获取「证书序列号」
  26. $platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateFilePath);
  27. // 构造一个 APIv3 客户端实例
  28. $instance = Builder::factory([
  29. 'mchid' => $merchantId,
  30. 'serial' => $merchantCertificateSerial,
  31. 'privateKey' => $merchantPrivateKeyInstance,
  32. 'certs' => [
  33. $platformCertificateSerial => $platformPublicKeyInstance,
  34. ],
  35. ]);
  36. try {
  37. // 调用 transactions/jsapi 接口后会生成prepay_id
  38. $resp = $this->instance()
  39. ->chain('v3/pay/transactions/jsapi')
  40. ->post(['json' => [
  41. 'mchid' => $merchantId, // 商户号
  42. 'out_trade_no' => $out_trade_no, // 商户订单号
  43. 'appid' => '********换成跟商户号绑定的公众号APPID**********',
  44. 'description' => $input['description'], //商品描述
  45. 'notify_url' => 'https://xxxxx/notify', // 用户支付后的回调地址,在这里修改订单的状态
  46. 'amount' => [
  47. 'total' => $total,
  48. 'currency' => 'CNY'
  49. ],
  50. 'payer' => [
  51. 'openid' => $input['openid']
  52. ]
  53. ]]);
  54. // 需要根据prepay_id去生成加密的信息
  55. $prepay_id = json_decode($resp->getBody(), true)['prepay_id'];
  56. $sign = getSign($prepay_id);
  57. // 如果请求成功,需要将一些参数进行入库,这里仅作演示,非正式数据入库
  58. $response = Db::table('order')->insert([
  59. 'openid' => $input['openid'],
  60. 'name' => $input['name'],
  61. 'description' => $input['description'],
  62. 'total' => $input['total'],
  63. 'phone' => $input['phone'],
  64. 'trade_state' => 'START',
  65. ]);
  66. // 入库成功后,将跳转链接和订单号传给前端,前端拿到跳转地址跳转即可
  67. if($response) {
  68. return jsonp([
  69. 'code' => 200,
  70. 'msg' => '操作成功',
  71. 'data' => [
  72. 'out_trade_no' => $out_trade_no,
  73. 'sign' => $sign
  74. ]
  75. ]);
  76. } else {
  77. return jsonp([
  78. 'code' => 100,
  79. 'msg' => '操作失败'
  80. ]);
  81. }
  82. } catch (\Exception $e) {
  83. // 进行错误处理
  84. if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
  85. $r = $e->getResponse();
  86. echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
  87. }
  88. }
  89. // 获取加密参数
  90. function getSign($prepay_id)
  91. {
  92. $merchantPrivateKeyInstance = Rsa::from($this->merchantPrivateKeyFilePath);
  93. $params = [
  94. 'appId' => $this->appid,
  95. 'timeStamp' => (string)Formatter::timestamp(),
  96. 'nonceStr' => Formatter::nonce(),
  97. 'package' => 'prepay_id=' . $prepay_id,
  98. ];
  99. $params += ['paySign' => Rsa::sign(
  100. Formatter::joinedByLineFeed(...array_values($params)),
  101. $merchantPrivateKeyInstance
  102. ), 'signType' => 'RSA'];
  103. return $params;
  104. }

通用微信支付库封装

  • 由于直接使用微信的支付库,代码非常的匀余,所以封装了一个微信支付库
  • 由于只针对一些业务的 api封装,所以肯定不全,需要的可以自己添加需要的api
  • 微信支付API接口列表: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/index.shtml
  1. <?php
  2. /**
  3. * User: tinygeeker
  4. * Desc: 微信支付库封装
  5. * Date: 2023/08/10
  6. */
  7. namespace App;
  8. use App\Helper;
  9. use WeChatPay\Builder;
  10. use WeChatPay\Formatter;
  11. use WeChatPay\Crypto\Rsa;
  12. use WeChatPay\Util\PemUtil;
  13. class WxPay
  14. {
  15. // appid
  16. private $appid;
  17. // 商户号
  18. private $merchantId;
  19. // 商户API私钥
  20. private $merchantPrivateKeyFilePath;
  21. // 证书序列号
  22. private $merchantCertificateSerial;
  23. // 微信支付平台证书
  24. private $platformCertificateFilePath;
  25. /**
  26. * @param $appid
  27. * @param $merchantId
  28. * @param $merchantCertificateSerial
  29. */
  30. public function __construct($appid = '', $merchantId = '', $merchantCertificateSerial = '')
  31. {
  32. $this->appid = $appid ?: '换成自己的APPID';
  33. $this->merchantId = $merchantId ?: '换成自己的商户号';
  34. $this->merchantCertificateSerial = $merchantCertificateSerial ?: '换成自己的证书序列号';
  35. $this->merchantPrivateKeyFilePath = 'file:///common/cert/merchant/apiclient_key.pem'; // 换成自己的
  36. $this->platformCertificateFilePath = 'file:///common/cert/wechatpay/wechatpay_xxx.pem'; // 换成自己的
  37. }
  38. /**
  39. * @return \WeChatPay\BuilderChainable
  40. */
  41. protected function instance()
  42. {
  43. $merchantPrivateKeyInstance = Rsa::from($this->merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
  44. $platformPublicKeyInstance = Rsa::from($this->platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
  45. $platformCertificateSerial = PemUtil::parseCertificateSerialNo($this->platformCertificateFilePath);
  46. $instance = Builder::factory([
  47. 'mchid' => $this->merchantId,
  48. 'serial' => $this->merchantCertificateSerial,
  49. 'privateKey' => $merchantPrivateKeyInstance,
  50. 'certs' => [
  51. $platformCertificateSerial => $platformPublicKeyInstance,
  52. ],
  53. ]);
  54. return $instance;
  55. }
  56. public function getSign($prepay_id)
  57. {
  58. $merchantPrivateKeyInstance = Rsa::from($this->merchantPrivateKeyFilePath);
  59. $params = [
  60. 'appId' => $this->appid,
  61. 'timeStamp' => (string)Formatter::timestamp(),
  62. 'nonceStr' => Formatter::nonce(),
  63. 'package' => 'prepay_id=' . $prepay_id,
  64. ];
  65. $params += ['paySign' => Rsa::sign(
  66. Formatter::joinedByLineFeed(...array_values($params)),
  67. $merchantPrivateKeyInstance
  68. ), 'signType' => 'RSA'];
  69. return $params;
  70. }
  71. public function checkOutTradeNo($out_trade_no)
  72. {
  73. try {
  74. $resp = $this->instance()
  75. ->v3->pay->transactions->outTradeNo->_out_trade_no_
  76. ->get([
  77. // Query 参数
  78. 'query' => ['mchid' => $this->merchantId],
  79. // 变量名 => 变量值
  80. 'out_trade_no' => $out_trade_no,
  81. ]);
  82. return $resp->getBody();
  83. } catch (\Exception $e) {
  84. // 进行错误处理
  85. echo $e->getMessage(), PHP_EOL;
  86. if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
  87. $r = $e->getResponse();
  88. echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
  89. echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
  90. }
  91. echo $e->getTraceAsString(), PHP_EOL;
  92. }
  93. }
  94. // h5下单
  95. public function h5($total, $out_trade_no, $description, $notify_url)
  96. {
  97. try {
  98. $resp = $this->instance()
  99. ->chain('v3/pay/transactions/h5')
  100. ->post(['json' => [
  101. 'mchid' => $this->merchantId,
  102. 'out_trade_no' => $out_trade_no,
  103. 'appid' => $this->appid,
  104. 'description' => $description,
  105. 'notify_url' => $notify_url,
  106. 'amount' => [
  107. 'total' => $total,
  108. 'currency' => 'CNY'
  109. ],
  110. 'scene_info' => [
  111. 'payer_client_ip' => Helper::getClientIp(),
  112. 'h5_info' => [
  113. 'type' => 'Wap'
  114. ]
  115. ]
  116. ]]);
  117. return $resp->getBody();
  118. } catch (\Exception $e) {
  119. // 进行错误处理
  120. echo $e->getMessage(), PHP_EOL;
  121. if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
  122. $r = $e->getResponse();
  123. echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
  124. echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
  125. }
  126. echo $e->getTraceAsString(), PHP_EOL;
  127. }
  128. }
  129. // jsapi下单
  130. public function jsapi($openid, $total, $out_trade_no, $description, $notify_url)
  131. {
  132. try {
  133. $resp = $this->instance()
  134. ->chain('v3/pay/transactions/jsapi')
  135. ->post(['json' => [
  136. 'mchid' => $this->merchantId,
  137. 'out_trade_no' => $out_trade_no,
  138. 'appid' => $this->appid,
  139. 'description' => $description,
  140. 'notify_url' => $notify_url,
  141. 'amount' => [
  142. 'total' => $total,
  143. 'currency' => 'CNY'
  144. ],
  145. 'payer' => [
  146. 'openid' => $openid
  147. ]
  148. ]]);
  149. return $resp->getBody();
  150. } catch (\Exception $e) {
  151. // 进行错误处理
  152. echo $e->getMessage(), PHP_EOL;
  153. if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
  154. $r = $e->getResponse();
  155. echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
  156. echo $r->getBody(), PHP_EOL, PHP_EOL, PHP_EOL;
  157. }
  158. echo $e->getTraceAsString(), PHP_EOL;
  159. }
  160. }
  161. // todo... 更多接口可根据官方文档列表自行添加
  162. }
  1. <?php
  2. /**
  3. * User: tinygeeker
  4. * Desc: 工具库
  5. * Date: 2023/08/10
  6. */
  7. namespace App;
  8. class Helper
  9. {
  10. /**
  11. * @return array|mixed|string|string[]
  12. */
  13. static public function getClientIP()
  14. {
  15. if (@$_SERVER["HTTP_ALI_CDN_REAL_IP"]) {
  16. $ip = $_SERVER["HTTP_ALI_CDN_REAL_IP"];
  17. } elseif (@$_SERVER["HTTP_X_FORWARDED_FOR"] ?: false) {
  18. $ips = explode(',', $_SERVER["HTTP_X_FORWARDED_FOR"]);
  19. $ip = $ips[0];
  20. } elseif (@$_SERVER["HTTP_CDN_SRC_IP"] ?: false) {
  21. $ip = $_SERVER["HTTP_CDN_SRC_IP"];
  22. } elseif (getenv('HTTP_CLIENT_IP')) {
  23. $ip = getenv('HTTP_CLIENT_IP');
  24. } elseif (getenv('HTTP_X_FORWARDED')) {
  25. $ip = getenv('HTTP_X_FORWARDED');
  26. } elseif (getenv('HTTP_FORWARDED_FOR')) {
  27. $ip = getenv('HTTP_FORWARDED_FOR');
  28. } elseif (getenv('HTTP_FORWARDED')) {
  29. $ip = getenv('HTTP_FORWARDED');
  30. } else {
  31. $ip = $_SERVER['REMOTE_ADDR'];
  32. }
  33. $ip = str_replace(['::ffff:', '[', ']'], ['', '', ''], $ip);
  34. return $ip;
  35. }
  36. /**
  37. * @param $length
  38. * @param $type
  39. * @return false|string
  40. */
  41. static public function createRandomStr($length = 32, $type = 0)
  42. {
  43. switch ($type) {
  44. case 1:
  45. $chars = '0123456789';
  46. break;
  47. case 2:
  48. $chars = 'abcdefghijklmnopqrstuvwxyz';
  49. break;
  50. case 3:
  51. $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  52. break;
  53. case 4:
  54. $chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
  55. break;
  56. case 5:
  57. $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  58. break;
  59. default:
  60. $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  61. break;
  62. }
  63. return substr(str_shuffle($chars), 0, $length);
  64. }
  65. /**
  66. * @param $url
  67. * @param $timeout
  68. * @return bool|string
  69. */
  70. static public function getCurl($url, $timeout = 5)
  71. {
  72. $ch = curl_init();
  73. curl_setopt($ch, CURLOPT_URL, $url);
  74. curl_setopt($ch, CURLOPT_HEADER, 0);
  75. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  76. curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
  77. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
  78. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
  79. $result = curl_exec($ch);
  80. curl_close($ch);
  81. return $result;
  82. }
  83. /**
  84. * @param $url
  85. * @param $data
  86. * @param $header
  87. * @param $timeout
  88. * @return bool|string
  89. */
  90. static public function postCurl($url, $data, $header = [], $timeout = 5)
  91. {
  92. $ch = curl_init();
  93. curl_setopt($ch, CURLOPT_URL, $url);
  94. if ($header) {
  95. curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
  96. }
  97. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  98. curl_setopt($ch, CURLOPT_POST, 1);
  99. curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
  100. curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
  101. curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
  102. curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
  103. $result = curl_exec($ch);
  104. curl_close($ch);
  105. return $result;
  106. }
  107. }

微信的 h5 支付和 jsapi 支付的更多相关文章

  1. 转载【微信支付】jsapi支付之传参问题(使用微信官方SDK之PHP版本) V3之WxpayPubHelper 亲测有效,V3WxpayAPI_php_v3.zip版未测试,理论上也是一样的。

    本文转载至:http://blog.csdn.net/geeklx/article/details/51146151 (微信支付现在分为v2版和v3版,2014年9月10号之前申请的为v2版,之后申请 ...

  2. 微信小程序支付(JSAPI支付)

    开发环境:.NET MVC+ ORM框架(EF) 一.参考文档: 1.微信JSAPI支付官方文档:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api. ...

  3. 微信支付:JSAPI支付一直提示URL未注册

    今天意外碰上了这个问题,想想微信的坑真多…… 解决办法: 首先要看微信公众号里的 支付授权目录 是否已正确填写,还要验证 url大小写 必须相同 其次查看一下自己请求的地址是否与上面填写的是否一样!u ...

  4. 微信支付之JsApi支付

    常见问题:金额错误,微信金额是int类型,最小单位为分,即是1 客户端调用微信支付的时候一闪而过:这个原因是因为微信商户后台支付目录地址没设置对,导致js调用的时候验证没通过 .aspx页面设置: x ...

  5. 微信支付JSAPI支付

    1.介绍 JSAPI支付是用户在微信中打开商户的H5页面,商户在H5页面通过调用微信支付提供的JSAPI接口调起微信支付模块完成支付.应用场景有:    ◆ 用户在微信公众账号内进入商家公众号,打开某 ...

  6. 微信支付系列(2)——jsapi支付源码解析

    版权声明:转载请注明出处:http://blog.csdn.net/m0sh1 http://www.share345.com 在微信支付 开发者文档页面 下载最新的 PHP SDK http://m ...

  7. 网站如何接入微信公众号JSAPI支付PHP版

    1.首先,我们要有一个微信公众号(分类类型有订阅号,服务号,企业号)我们的微信公众号一定是个服务号只有它才有微信支付接口.. 并且这个微信公众号一定要进行微信认证才能申请微信支付接口. 2.申请JSA ...

  8. JAVA微信支付接口开发——支付

    微信支付接口开发--支付 这几天在做支付服务,系统接入了支付宝.微信.银联三方支付接口.个人感觉支付宝的接口开发较为简单,并且易于测试. 关于数据传输,微信是用xml,所以需要对xml进行解析. 1. ...

  9. 微信App支付接入步骤&支付中前后端交互流程

    最近对微信App支付(App端集成微信支付SDK)申请步骤,以及终端在进行微信支付时商户App.商户Server.微信App.微信支付Server的交互流程进行了简单了解.这篇文章应该算是学习笔记,分 ...

  10. 微信JSAPI 公众号支付 H5支付以及APP支付 WEBAPI接口开发测试

    统一下单入口 调用该方法入口: public void WxPayAPI() { //string PayPrice ="99.9"; ////订单号 //string Payor ...

随机推荐

  1. 2022-04-11:给定一个正数数组arr,其中每个值代表砖块长度, 所有砖块等高等宽,只有长度有区别, 每一层可以用1块或者2块砖来摆, 要求每一层的长度一样, 要求必须使用所有的砖块, 请问最多

    2022-04-11:给定一个正数数组arr,其中每个值代表砖块长度, 所有砖块等高等宽,只有长度有区别, 每一层可以用1块或者2块砖来摆, 要求每一层的长度一样, 要求必须使用所有的砖块, 请问最多 ...

  2. 2021-04-10:给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的 第一个节点。如果不相交,返回null。【要求】如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度 请达到O(1)。

    2021-04-10:给定两个可能有环也可能无环的单链表,头节点head1和head2.请实现一个函数,如果两个链表相交,请返回相交的 第一个节点.如果不相交,返回null.[要求]如果两个链表长度之 ...

  3. vue全家桶进阶之路26:Vue.js 3.0与Vue.js 2.x 的比较和注意事项

    Vue.js 3.0 是 Vue.js 框架的最新版本,于 2020 年 9 月正式发布.Vue.js 3.0 主要的改进和新特性包括: 更好的性能:Vue.js 3.0 使用了更快的虚拟 DOM 实 ...

  4. 中文环境下使用 huggingface 模型替换 OpenAI的Embedding 接口

    OpenAI的文本嵌入衡量文本字符串的相关性.嵌入通常用于: 搜索(其中结果按与查询字符串的相关性排名) 聚类(其中文本字符串按相似性分组) 推荐(推荐具有相关文本字符串的项目) 异常检测(识别出相关 ...

  5. 痞子衡嵌入式:MCUBootUtility v5.0发布,初步支持i.MXRT1180

    -- 痞子衡维护的NXP-MCUBootUtility工具距离上一个大版本(v4.0.0)发布过去4个多月了,期间痞子衡也做过两个小版本更新,但不足以单独介绍.这一次痞子衡为大家带来了全新大版本v5. ...

  6. 整合vxgPlayer使chrome支持vxg_media_player播放rtsp视频

    目前有一个关于接入海康监控进行视频融合的项目需求,按理说在前端技术发展如此迅速的今天,使用web播放一个视频应该是不算什么难事,只是万事都有意外,因很多视频厂家的监控数据都不是普通的mp4啥的,所以使 ...

  7. web自动化04-css定位

    css元素定位 1. 是什么? 用来描述html元素的显示样式 选择器是一种模式,用于选择需要添加样式的元素   selenium中推荐使用css定位,比XPath定位要快    2.如何定位?   ...

  8. dockder 学习第一篇

    1 docker安装 1 yum包的更新到最新 yum update 2 安装需要软件包,yum-util [root@localhost ~]# yum install -y yum-utils d ...

  9. Multiserver游戏服务器Demo[C++&Lua]

    代码参考 代码文件参考下述详解的类图,工程参考第零章工程说明 关键特性 对Socket库进行封装,抹平Socket的Window&Linux的平台差异. C++嵌入lua脚本,增加开发者编码效 ...

  10. 论c++实现sql连接

    寻找关于c++ 对 sql连接的过程非常艰辛. 今天要做一个简单项目,要求在远程sql上实现对数据的实时模拟,每五分钟进行一次随机产生数据并写入. 在此之前我并没有用过代码实现sql连接的经历,在翻阅 ...