网站怎么接入微信扫码支付?

祗疼妳一个  金牌会员 | 2023-6-21 01:10:11 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 858|帖子 858|积分 2576

第01章-准备工作

1、微信支付产品介绍

参考资料:产品中心 - 微信支付商户平台 (qq.com)
付款码支付、JSAPI支付、小程序支付、Native支付、APP支付、刷脸支付
1.1、付款码支付

用户展示微信钱包内的“付款码”给商家,商家扫描后直接完成支付,适用于线下面对面收银的场景。
1.2、JSAPI支付


  • 线下场所:商户展示一个支付二维码,用户使用微信扫描二维码后,输入需要支付的金额,完成支付。
  • 公众号场景:用户在微信内进入商家公众号,打开某个页面,选择某个产品,完成支付。
  • PC网站场景:在网站中展示二维码,用户使用微信扫描二维码,输入需要支付的金额,完成支付。
特点:用户在客户端输入支付金额
1.3、小程序支付

在微信小程序平台内实现支付的功能。
1.4、Native支付

Native支付是指商户展示支付二维码,用户再用微信“扫一扫”完成支付的模式。这种方式适用于PC网站。
特点:商家预先指定支付金额
1.5、APP支付

商户通过在移动端独立的APP应用程序中集成微信支付模块,完成支付。
1.6、刷脸支付

用户在刷脸设备前通过摄像头刷脸、识别身份后进行的一种支付方式。
2、接口版本

微信支付企业主流的API版本有v2和v3,课程中我们使用微信支付APIv3。
V2和V3的比较

相比较而言,APIv2比APIv3安全性更高,但是APIv2中有一些功能在APIv3中尚未完整实现,具体参考如下API字典页面:API字典概览 | 微信支付商户平台文档中心 (qq.com)

3、接入指引

3.1、获取开发参数

如果需要独立申请和开通微信支付功能,可以参考如下官方文档。开通微信支付后,才能获取相关的开发参数以及商户公钥和商户私钥文件。
参考资料:微信支付接入指引 - 微信支付商户平台 (qq.com)
3.2、配置开发参数

在service-order服务的resources目录中创建wxpay.properties
这个文件定义了在“接入指引”的步骤中我们提前准备的微信支付相关的参数,例如商户号、APPID、API秘钥等等
  1. # 微信支付相关参数
  2. wxpay:
  3.   mch-id: 1558950191 #商户号
  4.   mch-serial-no: 34345964330B66427E0D3D28826C4993C77E631F # 商户API证书序列号
  5.   private-key-path: D:/project/yygh/cert/apiclient_key.pem # 商户私钥文件
  6.   api-v3-key: UDuLFDcmy5Eb6o0nTNZdu6ek4DDh4K8B # APIv3密钥
  7.   appid: wx74862e0dfcf69954 # APPID
  8.   notify-url: https://7d92-115-171-63-135.ngrok.io/api/order/wxpay/payment/notify # 接收支付结果通知地址
  9.   notify-refund-url: http://agxnyzl04y90.ngrok.xiaomiqiu123.top/api/order/wxpay/refunds/notify # 接收退款结果通知地址
复制代码
3.3、复制商户私钥

将商户私钥文件复制到配置文件指定的目录下:
资料:资料>微信支付>商户证书>apiclient_key.pem
  1. private-key-path: D:/project/yygh/cert/apiclient_key.pem # 商户私钥文件
复制代码
3.4、证书密钥使用说明(了解)

参考文档:APIv3证书与密钥使用说明
一个完整的请求和响应的流程:

  • 商户使用商户私钥对请求进行签名,发送给微信支付平台,平台使用商户公钥进行签名验证。
  • 微信支付平台使用平台私钥对响应进行签名,商户使用微信支付平台公钥对响应进行验签。

第02章-订单支付

1、微信支付平台证书的获取

1.1、引入SDK

参考文档:SDK&工具
我们可以使用官方提供的 SDK wechatpay-java
在service-order微服务中添加依赖:
  1. <dependency>
  2.   <groupId>com.github.wechatpay-apiv3</groupId>
  3.   <artifactId>wechatpay-java</artifactId>
  4.   <version>0.2.6</version>
  5. </dependency>
复制代码
1.2、读取支付参数

