一、前期准备工作:
上篇博客配置的一些参数和文件Jar包 都要用到
微信支付需要小程序和商户绑定
APP绑定微信商户平台获取商户id(mchID)、
证书(商户后台下载)、
支付签名密钥(商户后台设置api密钥)、
退款签名密钥(商户后台设置api密钥ipv3)等
可以去微信官方文档那里查看怎么获取生成后台所需要的参数
官方文档:
支付异步回调通知:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7&index=8
退款异步回调通知:https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_16&index=11
二、支付流程:
个人理解:
微信支付需要提供三个接口 :
第一步是你的创建订单的动作
第二步前端确认用户支付的情况下 通过订单ID查询生成调用微信支付的参数 执行扣款动作
第三步微信支付成功后会自动调你提供给微信的回调地址
在回调操作里面修改订单状态 完成你支付成功后的后续动作。
PS:
支付退款的回调地址 必须是外网可以访问的
微信支付金额是分 支付宝是元
三、微信支付详细代码案例:
日期工具类:
import cn.njcool.common.enums.DateEnum;
import cn.njcool.common.enums.NumberEnum;
import org.apache.commons.lang3.StringUtils;
import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalField;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.List;
/**
* @Name: DateUtil
* @Author: zhu
* @Date: 2020-02-07
* @Description: 日期时间处理工具类
*/
public class DateUtil {
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter
.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter NO_SPACE_DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
private static final DateTimeFormatter DATE_TIME_WITH_HMS_FORMAT = DateTimeFormatter
.ofPattern("HH:mm:ss");
public static String formatDateTime(LocalDateTime date, DateEnum dateEnum) {
if (date != null) {
switch (dateEnum) {
case DATE: {
return DATE_FORMAT.format(date);
}
case DATE_TIME: {
return DATE_TIME_FORMAT.format(date);
}
case NO_SPACE_DATE_TIME: {
return NO_SPACE_DATE_TIME_FORMAT.format(date);
}
case TIME_WITH_HMS: {
return DATE_TIME_WITH_HMS_FORMAT.format(date);
}
}
}
return null;
}
public static LocalDateTime formatDateTime(String dateStr) {
if (StringUtils.isNotEmpty(dateStr)) {
if (dateStr.length() == DateEnum.DATE.getValue()) {
dateStr += " 00:00:00";
return LocalDateTime.parse(dateStr, DATE_TIME_FORMAT);
} else if (dateStr.length() == DateEnum.DATE_TIME.getValue()) {
return LocalDateTime.parse(dateStr, DATE_TIME_FORMAT);
} else if (dateStr.length() == DateEnum.NO_SPACE_DATE_TIME.getValue()) {
return LocalDateTime.parse(dateStr, NO_SPACE_DATE_TIME_FORMAT);
} else if (dateStr.length() == DateEnum.TIME.getValue()) {
dateStr = "2020-01-01 " + dateStr + ":00";
return LocalDateTime.parse(dateStr, DATE_TIME_FORMAT);
}
}
return null;
}
public static List<String> getWeekDateList(int week, String endDate, List<String> excludeDateList) {
LocalDateTime now = LocalDateTime.now();
List<String> resultList = new ArrayList<>();
TemporalField temporalField = WeekFields.of(DayOfWeek.of(1), 1).dayOfWeek();
LocalDateTime localDateTime = now.with(temporalField, week);
if (localDateTime.isBefore(now)) {
localDateTime = localDateTime.plusWeeks(1);
}
while (true) {
String localDateTimeStr = formatDateTime(localDateTime, DateEnum.DATE);
if (localDateTimeStr.compareTo(endDate) > NumberEnum.ZERO.getValue()) {
break;
}
if (!excludeDateList.contains(localDateTimeStr)) {
resultList.add(localDateTimeStr + "_" + week);
}
localDateTime = localDateTime.plusWeeks(1);
}
return resultList;
}
}
枚举工具 两个:
日期:
/**
* @Name: DateEnum
* @Author: zhu
* @Date: 2020-12-07
* @Description:
*/
public enum DateEnum {
// yyyy-MM-dd
DATE(10),
// yyyy-MM-dd HH:mm:ss
DATE_TIME(19),
// yyyyMMddHHmmss
NO_SPACE_DATE_TIME(14),
// HH:mm
TIME(5),
// HH:mm:ss
TIME_WITH_HMS(8);
private final int value;
DateEnum(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
数字:
/**
* @Name: NumberEnum
* @Author: zhu
* @Date: 2021-01-2021/1/9
* @Description: 数字枚举
*/
public enum NumberEnum {
ONE(1),
TWO(2),
THREE(3),
FOUR(4),
FIVE(5),
SIX(6),
SEVEN(7),
EIGHT(8),
NINE(9),
TEN(10),
ELEVEN(11),
TWELVE(12),
THIRTEEN(13),
FOURTEEN(14),
FIFTEEN(15),
SIXTEEN(16),
SEVENTEEN(17),
EIGHTEEN(18),
TWENTY(20),
THIRTY_ONE(31),
THIRTY_TWO(32),
HUNDRED(100),
FIVE_HUNDRED(500),
ZERO(0);
private final int value;
NumberEnum(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
支付代码:
@ApiOperation(value = "支付")
@ApiOperationSupport(ignoreParameters = {
"limit", "offset", "paramError" })
@PostMapping("pay")
public BaseResponse<WxPayOrderResponse> orderPay(@RequestBody OrderPayRequest payRequest,
HttpServletRequest request) {
return wxPayOrderService.orderPay(payRequest, request);
}
@Autowired
private WxPayService wxPayService;
/**
* 根据订单编号调用微信支付
*
* @param payRequest 支付参数
* @param request http请求体
* @return WxPayOrderResponse
*/
@Override
public BaseResponse<WxPayOrderResponse> orderPay(OrderPayRequest payRequest, HttpServletRequest request) {
BaseResponse<WxPayOrderResponse> response = new BaseResponse<>();
// 校验入参
if (Objects.isNull(request) || payRequest.isParamError()) {
log.info("[WxPayOrderServiceImpl-orderPay] 入参校验错误");
response.fail(ResponseEnum.PARAM_VALIDATE_ERROR);
return response;
}
//查询订单信息
List<OrderDmo> orderDtoList = listByIds(payRequest.getOrderIdList());
//支付名称
String body = "xx服务套餐";
//订单总金额
BigDecimal priceCount = orderDtoList.stream().map(OrderDmo::getPrice).reduce(BigDecimal.ZERO, BigDecimal::add);
log.info("WxPay总金额为:{}", priceCount);
//生成微信请求订单号
String orderNo = UUIDUtil.generateUUID();
//元转成分
Integer price = BaseWxPayRequest.yuanToFen(String.valueOf(priceCount));
//用户真实ip
String ipAddress = getIpAddress(request);
//回调附加数据 可以自定义回调数据 我这里是将订单表主键ID带过去了
String attach = orderDtoList.stream()
.map(OrderDmo::getId)
.map(String::valueOf)
.collect(Collectors.joining(","));
log.info("回调附加数据={}", attach);
log.info("根据订单编号调用微信支付 OpenId = {}", payRequest.getOpenId());
WxPayMpOrderResult wxPayMpOrderResult = wxPay(body, orderNo, price, payRequest.getOpenId(), ipAddress, attach);
if (ObjectUtils.isEmpty(wxPayMpOrderResult)) {
log.info("[WxPayOrderServiceImpl-orderPay]微信支付失败");
response.fail(ResponseEnum.ORDER_PAY_FAIL);
return response;
}
WxPayOrderResponse result = BeanUtil.toBean(wxPayMpOrderResult, WxPayOrderResponse.class);
log.info("微信支付成功返回信息:{}", result);
response.setData(new ResultData<>(result));
return response;
}
/**
* @param body 支付名称
* @param orderNo 订单编号
* @param price 订单价格
* @param openid 用户openid
* @param ip 用户ip
* @param attach 附加数据
* @return WxPayMpOrderResult
*/
private WxPayMpOrderResult wxPay(String body, String orderNo, Integer price, String openid, String ip,
String attach) {
WxPayUnifiedOrderRequest orderRequest = new WxPayUnifiedOrderRequest();
orderRequest.setBody(body);
orderRequest.setOutTradeNo(orderNo);
//分
orderRequest.setTotalFee(price);
orderRequest.setOpenid(openid);
orderRequest.setSpbillCreateIp(ip);
LocalDateTime now = LocalDateTime.now();
orderRequest.setTimeStart(DateUtil.formatDateTime(now, DateEnum.NO_SPACE_DATE_TIME));
LocalDateTime plus = now.plus(30, ChronoUnit.MINUTES);
orderRequest.setTimeExpire(DateUtil.formatDateTime(plus, DateEnum.NO_SPACE_DATE_TIME));
orderRequest.setAttach(attach);
log.info("请求微信支付参数为:{}", orderRequest);
try {
return wxPayService.createOrder(orderRequest);
} catch (WxPayException e) {
log.error("[WxPayBusinessService-wxPay]支付创建订单失败!订单号:{}, 原因:{}", orderNo, e.getMessage());
return null;
}
}
工具类:
/**
* 获取微信下单时 用户真实Ip地址
*
* @param request HttpServletRequest
* @return iP
*/
public String getIpAddress(HttpServletRequest request) {
String ip = null;
//X-Forwarded-For:Squid 服务代理
String ipAddresses = request.getHeader("X-Forwarded-For");
if (StringUtils.isEmpty(ipAddresses) || "unknown".equalsIgnoreCase(ipAddresses)) {
//Proxy-Client-IP:apache 服务代理
ipAddresses = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isEmpty(ipAddresses) || "unknown".equalsIgnoreCase(ipAddresses)) {
//WL-Proxy-Client-IP:weblogic 服务代理
ipAddresses = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isEmpty(ipAddresses) || "unknown".equalsIgnoreCase(ipAddresses)) {
//HTTP_CLIENT_IP:有些代理服务器
ipAddresses = request.getHeader("HTTP_CLIENT_IP");
}
if (StringUtils.isEmpty(ipAddresses) || "unknown".equalsIgnoreCase(ipAddresses)) {
//X-Real-IP:nginx服务代理
ipAddresses = request.getHeader("X-Real-IP");
}
//有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP
if (StringUtils.isNotEmpty(ipAddresses)) {
ip = ipAddresses.split(",")[0];
}
//还是不能获取到,最后再通过request.getRemoteAddr();获取
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
ip = request.getRemoteAddr();
}
return ip;
}
微信回调接口:
@Slf4j
@RestController
@RequestMapping("wxPay")
public class WxPayNotifyController {
private final WxPayNotifyService wxPayNotifyService;
public WxPayNotifyController(WxPayNotifyService wxPayNotifyService) {
this.wxPayNotifyService = wxPayNotifyService;
}
@RequestMapping("payNotify")
public String payNotify(HttpServletRequest request) {
log.info("[<<<<< 微信支付回调开始 >>>>>]");
String response;
try {
response = wxPayNotifyService.payNotify(request);
} catch (Exception e) {
log.info("微信支付回调Error= {}", e.getMessage());
response = "WxCallback Exception";
}
log.info("[WxPayNotifyController-payNotify]支付回调Result= {}", response);
log.info("[<<<<< 微信支付回调结束 >>>>>]");
return response;
}
}
业务层:
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.service.WxPayService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* @author zhu
* @date 2021/9/15 16:13
*/
@Slf4j
@Service
public class WxPayNotifyServiceImpl implements WxPayNotifyService {
private final WxPayService wxPayService;
/**
* 微信支付回调接口
*
* @param request HttpServletRequest
* @return 日志参数信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public String payNotify(HttpServletRequest request) throws Exception {
WxPayOrderNotifyResult result;
try {
String xmlResult = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding());
result = wxPayService.parseOrderNotifyResult(xmlResult);
log.info("微信支付回调请求参数= {}", result);
} catch (Exception e) {
log.error("[WxPayNotifyService-payNotify]微信回调结果异常: {}", e.getMessage());
return WxPayNotifyResponse.fail("回调操作失败" + e);
}
return updateOrderState(result);
}
String updateOrderState(WxPayOrderNotifyResult result) throws Exception {
//获取微信回调订单编号修改状态
String attach = result.getAttach();
//获取回调数据
List<String> orderIdList = Arrays.stream(attach.split(",")).collect(Collectors.toList());
if (CollectionUtils.isEmpty(orderIdList)) {
return WxPayNotifyResponse.fail("订单ID不存在");
}
List<OrderDmo> orderDtoList = wxPayOrderService.listByIds(orderIdList);
log.info("修改订单状态orderDtoList={}", orderDtoList);
boolean orderState = wxPayOrderService.updateBatchById(orderDtoList);
log.info("修改订单状态={}", orderState);
//修改订单支付状态
if (!orderState) {
log.error("[WxPayNotifyService-payNotify]微信回调 修改订单状态失败");
throw new Exception("微信回调 修改订单状态失败");
}
//..........个人支付回调业务处理
}
return WxPayNotifyResponse.success("处理成功");
}
}
四、微信退款案例代码:
@ApiOperation(value = "退款")
@ApiOperationSupport(ignoreParameters = {
"limit", "offset", "paramError"})
@PostMapping("refund")
public BaseResponse<Object> refund(@RequestBody OrderRefundRequest refundRequest, HttpServletRequest request) {
BaseResponse<Object> response;
try {
response = wxPayBusinessService.refund(refundRequest, request);
} catch (Exception e) {
response = new BaseResponse<>();
response.fail(ResponseEnum.ORDER_REFUND_FAIL);
}
return response;
}
业务层:
@Transactional(rollbackFor = Exception.class)
public BaseResponse<Object> refund(OrderRefundRequest refundRequest, HttpServletRequest request) throws Exception {
BaseResponse<Object> response = new BaseResponse<>();
// 校验入参
if (refundRequest == null || refundRequest.isParamError()) {
log.info("[WxPayBusinessService-refund] 入参校验错误");
response.fail(ResponseEnum.PARAM_VALIDATE_ERROR);
return response;
}
WxPayRefundResult wxPayRefundResult = getWxPayRefundResult(refundRequest, request);
if (ObjectUtils.isEmpty(wxPayRefundResult)) {
throw new Exception("订单退款失败");
}
return response;
}
WxPayRefundResult getWxPayRefundResult(OrderRefundRequest refundRequest, HttpServletRequest request) throws Exception {
//修改订单状态为退款中
if (transactionDao.updateOrderStatusById(refundRequest.getId(), NumberEnum.FOUR.getValue()) != NumberEnum.ONE.getValue()) {
log.info("[WxPayBusinessService-refund]修改订单状态失败");
throw new Exception("订单退款失败");
}
String orderNo = refundRequest.getOrderNo();
Integer price = refundRequest.getPrice().scaleByPowerOfTen(2).intValue();
//退款
WxPayRefundResult wxPayRefundResult;
try {
wxPayRefundResult = wxRefund(orderNo, price);
} catch (WxPayException e) {
log.error("[WxPayBusinessService-refund]退款失败!订单号: {}, 原因: {}", orderNo, e.getMessage());
return null;
}
return wxPayRefundResult;
}
public WxPayRefundResult wxRefund(String orderNo, Integer price) throws WxPayException {
WxPayRefundRequest refundRequest = new WxPayRefundRequest();
//商户订单号
refundRequest.setOutTradeNo(orderNo);
//商户退款订单号
refundRequest.setOutRefundNo(UUIDUtil.generateUUID());
//订单金额
refundRequest.setTotalFee(price);
//退款金额
refundRequest.setRefundFee(price);
//商户号
refundRequest.setOpUserId(wxConfig.wxMchId);
//微信支付退款结果通知的回调地址
refundRequest.setNotifyUrl(wxConfig.refundNotifyUrl);
return wxPayService.refund(refundRequest);
}
微信退款回调接口:
@Slf4j
@RestController
@RequestMapping("wxRefund")
public class WxRefundNotifyController {
@RequestMapping("refundNotify")
public String refundNotify(HttpServletRequest request) {
log.info("[======微信退款回调开始======]");
String response;
try {
response = wxRefundNotifyService.refundNotify(request);
} catch (Exception e) {
response = "callback exception";
}
log.info("[WxPayNotifyController-refundNotify]支付回调: {}", response);
log.info("[======微信退款回调结束======]");
return response;
}
}
业务层:
@Transactional(rollbackFor = Exception.class)
public String refundNotify(HttpServletRequest request) throws Exception {
WxPayRefundNotifyResult result;
try {
String xmlResult = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding());
result = wxPayService.parseRefundNotifyResult(xmlResult);
} catch (Exception e) {
log.error("[WxRefundNotifyService-refundNotify]微信退款回调结果异常", e);
return WxPayNotifyResponse.fail("回调操作失败");
}
if (ObjectUtils.isNotEmpty(result)) {
return callback(result);
} else {
log.info("[WxRefundNotifyService-refundNotify result is Empty]");
return WxPayNotifyResponse.fail("回调操作失败");
}
}
private String callback(WxPayRefundNotifyResult result) throws Exception {
//获取加密信息字段解密后的内容
WxPayRefundNotifyResult.ReqInfo reqInfo = result.getReqInfo();
//商家订单号
String outTradeNo = reqInfo.getOutTradeNo();
//商家退款订单号
String outRefundNo = reqInfo.getOutRefundNo();
//个人业务代码
//修改订单状态
//钱包扣除金额
return WxPayNotifyResponse.success("处理成功!");
} else {
return WxPayNotifyResponse.fail("未查询到订单");
}
}
总结: 案例Demo 省去了很多代码 大家可以去看Wx开发工具包提供的接口 里面都有很详细的介绍
结合你当前项目的需求进行开发即可
附上学习途径:
找到对应的Wx开发工具包提供的Api接口 看对接参数是否必传
线上排查时 只要你的日志打印的好 多测几遍就可以成功了 也可以看看官方的文档报错信息
微信返回过来的参数都是XMl格式的 报错直接百度搜错误Code即可 解决办法一大堆