第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="二维码链接" alt="" />

替换成

<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;//支付成功、关单、。。。
} //更新订单状态
//记录支付日志
......

尚医通day16-网站怎么接入微信扫码支付?的更多相关文章

  1. ASP.NET Core Web 支付功能接入 微信-扫码支付篇

    这篇文章将介绍ASP.NET Core中使用 开源项目 Payment,实现接入微信-扫码支付及异步通知功能. 开发环境:Win 10 x64.VS2017 15.6.4..NET Core SDK ...

  2. 【转载】ASP.NET Core Web 支付功能接入 微信-扫码支付篇

    转自:http://www.cnblogs.com/essenroc/p/8630730.html 这篇文章将介绍ASP.NET Core中使用 开源项目 Payment,实现接入微信-扫码支付及异步 ...

  3. ASP.NET Core Web 支付功能接入 微信-扫码支付篇(转)

    原文 https://www.cnblogs.com/essenroc/p/8630730.html // 随着版本更迭,新版本可能无法完全适用,请参考仓库内的示例. 这篇文章将介绍ASP.NET C ...

  4. thinkphp.2 thinkphp5微信支付 微信公众号支付 thinkphp 微信扫码支付 thinkphp 微信企业付款5

    前面已经跑通了微信支付的流程,接下来吧微信支付和微信企业付款接入到thinkphp中,版本是3.2 把微信支付类.企业付款类整合到一起放到第三方类库,这里我把微信支付帮助类和企业付款类放到同一个文件了 ...

  5. PHP PC端微信扫码支付【模式二】详细教程-附带源码(转)

    博主写这破玩意儿的时候花了大概快两天时间才整体的弄懂逻辑,考虑了一下~还是把所有代码都放出来给大家~抱着开源大无私的精神!谁叫我擅长拍黄片呢?同时也感谢我刚入行时候那些无私帮过我的程序员们! 首先还是 ...

  6. 【移动支付】.NET微信扫码支付接入(模式二-NATIVE)

    一.前言       经过两三天的琢磨总算完成了微信扫码支付功能,不得不感叹几句: 微信提供的DEMO不错,直接复制粘贴就可以跑起来了: 微信的配置平台我真是服了.公众平台.商户平台.开放平台,一个平 ...

  7. 微信扫码支付PHP接入总结

    微信扫码支付分为两种模式, 模式一比较复杂,需要公众号配置回调地址. 模式二比较简单,只需要在代码中配置回调地址就可以了. 我这次使用的是模式二. 需要配置参数, const APPID = 'xxx ...

  8. JAVA微信扫码支付模式二功能实现完整例子

    概述 本例子实现微信扫码支付模式二的支付功能,应用场景是,web网站微信扫码支付.实现从点击付费按钮.到弹出二维码.到用户用手机微信扫码支付.到手机上用户付费成功.web网页再自动调整到支付成功后的页 ...

  9. Net MVC微信扫码支付

    微信扫码支付+Asp.Net MVC 这里的扫码支付指的是PC网站上面使用微信支付,也就是官方的模式二,网站是Asp.net MVC,整理如下. 一.准备工作 使用的微信API中的统一下单方法,关键的 ...

  10. 微信公众号支付|微信H5支付|微信扫码支付|小程序支付|APP微信支付解决方案总结

    最近负责的一些项目开发,都用到了微信支付(微信公众号支付.微信H5支付.微信扫码支付.APP微信支付).在开发的过程中,在调试支付的过程中,或多或少都遇到了一些问题,今天总结下,分享,留存. 先说注意 ...

随机推荐

  1. go微服务框架kratos学习笔记二(kratos demo 结构)

    目录 api cmd configs dao di model server service 上篇文章go微服务框架kratos学习笔记一(kratos demo)跑了kratos demo 本章来看 ...

  2. 【Deep Learning】DDPM

    DDPM 1. 大致流程 1.1 宏观流程 1.2 训练过程 1.3 推理过程 2. 对比GAN 2.1 GAN流程 2.2 相比GAN优点 训练过程更稳定,损失函数指向性更强(loss数值大小指示训 ...

  3. Charlotte Holmes series

    Charlotte Holmes Novel The charactors are adorable. Jamie and Charlotte are a very cute couple. More ...

  4. 通过python修改本地ip

    写在前面, 1 对于个人公司需要固定ip,而回家需要用到家里的ip, 2对于公司it人员,每台电脑都需要设置ip,,尤其批量的时候,这个作为it的自己知道 3运维人员,可以通过ip测试哪些ip可以用, ...

  5. FFmpeg开发笔记(一)搭建Linux系统的开发环境

    对于初学者来说,如何搭建FFmpeg的开发环境是个不小的拦路虎,因为FFmpeg用到了许多第三方开发包,所以要先编译这些第三方源码,之后才能给FFmpeg集成编译好的第三方库.不过考虑到刚开始仅仅调用 ...

  6. Python实现网络工具

    使用python编写网络工具 基础内容 介绍基本的网络编程 Socket编程 Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请 ...

  7. 如何根据需求选择合适的数据库管理工具?Navicat OR DBeaver

    1.写在前面 在阅读本文之前,糖糖给大家准备了Navicat和DBeaver安装包,在公众号内回复"Navicat"或"DBeaver"或"数据库管理 ...

  8. 常见API使用

    String类 字符串相关的类 Java程序中的所有字符串文字(例如"abc")都实现为此类的实例 字符串是不变的 他们的值在创建后无法更改 int length() 返回字符串对 ...

  9. 开心档之MySQL 连接

    MySQL 连接 使用mysql二进制方式连接 您可以使用MySQL二进制方式进入到mysql命令提示符下来连接MySQL数据库. 实例 以下是从命令行中连接mysql服务器的简单实例: [root@ ...

  10. Django笔记二十八之数据库查询优化汇总

    本文首发于公众号:Hunter后端 原文链接:Django笔记二十八之数据库查询优化汇总 这一篇笔记将从以下几个方面来介绍 Django 在查询过程中的一些优化操作,有一些是介绍如何获取 Django ...