在config 包中 创建 WxPayConfig.java
  1. package com.atguigu.syt.order.config;
  2. @Configuration
  3. @PropertySource("classpath:wxpay.properties") //读取配置文件
  4. @ConfigurationProperties(prefix="wxpay") //读取wxpay节点
  5. @Data
  6. public class WxPayConfig {
  7.     // 商户号
  8.     private String mchId;
  9.     // 商户API证书序列号
  10.     private String mchSerialNo;
  11.     // 商户私钥文件
  12.     private String privateKeyPath;
  13.     // APIv3密钥
  14.     private String apiV3Key;
  15.     // APPID
  16.     private String appid;
  17.    
  18.     // 接收支付结果通知地址
  19.     private String notifyUrl;
  20.    
  21.     // 接收退款结果通知地址
  22.     private String notifyRefundUrl;
  23.    
  24. }
复制代码
1.3、自动更新微信支付平台证书

在 API 请求过程中,客户端需使用微信支付平台证书,验证服务器应答的真实性和完整性。我们使用自动更新平台证书的配置类 RSAAutoCertificateConfig。每个商户号只能创建一个 RSAAutoCertificateConfig。
在WxPayConfig中添加如下方法:
  1. /**
  2.      * 获取微信支付配置对象
  3.      * @return
  4.      */
  5. @Bean
  6. public RSAAutoCertificateConfig getConfig(){
  7.     return new RSAAutoCertificateConfig.Builder()
  8.         .merchantId(mchId)
  9.         .privateKeyFromPath(privateKeyPath)
  10.         .merchantSerialNumber(mchSerialNo)
  11.         .apiV3Key(apiV3Key)
  12.         .build();
  13. }
复制代码
RSAAutoCertificateConfig 通过 RSAAutoCertificateProvider 自动下载微信支付平台证书。 同时,RSAAutoCertificateProvider 会启动一个后台线程,定时更新证书(目前设计为60分钟),以实现证书过期时的新老证书平滑切换。
常见错误:引入商户私钥后如果项目无法启动,则需要升级JDK版本,并重新配置idea编译和运行环境到最新版本的JDK。建议升级到1.8.0_300以上
<img alt="img" loading="lazy">
2、生成支付二维码

2.1、Native支付流程

参考文档:业务流程时序图

2.2、Controller

在service-order中创建FrontWXPayController
  1. package com.atguigu.syt.order.controller.front;
  2. @Api(tags = "微信支付接口")
  3. @RestController
  4. @RequestMapping("/front/order/wxpay")
  5. public class FrontWXPayController {
  6.     @Resource
  7.     private WxPayService wxPayService;
  8.     @Resource
  9.     private AuthContextHolder authContextHolder;
  10.     @ApiOperation("获取支付二维码url")
  11.     @ApiImplicitParam(name = "outTradeNo",value = "订单号", required = true)
  12.     @GetMapping("/auth/nativePay/{outTradeNo}")
  13.     public Result<String> nativePay(@PathVariable String outTradeNo, HttpServletRequest request, HttpServletResponse response) {
  14.         //校验用户登录状态
  15.         authContextHolder.checkAuth(request, response);
  16.         String codeUrl = wxPayService.createNative(outTradeNo);
  17.         return Result.ok(codeUrl);
  18.     }
  19. }
复制代码
2.3、Service

SDK参考代码:wechatpay-java/NativePayServiceExample.java at main · wechatpay-apiv3/wechatpay-java · GitHub

具体代码示例如下:

Native下单API参数参考:Native下单API
接口:OrderInfoService
  1. /**
  2. * 根据订单号获取订单
  3. * @param outTradeNo
  4. * @return
  5. */
  6. OrderInfo selectByOutTradeNo(String outTradeNo);
复制代码
实现:OrderInfoServiceImpl
  1. @Override
  2. public OrderInfo selectByOutTradeNo(String outTradeNo) {
  3.     LambdaQueryWrapper<OrderInfo> queryWrapper = new LambdaQueryWrapper<>();
  4.     queryWrapper.eq(OrderInfo::getOutTradeNo, outTradeNo);
  5.     return baseMapper.selectOne(queryWrapper);
  6. }
复制代码
接口:WxPayService
  1. package com.atguigu.syt.order.service;
  2. public interface WxPayService {
  3.     /**
  4.      * 获取支付二维码utl
  5.      * @param outTradeNo
  6.      * @return
  7.      */
  8.     String createNative(String outTradeNo);
  9. }
