最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

网站怎么接入微信扫码支付?|焦点快报

来源:博客园

第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秘钥等等

# 微信支付相关参数wxpay:  mch-id: 1558950191 #商户号  mch-serial-no: 34345964330B66427E0D3D28826C4993C77E631F # 商户API证书序列号  private-key-path: D:/project/yygh/cert/apiclient_key.pem # 商户私钥文件  api-v3-key: UDuLFDcmy5Eb6o0nTNZdu6ek4DDh4K8B # APIv3密钥  appid: wx74862e0dfcf69954 # APPID  notify-url: https://7d92-115-171-63-135.ngrok.io/api/order/wxpay/payment/notify # 接收支付结果通知地址  notify-refund-url: http://agxnyzl04y90.ngrok.xiaomiqiu123.top/api/order/wxpay/refunds/notify # 接收退款结果通知地址

3.3、复制商户私钥

将商户私钥文件复制到配置文件指定的目录下:

资料:资料>微信支付>商户证书>apiclient_key.pem

private-key-path: D:/project/yygh/cert/apiclient_key.pem # 商户私钥文件

3.4、证书密钥使用说明(了解)

参考文档:APIv3证书与密钥使用说明

一个完整的请求和响应的流程:

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

第02章-订单支付

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

1.1、引入SDK

参考文档:SDK&工具

我们可以使用官方提供的 SDK wechatpay-java

在service-order微服务中添加依赖:

  com.github.wechatpay-apiv3  wechatpay-java  0.2.6

1.2、读取支付参数

在config 包中 创建 WxPayConfig.java

package com.atguigu.syt.order.config;@Configuration@PropertySource("classpath:wxpay.properties") //读取配置文件@ConfigurationProperties(prefix="wxpay") //读取wxpay节点@Datapublic class WxPayConfig {    // 商户号    private String mchId;    // 商户API证书序列号    private String mchSerialNo;    // 商户私钥文件    private String privateKeyPath;    // APIv3密钥    private String apiV3Key;    // APPID    private String appid;        // 接收支付结果通知地址    private String notifyUrl;        // 接收退款结果通知地址    private String notifyRefundUrl;   }

1.3、自动更新微信支付平台证书

在 API 请求过程中,客户端需使用微信支付平台证书,验证服务器应答的真实性和完整性。我们使用自动更新平台证书的配置类 RSAAutoCertificateConfig。每个商户号只能创建一个 RSAAutoCertificateConfig

WxPayConfig中添加如下方法:

/**     * 获取微信支付配置对象     * @return     */@Beanpublic RSAAutoCertificateConfig getConfig(){    return new RSAAutoCertificateConfig.Builder()        .merchantId(mchId)        .privateKeyFromPath(privateKeyPath)        .merchantSerialNumber(mchSerialNo)        .apiV3Key(apiV3Key)        .build();}

RSAAutoCertificateConfig通过 RSAAutoCertificateProvider自动下载微信支付平台证书。 同时,RSAAutoCertificateProvider会启动一个后台线程,定时更新证书(目前设计为60分钟),以实现证书过期时的新老证书平滑切换。

常见错误:引入商户私钥后如果项目无法启动,则需要升级JDK版本,并重新配置idea编译和运行环境到最新版本的JDK。建议升级到1.8.0_300以上

2、生成支付二维码

2.1、Native支付流程

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

2.2、Controller

在service-order中创建FrontWXPayController

