1.开发前的准备条件 【 微信支付 NATIVE
支付模式 (V2版本)】
- 开发环境
1. Spring Boot Version: 2.1.17.RELEASE
2. jdk 8
- 准备工作
- maven坐标 (version 3.5.4.B)
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-pay-spring-boot-starter</artifactId>
<version>${wx.pay.version}</version>
</dependency>
- 微信开发相关配置
1.appId,mchId,mchKey [这些都需要去微信平台获取]
2.配置一个可以直接外网访问的url 用作微信支付后微信平台回调我们的接口
- 支付流程图
- 流程
1.商户后台系统根据用户选购的商品生成订单。
2.用户确认支付后调用微信支付【统一下单API】生成预支付交易;
3.微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。
4.商户后台系统根据返回的code_url生成二维码。
5.用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
6.微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。
7.用户在微信客户端输入密码,确认支付后,微信客户端提交授权。
8.微信支付系统根据用户授权完成支付交易。
9.微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
10.微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
11.未收到支付通知的情况,商户后台系统调用【查询订单API】。
12.商户确认订单已支付后给用户发货。
2.开发步骤
- yml配置文件 (
替换自己的appId,mchId,mchKey,回调地址一定要可以直接外网访问通,不能有查询参数限制,不然微信无法通知自己的业务系统
)
wx:
pay:
appId: wx538891b3add78wew
mchId: 1644361212
mchKey: 5b2b6687cfdde9cb5e2d0b36d95f6548
subAppId:
subMchId:
keyPath:
notifyUrl: https://payapi.wx.com/wx/order/notify
- 编写配置文件
- properties
/**
* @author:kyrie
* @date:2023/5/16 13:21
* @Description: 微信支付配置属性
**/
@Data
@Component
@ConfigurationProperties(prefix = "wx.pay")
public class WxPayProperties {
/**
* 设置微信公众号或者小程序等的appid.
*/
private String appId;
/**
* 微信支付商户号.
*/
private String mchId;
/**
* 微信支付商户密钥.
*/
private String mchKey;
/**
* 服务商模式下的子商户公众账号ID,普通模式请不要配置,请在配置文件中将对应项删除.
*/
private String subAppId;
/**
* 服务商模式下的子商户号,普通模式请不要配置,最好是请在配置文件中将对应项删除.
*/
private String subMchId;
/**
* apiclient_cert.p12文件的绝对路径,或者如果放在项目中,请以classpath:开头指定.
*/
private String keyPath;
private String notifyUrl;
}
- javaConfig
/**
* @author: kyrie
* @date:2023/5/11 13:02
* @Description: 微信支付配置类
**/
@Configuration
public class WxPayConfiguration {
@Autowired
private WxPayProperties payProperties;
@Bean
public WxPayService wxPayService() {
WxPayServiceImpl wxPayService = new WxPayServiceImpl();
WxPayConfig payConfig = new WxPayConfig();
payConfig.setAppId(payProperties.getAppId());
payConfig.setMchId(payProperties.getMchId());
payConfig.setMchKey(payProperties.getMchKey());
payConfig.setKeyPath(payProperties.getKeyPath());
payConfig.setNotifyUrl(payProperties.getNotifyUrl());
wxPayService.setConfig(payConfig);
return wxPayService;
}
}
- 编写业务
- 创建订单
--------------------controller--------------------
/**
* 创建订单【微信】
*
* @param orderRequest 请求对象
* @return result
*/
@ApiOperation("创建订单【微信】")
@Log(title = "创建订单【微信】", businessType = BusinessTypeEnum.INSERT)
@PostMapping("/native")
@ApiTimeStatistics
public AjaxResult createNativeOrder(@RequestBody @Validated OrderNativeCreatRequest orderRequest) {
OrderNativeCreatedResponse response = orderService.handlerNativeCreateOrder(orderRequest);
return AjaxResult.success(response);
}
--------------------dto--------------------
/**
* @author:kyrie
* @date:2023/5/17 16:25
* @Description: 请求对象
**/
@Data
public class OrderNativeCreatRequest implements Serializable {
private static final long serialVersionUID = -7961794957591750053L;
@NotBlank(message = "订单号不能为空")
private String outTradeNo;
}
--------------------serviceImpl--------------------
/**
* 微信创建订单
*
* @param orderRequest 请求对象
* @return 返回对象
*/
@Transactional(rollbackFor = Exception.class)
@Override
public OrderNativeCreatedResponse handlerNativeCreateOrder(OrderNativeCreatRequest orderRequest) {
// 我这的outTradeNo是我前置的服务创建的,就是先创建了自己业务的一个订单对象,这个no要确保唯一,我之前使用的是MongoDB的Id生成策略生成的,这里可以自己自定义
OrderNativeCreatedResponse result;
String outTradeNo = orderRequest.getOutTradeNo();
Order order = null;
// 单服务的话这里可以直接使用RT自旋锁解决并发问题(ReentrantLock)
// 没必要采用这种Redisson的分布式lock
locker.lock(outTradeNo, Constants.REDIS_TIMEOUT);
try {
// 前置校验查询订单是否已经存在 就是一个getById()
order = selectOrderByOrderNo(outTradeNo);
Assert.notNull(order, "创建订单异常:订单不存在");
// 构建下单请求对象 这里我使用的是wxjava工具包 直接封装好了这个对象 sign和时间戳无需自己生成
WxPayUnifiedOrderRequest wxPayUnifiedOrderRequest = new WxPayUnifiedOrderRequest();
wxPayUnifiedOrderRequest.setBody(order.getProductDescription() + "_" + order.getEnterpriseName());
wxPayUnifiedOrderRequest.setOutTradeNo(outTradeNo);
// 金额需要转成分 微信是认分的所以不能有小数点 直接简单点*100
wxPayUnifiedOrderRequest.setTotalFee(Integer.valueOf(MoneyUtil.getMoney(order.getOrderMoney().toString())));
// 获取真实的ip 直接找个工具类就行
wxPayUnifiedOrderRequest.setSpbillCreateIp(IpUtils.getRealIP(ServletUtils.getRequest()));
wxPayUnifiedOrderRequest.setTradeType(com.github.binarywang.wxpay.constant.WxPayConstants.TradeType.NATIVE);
// 自己商品的id 这里我是瞎写的 可以自己根据自己业务修改
wxPayUnifiedOrderRequest.setProductId("12235413214070356458058");
// 这个我一开始没写 按照wxjava的配置 没有设置好像会去加载config的默认配置【我觉得没必要写】
wxPayUnifiedOrderRequest.setNotifyUrl(notifyUrl);
// 调用封装的wxPayService的方法即可【v2版本的都是xml交互,v3是json交互 ,所以不一样,这里wxjava已经写好了现成的bean转xml,直接用即可】
WxPayUnifiedOrderResult wxOrderResponse = wxPayService.unifiedOrder(wxPayUnifiedOrderRequest);
// 可能是啥情况error了直接订单失败 抛异常
if (ObjectUtil.isNull(wxOrderResponse)) {
order.setOrderStatus(WxPayStatusEnum.PAY_FAIL.getPayStatusCode());
updateById(order);
throw WxPayException.newBuilder().returnMsg("创建订单异常:无返回数据").build();
} else {
// 预支付id【可以不存,因为好多微信支付接口都可以用你自己的那个no去调用,就相当于你的orderNo和微信的内部的一个id是对应的,这样双方就可以通了】
order.setPrePayId(wxOrderResponse.getPrepayId());
order.setOrderStatus(WxPayStatusEnum.PAY_ING.getPayStatusCode());
updateById(order);
}
// 返回前端展示的数据而已 【没啥】
result = new OrderNativeCreatedResponse();
result.setPrepayId(wxOrderResponse.getPrepayId());
result.setCodeUrl(wxOrderResponse.getCodeURL());
// hutool 工具类生成base64的字符串
String qrCode = QrCodeUtil.generateAsBase64(wxOrderResponse.getCodeURL(), QrConfig.create(), "jpg");
result.setQrCode(qrCode);
result.setReturnUrl(order.getReturnUrl());
result.setBasiUrl(order.getBasiUrl());
result.setOrderId(order.getOrderId());
result.setOrderNo(order.getOrderNo());
result.setProductDescription(order.getProductDescription());
result.setEnterpriseName(order.getEnterpriseName());
} catch (WxPayException e) {
// 异常log
log.error("OrderServiceImpl--handlerNativeCreateOrder===error---->{}", e.getMessage());
if (ObjectUtil.isNotNull(order)) {
order.setOrderStatus(WxPayStatusEnum.PAY_FAIL.getPayStatusCode());
updateById(order);
}
throw new CustomException("创建订单异常:" + e.getReturnMsg());
} finally {
// 释放锁
locker.unlock(outTradeNo);
}
// return
return result;
}
- 查询订单
--------------------controller--------------------
/**
* 根据订单no查询订单号
*
* @param orderQueryRequest 请求对象
* @return result
*/
@ApiOperation("查询订单")
@Log(title = "查询订单", businessType = BusinessTypeEnum.UPDATE)
@PostMapping("/query")
@ApiTimeStatistics
public AjaxResult queryOrder(@RequestBody @Validated OrderQueryRequest orderQueryRequest) {
OrderQueryResponse response = orderService.queryOrder(orderQueryRequest);
return AjaxResult.success(response);
}
--------------------dto--------------------
/**
* @author:kyrie
* @date:2023/5/17 16:25
* @Description: 请求对象
**/
@Data
public class OrderQueryRequest implements Serializable {
private static final long serialVersionUID = 2516981824084563768L;
@NotBlank(message = "订单号不能为空")
private String outTradeNo;
}
--------------------serviceImpl--------------------
/**
* 查询订单
*
* @param orderQueryRequest 订单号
* @return 返回对象
*/
@Override
public OrderQueryResponse queryOrder(OrderQueryRequest orderQueryRequest) {
OrderQueryResponse response = new OrderQueryResponse();
String outTradeNo = orderQueryRequest.getOutTradeNo();
locker.lock(outTradeNo, Constants.REDIS_TIMEOUT);
try {
// 再次去查一下order的状态
Order order = selectOrderByOrderNo(outTradeNo);
response.setOrderId(order.getOrderId());
response.setOrderNo(order.getOrderNo());
response.setReturnUrl(order.getReturnUrl());
response.setBasiUrl(order.getBasiUrl());
response.setValidityDateNow(order.getValidityDateNow());
response.setValidityDateAfter(order.getValidityDateAfter());
response.setProductDescription(order.getProductDescription());
response.setMoney(order.getOrderMoney());
// 自己业务系统中已经成功或者事变 直接返回就行 没必要调微信接口
if (WxPayStatusEnum.PAY_SUCCESS.getPayStatusCode().equals(order.getOrderStatus()) || WxPayStatusEnum.PAY_FAIL.getPayStatusCode().equals(order.getOrderStatus()) || WxPayStatusEnum.CLOSED.getPayStatusCode().equals(order.getOrderStatus())) {
response.setOrderStatus(order.getOrderStatus());
} else {
// 这就是拿那个订单号去调微信的查询接口
WxPayOrderQueryResult result = wxPayService.queryOrder(null, outTradeNo);
if (ObjectUtil.isNotNull(result)) {
// 返回值
String tradeState = result.getTradeState();
String status = null;
// 判断
if (StrUtil.isNotBlank(tradeState)) {
if (WxPayConstants.NOT_PAY.equals(tradeState)) {
// status = WxPayStatusEnum.TO_BE_PAID.getPayStatusCode();
} else if (WxPayConstants.PAY_SUCCESS.equals(tradeState)) {
status = WxPayStatusEnum.PAY_SUCCESS.getPayStatusCode();
} else if (WxPayConstants.PAY_CLOSED.equals(tradeState)) {
status = WxPayStatusEnum.CLOSED.getPayStatusCode();
} else if (WxPayConstants.PAY_REFUND.equals(tradeState)) {
// status = WxPayStatusEnum.PAY_REFUND.getPayStatusCode();
} else if (WxPayConstants.PAY_REVOKED.equals(tradeState)) {
// status = WxPayStatusEnum.CANCEL.getPayStatusCode();
} else if (WxPayConstants.PAY_ING.equals(tradeState)) {
// status = WxPayStatusEnum.PAY_ING.getPayStatusCode();
} else if (WxPayConstants.PAY_ERROR.equals(tradeState)) {
// status = WxPayStatusEnum.PAY_FAIL.getPayStatusCode();
}
order.setOrderStatus(status);
response.setOrderStatusDesc(result.getTradeStateDesc());
}
if (StrUtil.isNotBlank(status)) {
updateById(order);
response.setOrderStatus(order.getOrderStatus());
// 发送通知给别的系统
sendNotifyToOtherSystem(order);
}
} else {
removeById(order.getOrderId());
}
}
} catch (WxPayException e) {
log.error("OrderServiceImpl--[queryOrder]===error---->{}", e.getMessage());
throw new CustomException("查询订单异常:" + e.getErrCodeDes());
} finally {
locker.unlock(outTradeNo);
}
return response;
}
- 关闭订单
--------------------controller--------------------
/**
* 根据订单no关闭订单
*
* @param closeRequest 请求对象
* @return result
*/
@ApiOperation("关闭订单")
@Log(title = "关闭订单", businessType = BusinessTypeEnum.UPDATE)
@PostMapping("/close")
@ApiTimeStatistics
public AjaxResult closeOrder(@RequestBody @Validated OrderCloseRequest closeRequest) {
WxPayOrderCloseResult result = orderService.closeOrder(closeRequest);
return AjaxResult.success(result);
}
--------------------dto--------------------
/**
* @author:kyrie
* @date:2023/5/17 16:25
* @Description: 请求对象
**/
@Data
public class OrderCloseRequest implements Serializable {
private static final long serialVersionUID = 2516981824084563768L;
@NotBlank(message = "订单号不能为空")
private String outTradeNo;
}
--------------------serviceImpl--------------------
/**
* 关闭订单
*
* @param closeRequest 关闭对象
* @return closeRequest
*/
@Override
public WxPayOrderCloseResult closeOrder(OrderCloseRequest closeRequest) {
WxPayOrderCloseResult result;
String outTradeNo = closeRequest.getOutTradeNo();
locker.lock(outTradeNo, Constants.REDIS_TIMEOUT);
try {
// 根据订单号查询订单
Order order = selectOrderByOrderNo(outTradeNo);
if (WxPayStatusEnum.PAY_SUCCESS.getPayStatusCode().equals(order.getOrderStatus())) {
throw WxPayException.newBuilder().returnMsg(outTradeNo + "订单已支付成功,不允许关闭!").build();
}
result = wxPayService.closeOrder(outTradeNo);
if (ObjectUtil.isNotNull(result)) {
order.setOrderStatus(WxPayStatusEnum.CLOSED.getPayStatusCode());
updateById(order);
}
} catch (WxPayException e) {
log.error("OrderServiceImpl--[{}]===error---->{}", "closeOrder", e.getMessage());
throw new CustomException("关闭订单异常:" + e.getReturnMsg());
} finally {
locker.unlock(outTradeNo);
}
return result;
}
- 支付后回调 (
支付后微信回通过回调地址调用我们自己的业务,需要给微信返回一个成功code和message 不然它会一直尝试调用 【好像会重试24h】
)
/**
* 微信支付回调处理
*
* @param paramsMap 参数
* @return 返回结果
*/
@PostMapping(value = "/notify", produces = "application/xml")
@ApiTimeStatistics
public Map<String, String> wxNotify(@RequestBody Map<String, String> paramsMap) {
orderService.wxNotify(paramsMap);
// 返回信息给微信
Map<String, String> resultMap = new HashMap<>(2);
resultMap.put("return_code", "SUCCESS");
resultMap.put("return_msg", "OK");
return resultMap;
}
--------------------serviceImpl--------------------
/**
* 微信支付后回调
*
* @param paramsMap 参数
*/
@Override
public void wxNotify(Map<String, String> paramsMap) {
log.info("【微信支付回调】参数--->{}", paramsMap);
// 获取订单号和支付金额
String orderNo = paramsMap.get("out_trade_no");
long totalFee = Long.parseLong(paramsMap.get("total_fee"));
locker.lock(orderNo, Constants.REDIS_TIMEOUT);
try {
// 根据订单号查询订单
Order order = selectOrderByOrderNo(orderNo);
// 判断支付金额是否一致
if (Long.parseLong(MoneyUtil.getMoney(order.getOrderMoney().toString())) != totalFee) {
log.error("【微信支付回调】支付金额不一致!");
throw new CustomException("支付金额不一致");
}
// 判断订单状态如果已经支付过了,无需支付了
if (WxPayStatusEnum.PAY_SUCCESS.getPayStatusCode().equals(order.getOrderStatus())) {
log.error("【微信支付回调】订单已经支付,无需重复支付!");
throw new CustomException("订单已经支付,无需重复支付");
}
// 判断订单状态
if (WxPayStatusEnum.PAY_FAIL.getPayStatusCode().equals(order.getOrderStatus())) {
log.error("【微信支付回调】订单已关闭,无发支付!");
throw new CustomException("订单已关闭,无发支付");
}
order.setOrderStatus(WxPayStatusEnum.PAY_SUCCESS.getPayStatusCode());
order.setPayTime(DateUtil.date());
updateById(order);
log.info("【微信支付回调】订单状态更新成功");
sendNotifyToOtherSystem(order);
} catch (Exception e) {
log.error("【微信支付回调】订单状态更新失败-->{}", e.getMessage());
} finally {
locker.unlock(orderNo);
}
}