复制代码
实现:WxPayServiceImpl
  1. package com.atguigu.syt.order.service.impl;
  2. @Service
  3. @Slf4j
  4. public class WxPayServiceImpl implements WxPayService {
  5.     @Resource
  6.     private RSAAutoCertificateConfig rsaAutoCertificateConfig;
  7.     @Resource
  8.     private WxPayConfig wxPayConfig;
  9.     @Resource
  10.     private OrderInfoService orderInfoService;
  11.     @Override
  12.     public String createNative(String outTradeNo) {
  13.         // 初始化服务
  14.         NativePayService service = new NativePayService.Builder().config(rsaAutoCertificateConfig).build();
  15.         // 调用接口
  16.         try {
  17.             //获取订单
  18.             OrderInfo orderInfo = orderInfoService.selectByOutTradeNo(outTradeNo);
  19.             PrepayRequest request = new PrepayRequest();
  20.             // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
  21.             request.setAppid(wxPayConfig.getAppid());
  22.             request.setMchid(wxPayConfig.getMchId());
  23.             request.setDescription(orderInfo.getTitle());
  24.             request.setOutTradeNo(outTradeNo);
  25.             request.setNotifyUrl(wxPayConfig.getNotifyUrl());
  26.             Amount amount = new Amount();
  27.             //amount.setTotal(orderInfo.getAmount().multiply(new BigDecimal(100)).intValue());
  28.             amount.setTotal(1);//1分钱
  29.             request.setAmount(amount);
  30.             // 调用接口
  31.             PrepayResponse prepayResponse = service.prepay(request);
  32.             return prepayResponse.getCodeUrl();
  33.         } catch (HttpException e) { // 发送HTTP请求失败
  34.             // 调用e.getHttpRequest()获取请求打印日志或上报监控,更多方法见HttpException定义
  35.             log.error(e.getHttpRequest().toString());
  36.             throw new GuiguException(ResultCodeEnum.FAIL);
  37.         } catch (ServiceException e) { // 服务返回状态小于200或大于等于300,例如500
  38.             // 调用e.getResponseBody()获取返回体打印日志或上报监控,更多方法见ServiceException定义
  39.             log.error(e.getResponseBody());
  40.             throw new GuiguException(ResultCodeEnum.FAIL);
  41.         } catch (MalformedMessageException e) { // 服务返回成功,返回体类型不合法,或者解析返回体失败
  42.             // 调用e.getMessage()获取信息打印日志或上报监控,更多方法见MalformedMessageException定义
  43.             log.error(e.getMessage());
  44.             throw new GuiguException(ResultCodeEnum.FAIL);
  45.         }
  46.     }
  47. }
复制代码
可见,使用 SDK 并不需要计算请求签名和验证应答签名。
3、前端整合

3.1、api

创建api/wxpay.js
  1. import request from '~/utils/request'
  2. export default {
  3.   //获取支付二维码url
  4.   nativePay(outTradeNo) {
  5.     return request({
  6.       url: `/front/order/wxpay/auth/nativePay/${outTradeNo}`,
  7.       method: 'get'
  8.     })
  9.   },
  10. }
复制代码
3.2、修改show.vue

引入api
  1. import wxpayApi from '~/api/wxpay'
复制代码
添加data数据
  1. codeUrl: null, //微信支付二维码
  2. isPayShow: false, //不显示登录二维码组件
  3. payText: '支付'
复制代码
修改按钮
  1. 支付
  2. 修改为
  3. {{payText}}
复制代码
添加方法
  1. //支付
  2. pay() {
  3.     //防止重复提交
  4.     if(this.isPayShow) return
  5.     this.isPayShow = true
  6.     this.payText = '支付中.....'
  7.     //显示二维码
  8.     wxpayApi.nativePay(this.orderInfo.outTradeNo).then((response) => {
  9.         this.codeUrl = response.data
  10.         this.dialogPayVisible = true
  11.     })
  12. },
  13. //关闭对话框
  14. closeDialog(){
  15.     //恢复支付按钮
  16.     this.isPayShow = false
  17.     this.payText = '支付'
  18. }
复制代码
用二维码组件替换img图片
  1. <img src="https://www.cnblogs.com/二维码链接" alt="" />
  2. 替换成
  3. <qriously :value="codeUrl" :size="220"/>
复制代码

第03章-查询支付结果

1、支付查单

参考文档:微信支付查单接口
商户可以主动调用微信支付查单接口,同步订单状态。
调用查询订单接口,如果支付成功则更新订单状态并添加交易记录,如果支付尚未成功则轮询查单
1.1、更新订单状态

OrderInfoService接口
  1. /**
  2.      * 根据订单号更新订单状态
  3.      * @param outTradeNo
  4.      * @param status
  5.      */
  6. void updateStatus(String outTradeNo, Integer status);
复制代码
OrderInfoServiceImpl实现
  1. @Override
  2. public void updateStatus(String outTradeNo, Integer status) {
  3.     LambdaQueryWrapper<OrderInfo> queryWrapper = new LambdaQueryWrapper<>();
  4.     queryWrapper.eq(OrderInfo::getOutTradeNo, outTradeNo);
  5.     OrderInfo orderInfo = new OrderInfo();
  6.     orderInfo.setOrderStatus(status);
  7.     baseMapper.update(orderInfo, queryWrapper);
  8. }
复制代码
1.2、添加交易记录

PaymentInfoService接口
  1. /**
  2.      * 保存交易记录
  3.      * @param orderInfo
  4.      * @param transaction
  5.      */
  6. void savePaymentInfo(OrderInfo orderInfo, Transaction transaction);
复制代码
实现:PaymentInfoServiceImpl
  1. @Override
  2. public void savePaymentInfo(OrderInfo orderInfo, Transaction transaction) {
  3.     PaymentInfo paymentInfo = new PaymentInfo();
  4.     paymentInfo.setOrderId(orderInfo.getId());
  5.     paymentInfo.setPaymentType(PaymentTypeEnum.WEIXIN.getStatus());
  6.     paymentInfo.setOutTradeNo(orderInfo.getOutTradeNo());//数据库字段的长度改成32
  7.     paymentInfo.setSubject(orderInfo.getTitle());
  8.     paymentInfo.setTotalAmount(orderInfo.getAmount());
  9.     paymentInfo.setPaymentStatus(PaymentStatusEnum.PAID.getStatus());
  10.     paymentInfo.setTradeNo(transaction.getTransactionId());
  11.     paymentInfo.setCallbackTime(new Date());
  12.     paymentInfo.setCallbackContent(transaction.toString());
  13.     baseMapper.insert(paymentInfo);
  14. }
复制代码
1.3、查单Controller

FrontWXPayController
  1. @ApiOperation("查询支付状态")
  2. @ApiImplicitParam(name = "outTradeNo",value = "订单id", required = true)
  3. @GetMapping("/queryPayStatus/{outTradeNo}")
  4. public Result queryPayStatus(@PathVariable String outTradeNo) {
  5.     //调用查询接口
  6.     boolean success = wxPayService.queryPayStatus(outTradeNo);
  7.     if (success) {
  8.         return Result.ok().message("支付成功");
  9.     }
  10.     return Result.ok().message("支付中").code(250);
  11. }
复制代码
1.4、查单Service

接口:WxPayService
  1. /**
  2.      * 查询订单支付状态
  3.      * @param outTradeNo
  4.      * @return
  5.      */
  6. boolean queryPayStatus(String outTradeNo);
复制代码
实现:WxPayServiceImpl
  1. @Resource
  2. private PaymentInfoService paymentInfoService;
  3. @Override
  4. public boolean queryPayStatus(String outTradeNo) {
  5.     // 初始化服务
  6.     NativePayService service = new NativePayService.Builder().config(rsaAutoCertificateConfig).build();
  7.     // 调用接口
  8.     try {
  9.         QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
  10.         // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义
  11.         request.setOutTradeNo(outTradeNo);
  12.         request.setMchid(wxPayConfig.getMchId());
  13.         // 调用接口
  14.         Transaction transaction = service.queryOrderByOutTradeNo(request);
  15.         Transaction.TradeStateEnum tradeState = transaction.getTradeState();
  16.         //支付成功
  17.         if(tradeState.equals(Transaction.TradeStateEnum.SUCCESS)){
  18.             OrderInfo orderInfo = orderInfoService.selectByOutTradeNo(outTradeNo);
  19.             //更新订单状态
  20.             orderInfoService.updateStatus(outTradeNo, OrderStatusEnum.PAID.getStatus());
  21.             //记录支付日志
  22.             paymentInfoService.savePaymentInfo(orderInfo, transaction);
  23.             //通知医院修改订单状态
  24.             //组装参数
  25.             HashMap<String, Object> paramsMap = new HashMap<>();
  26.             paramsMap.put("hoscode", orderInfo.getHoscode());
  27.             paramsMap.put("hosOrderId", orderInfo.getHosOrderId());
  28.             paramsMap.put("timestamp", HttpRequestHelper.getTimestamp());
  29.             paramsMap.put("sign", HttpRequestHelper.getSign(paramsMap, "8af52af00baf6aec434109fc17164aae"));
  30.             //发送请求
  31.             JSONObject jsonResult = HttpRequestHelper.sendRequest(paramsMap, "http://localhost:9998/order/updatePayStatus");
  32.             //解析响应
  33.             if(jsonResult.getInteger("code") != 200) {
  34.                 log.error("查单失败,"
  35.                           + "code:" + jsonResult.getInteger("code")
  36.                           + ",message:" + jsonResult.getString("message")
  37.                          );
  38.                 throw new GuiguException(ResultCodeEnum.FAIL.getCode(), jsonResult.getString("message"));
  39.             }
  40.             //返回支付结果
  41.             return true;//支付成功
  42.         }
  43.         return false;//支付中
  44.     } catch (HttpException e) { // 发送HTTP请求失败
  45.         // 调用e.getHttpRequest()获取请求打印日志或上报监控,更多方法见HttpException定义
  46.         log.error(e.getHttpRequest().toString());
  47.         throw new GuiguException(ResultCodeEnum.FAIL);
  48.     } catch (ServiceException e) { // 服务返回状态小于200或大于等于300,例如500
  49.         // 调用e.getResponseBody()获取返回体打印日志或上报监控,更多方法见ServiceException定义
  50.         log.error(e.getResponseBody());
  51.         throw new GuiguException(ResultCodeEnum.FAIL);
  52.     } catch (MalformedMessageException e) { // 服务返回成功,返回体类型不合法,或者解析返回体失败
  53.         // 调用e.getMessage()获取信息打印日志或上报监控,更多方法见MalformedMessageException定义
  54.         log.error(e.getMessage());
  55.         throw new GuiguException(ResultCodeEnum.FAIL);
  56.     }
  57. }
复制代码
2、前端整合

2.1、修改request.js

utils/request.js的响应拦截器中增加对250状态的判断
  1. }else if (response.data.code !== 200) {
  2.    
  3. 修改为
  4. }else if (response.data.code !== 200 && response.data.code !== 250) {
复制代码
2.2、api

在api/wxpay.js中添加的方法
  1. //查询订单
  2. queryPayStatus(outTradeNo) {
  3.     return request({
  4.         url: `/front/order/wxpay/queryPayStatus/${outTradeNo}`,
  5.         method: 'get'
  6.     })
  7. },
复制代码
3.3、show.vue轮询查单

js中修改pay方法:每隔3秒查单
添加queryPayStatus方法
完善closeDialog方法:停止定时器
  1. //发起支付
  2. pay(){
  3.   if(this.isPayShow) return
  4.   this.isPayShow = true
  5.   this.payText = '支付中.....'
  6.    
  7.   wxpayApi.nativePay(this.orderInfo.outTradeNo).then((response) => {
  8.     this.codeUrl = response.data
  9.     this.dialogPayVisible = true
  10.       
  11.     //每隔3秒查单
  12.     this.timer = setInterval(()=>{
  13.       console.log('轮询查单:' + new Date())
  14.       this.queryPayStatus()
  15.     }, 3000)
  16.   })
  17. },
  18.    
  19. //查询订单状态
  20. queryPayStatus(){
  21.   wxpayApi.queryPayStatus(this.orderInfo.outTradeNo).then(response => {
  22.     if(response.code == 250){
  23.         return
  24.     }
  25.     //清空定时器
  26.     clearInterval(this.timer)
  27.     window.location.reload()
  28.   })
  29. },
  30. //关闭对话框
  31. closeDialog(){
  32.     //停止定时器
  33.     clearInterval(this.timer)
  34.     //恢复支付按钮
  35.     this.isPayShow = false
  36.     this.payText = '支付'
  37. }
复制代码
3、查单应答超时

3.1、测试应答超时
  1. //应答超时
  2. //设置响应超时,支付成功后没有及时响应,可能继续轮询查单,此时数据库会记录多余的支付日志
  3. try {
  4.     TimeUnit.SECONDS.sleep(5);
  5. } catch (InterruptedException e) {
  6.     throw new RuntimeException(e);
  7. }
  8. //返回支付结果
  9. return true;//支付成功
复制代码
3.2、处理重复通知

支付成功后,判断订单状态:
  1. OrderInfo orderInfo = orderInfoService.selectByOutTradeNo(outTradeNo);
  2. //处理支付成功后重复查单
  3. //保证接口调用的幂等性:无论接口被调用多少次,产生的结果是一致的
  4. Integer orderStatus = orderInfo.getOrderStatus();
  5. if (OrderStatusEnum.UNPAID.getStatus().intValue() != orderStatus.intValue()) {
  6.     return true;//支付成功、关单、。。。
  7. }
  8. //更新订单状态
  9. //记录支付日志
  10. ......
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

祗疼妳一个

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表