package com.atguigu.syt.order.controller.front;@Api(tags = "微信支付接口")@RestController@RequestMapping("/front/order/wxpay")public class FrontWXPayController {    @Resource    private WxPayService wxPayService;    @Resource    private AuthContextHolder authContextHolder;    @ApiOperation("获取支付二维码url")    @ApiImplicitParam(name = "outTradeNo",value = "订单号", required = true)    @GetMapping("/auth/nativePay/{outTradeNo}")    public Result nativePay(@PathVariable String outTradeNo, HttpServletRequest request, HttpServletResponse response) {        //校验用户登录状态        authContextHolder.checkAuth(request, response);        String codeUrl = wxPayService.createNative(outTradeNo);        return Result.ok(codeUrl);    }}

2.3、Service

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

具体代码示例如下:

Native下单API参数参考:Native下单API

接口:OrderInfoService

/** * 根据订单号获取订单 * @param outTradeNo * @return */OrderInfo selectByOutTradeNo(String outTradeNo);

实现:OrderInfoServiceImpl

@Overridepublic OrderInfo selectByOutTradeNo(String outTradeNo) {    LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();    queryWrapper.eq(OrderInfo::getOutTradeNo, outTradeNo);    return baseMapper.selectOne(queryWrapper);}

接口:WxPayService

package com.atguigu.syt.order.service;public interface WxPayService {    /**     * 获取支付二维码utl     * @param outTradeNo     * @return     */    String createNative(String outTradeNo);}

实现:WxPayServiceImpl

package com.atguigu.syt.order.service.impl;@Service@Slf4jpublic class WxPayServiceImpl implements WxPayService {    @Resource    private RSAAutoCertificateConfig rsaAutoCertificateConfig;    @Resource    private WxPayConfig wxPayConfig;    @Resource    private OrderInfoService orderInfoService;    @Override    public String createNative(String outTradeNo) {        // 初始化服务        NativePayService service = new NativePayService.Builder().config(rsaAutoCertificateConfig).build();        // 调用接口        try {            //获取订单            OrderInfo orderInfo = orderInfoService.selectByOutTradeNo(outTradeNo);            PrepayRequest request = new PrepayRequest();            // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义            request.setAppid(wxPayConfig.getAppid());            request.setMchid(wxPayConfig.getMchId());            request.setDescription(orderInfo.getTitle());            request.setOutTradeNo(outTradeNo);            request.setNotifyUrl(wxPayConfig.getNotifyUrl());            Amount amount = new Amount();            //amount.setTotal(orderInfo.getAmount().multiply(new BigDecimal(100)).intValue());            amount.setTotal(1);//1分钱            request.setAmount(amount);            // 调用接口            PrepayResponse prepayResponse = service.prepay(request);            return prepayResponse.getCodeUrl();        } catch (HttpException e) { // 发送HTTP请求失败            // 调用e.getHttpRequest()获取请求打印日志或上报监控,更多方法见HttpException定义            log.error(e.getHttpRequest().toString());            throw new GuiguException(ResultCodeEnum.FAIL);        } catch (ServiceException e) { // 服务返回状态小于200或大于等于300,例如500            // 调用e.getResponseBody()获取返回体打印日志或上报监控,更多方法见ServiceException定义            log.error(e.getResponseBody());            throw new GuiguException(ResultCodeEnum.FAIL);        } catch (MalformedMessageException e) { // 服务返回成功,返回体类型不合法,或者解析返回体失败            // 调用e.getMessage()获取信息打印日志或上报监控,更多方法见MalformedMessageException定义            log.error(e.getMessage());            throw new GuiguException(ResultCodeEnum.FAIL);        }    }}

可见,使用 SDK 并不需要计算请求签名和验证应答签名。

3、前端整合

3.1、api

创建api/wxpay.js

import request from "~/utils/request"export default {  //获取支付二维码url  nativePay(outTradeNo) {    return request({      url: `/front/order/wxpay/auth/nativePay/${outTradeNo}`,      method: "get"    })  },}

3.2、修改show.vue

引入api

import wxpayApi from "~/api/wxpay"

添加data数据

codeUrl: null, //微信支付二维码isPayShow: false, //不显示登录二维码组件payText: "支付"

修改按钮

支付
修改为
{{payText}}

添加方法

//支付pay() {    //防止重复提交    if(this.isPayShow) return    this.isPayShow = true    this.payText = "支付中....."    //显示二维码    wxpayApi.nativePay(this.orderInfo.outTradeNo).then((response) => {        this.codeUrl = response.data        this.dialogPayVisible = true    })},//关闭对话框closeDialog(){    //恢复支付按钮    this.isPayShow = false    this.payText = "支付"}

用二维码组件替换img图片

替换成

第03章-查询支付结果

1、支付查单

参考文档:微信支付查单接口

商户可以主动调用微信支付查单接口,同步订单状态。

调用查询订单接口,如果支付成功则更新订单状态添加交易记录,如果支付尚未成功则轮询查单

1.1、更新订单状态

OrderInfoService接口

/**     * 根据订单号更新订单状态     * @param outTradeNo     * @param status     */void updateStatus(String outTradeNo, Integer status);

OrderInfoServiceImpl实现

@Overridepublic void updateStatus(String outTradeNo, Integer status) {    LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();    queryWrapper.eq(OrderInfo::getOutTradeNo, outTradeNo);    OrderInfo orderInfo = new OrderInfo();    orderInfo.setOrderStatus(status);    baseMapper.update(orderInfo, queryWrapper);}

1.2、添加交易记录

PaymentInfoService接口

/**     * 保存交易记录     * @param orderInfo     * @param transaction     */void savePaymentInfo(OrderInfo orderInfo, Transaction transaction);

实现:PaymentInfoServiceImpl

@Overridepublic void savePaymentInfo(OrderInfo orderInfo, Transaction transaction) {    PaymentInfo paymentInfo = new PaymentInfo();    paymentInfo.setOrderId(orderInfo.getId());    paymentInfo.setPaymentType(PaymentTypeEnum.WEIXIN.getStatus());    paymentInfo.setOutTradeNo(orderInfo.getOutTradeNo());//数据库字段的长度改成32    paymentInfo.setSubject(orderInfo.getTitle());    paymentInfo.setTotalAmount(orderInfo.getAmount());    paymentInfo.setPaymentStatus(PaymentStatusEnum.PAID.getStatus());    paymentInfo.setTradeNo(transaction.getTransactionId());    paymentInfo.setCallbackTime(new Date());    paymentInfo.setCallbackContent(transaction.toString());    baseMapper.insert(paymentInfo);}

1.3、查单Controller

FrontWXPayController

@ApiOperation("查询支付状态")@ApiImplicitParam(name = "outTradeNo",value = "订单id", required = true)@GetMapping("/queryPayStatus/{outTradeNo}")public Result queryPayStatus(@PathVariable String outTradeNo) {    //调用查询接口    boolean success = wxPayService.queryPayStatus(outTradeNo);    if (success) {        return Result.ok().message("支付成功");    }    return Result.ok().message("支付中").code(250);}

1.4、查单Service

接口:WxPayService

/**     * 查询订单支付状态     * @param outTradeNo     * @return     */boolean queryPayStatus(String outTradeNo);

实现:WxPayServiceImpl

@Resourceprivate PaymentInfoService paymentInfoService;@Overridepublic boolean queryPayStatus(String outTradeNo) {    // 初始化服务    NativePayService service = new NativePayService.Builder().config(rsaAutoCertificateConfig).build();    // 调用接口    try {        QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();        // 调用request.setXxx(val)设置所需参数,具体参数可见Request定义        request.setOutTradeNo(outTradeNo);        request.setMchid(wxPayConfig.getMchId());        // 调用接口        Transaction transaction = service.queryOrderByOutTradeNo(request);        Transaction.TradeStateEnum tradeState = transaction.getTradeState();        //支付成功        if(tradeState.equals(Transaction.TradeStateEnum.SUCCESS)){            OrderInfo orderInfo = orderInfoService.selectByOutTradeNo(outTradeNo);            //更新订单状态            orderInfoService.updateStatus(outTradeNo, OrderStatusEnum.PAID.getStatus());            //记录支付日志            paymentInfoService.savePaymentInfo(orderInfo, transaction);            //通知医院修改订单状态            //组装参数            HashMap paramsMap = new HashMap<>();            paramsMap.put("hoscode", orderInfo.getHoscode());            paramsMap.put("hosOrderId", orderInfo.getHosOrderId());            paramsMap.put("timestamp", HttpRequestHelper.getTimestamp());            paramsMap.put("sign", HttpRequestHelper.getSign(paramsMap, "8af52af00baf6aec434109fc17164aae"));            //发送请求            JSONObject jsonResult = HttpRequestHelper.sendRequest(paramsMap, "http://localhost:9998/order/updatePayStatus");            //解析响应            if(jsonResult.getInteger("code") != 200) {                log.error("查单失败,"                          + "code:" + jsonResult.getInteger("code")                          + ",message:" + jsonResult.getString("message")                         );                throw new GuiguException(ResultCodeEnum.FAIL.getCode(), jsonResult.getString("message"));            }            //返回支付结果            return true;//支付成功        }        return false;//支付中    } catch (HttpException e) { // 发送HTTP请求失败        // 调用e.getHttpRequest()获取请求打印日志或上报监控,更多方法见HttpException定义        log.error(e.getHttpRequest().toString());        throw new GuiguException(ResultCodeEnum.FAIL);    } catch (ServiceException e) { // 服务返回状态小于200或大于等于300,例如500        // 调用e.getResponseBody()获取返回体打印日志或上报监控,更多方法见ServiceException定义        log.error(e.getResponseBody());        throw new GuiguException(ResultCodeEnum.FAIL);    } catch (MalformedMessageException e) { // 服务返回成功,返回体类型不合法,或者解析返回体失败        // 调用e.getMessage()获取信息打印日志或上报监控,更多方法见MalformedMessageException定义        log.error(e.getMessage());        throw new GuiguException(ResultCodeEnum.FAIL);    }}

2、前端整合

2.1、修改request.js

utils/request.js的响应拦截器中增加对250状态的判断

}else if (response.data.code !== 200) {    修改为}else if (response.data.code !== 200 && response.data.code !== 250) {

2.2、api

在api/wxpay.js中添加的方法

//查询订单queryPayStatus(outTradeNo) {    return request({        url: `/front/order/wxpay/queryPayStatus/${outTradeNo}`,        method: "get"    })},

3.3、show.vue轮询查单

js中修改pay方法:每隔3秒查单

添加queryPayStatus方法

完善closeDialog方法:停止定时器

//发起支付pay(){  if(this.isPayShow) return  this.isPayShow = true  this.payText = "支付中....."      wxpayApi.nativePay(this.orderInfo.outTradeNo).then((response) => {    this.codeUrl = response.data    this.dialogPayVisible = true          //每隔3秒查单    this.timer = setInterval(()=>{      console.log("轮询查单:" + new Date())      this.queryPayStatus()    }, 3000)  })},    //查询订单状态queryPayStatus(){  wxpayApi.queryPayStatus(this.orderInfo.outTradeNo).then(response => {    if(response.code == 250){        return    }    //清空定时器    clearInterval(this.timer)    window.location.reload()  })},//关闭对话框closeDialog(){    //停止定时器    clearInterval(this.timer)    //恢复支付按钮    this.isPayShow = false    this.payText = "支付"}

3、查单应答超时

3.1、测试应答超时

//应答超时//设置响应超时,支付成功后没有及时响应,可能继续轮询查单,此时数据库会记录多余的支付日志try {    TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {    throw new RuntimeException(e);}//返回支付结果return true;//支付成功

3.2、处理重复通知

支付成功后,判断订单状态:

OrderInfo orderInfo = orderInfoService.selectByOutTradeNo(outTradeNo);//处理支付成功后重复查单//保证接口调用的幂等性:无论接口被调用多少次,产生的结果是一致的Integer orderStatus = orderInfo.getOrderStatus();if (OrderStatusEnum.UNPAID.getStatus().intValue() != orderStatus.intValue()) {    return true;//支付成功、关单、。。。}//更新订单状态//记录支付日志......

关键词: