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

Posted Refeng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网站怎么接入微信扫码支付?相关的知识,希望对你有一定的参考价值。

第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微服务中添加依赖:

<!--微信支付APIv3-->
<dependency>
  <groupId>com.github.wechatpay-apiv3</groupId>
  <artifactId>wechatpay-java</artifactId>
  <version>0.2.6</version>
</dependency>

1.2、读取支付参数

在config 包中 创建 WxPayConfig.java

package com.atguigu.syt.order.config;

@Configuration
@PropertySource("classpath:wxpay.properties") //读取配置文件
@ConfigurationProperties(prefix="wxpay") //读取wxpay节点
@Data
public 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
     */
@Bean
public 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<String> 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

@Override
public OrderInfo selectByOutTradeNo(String outTradeNo) 
    LambdaQueryWrapper<OrderInfo> 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
@Slf4j
public 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: \'支付\'

修改按钮

将
<div class="v-button" @click="pay()">支付</div>
修改为
<div class="v-button" @click="pay()">payText</div>

添加方法

//支付
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图片

<img src="二维码链接"  />

替换成

<qriously :value="codeUrl" :size="220"/>

第03章-查询支付结果

1、支付查单

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

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

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

1.1、更新订单状态

OrderInfoService接口

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

OrderInfoServiceImpl实现

@Override
public void updateStatus(String outTradeNo, Integer status) 

    LambdaQueryWrapper<OrderInfo> 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

@Override
public 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

@Resource
private PaymentInfoService paymentInfoService;

@Override
public 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<String, Object> 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;//支付成功、关单、。。。


//更新订单状态
//记录支付日志
......

以上是关于网站怎么接入微信扫码支付?的主要内容,如果未能解决你的问题,请参考以下文章

java开发微信扫码支付,怎么做测试,公众账号ID,商户号这些怎么来?

扫码支付接入微信支付的流程是啥样的?

怎么样用微信扫码支付

微信扫码支付怎么使用?

微信扫码支付视频课程(Java版)

Net MVC微信扫码支付