在项目中使用微信、支付宝等第三方支付的时候,可能会遇到各种偶然性问题,本文将记录关于接入微信支付过程中出现的订单号重复的问题
微信支付官方文档: https://pay.weixin.qq.com/wiki/doc/api/index.html
常用的微信支付方式包括JSAPI支付、Native支付、APP支付、小程序支付等,对于商户而言,一般都有自己的订单系统,只是支付需要外接第三方,因此商户会有自己的订单编号,一个订单对应一个订单编号,那么会出现这样的场景,商家的用户在平台上下了一个订单,然后使用Native支付的方式提交过一次订单(调用微信的「统一下单接口」),但是未支付,然后再切换到APP支付/小程序支付,由于在微信支付系统中,一个商户的同一个订单号只能选择一种支付方式提交,当这个订单第二次使用不同的方式进行提交时便会出现「订单号重复」的错误
此时如果依旧使用原来的支付方式进行提交,则可以成功,但是切换到其他支付方式,就会返回「订单号重复」的错误
支付宝支付没有这种现象
将微信支付的每一种支付方式使用一个常量进行标识,在调用微信「统一下单」接口时,将商户自身系统的订单号后边追加支付方式标识,即:
微信「统一下单」订单号 = 商户订单号 + 微信支付方式标识
在微信支付回调时,将获取到的微信「统一下单」订单号还原到商户系统的订单号,然后再进行操作
这样,即使同一个订单通过不同的不同的微信支付方式进行提交,也不会再出现「订单号重复」的错误了(当然,每一次提交订单时也不要忘了校验订单是否已经支付/取消等等)
支付方式标识常量
./src/main/java/com/ljq/demo/pay/common/constant/PayTypeConst.java
package com.ljq.demo.pay.common.constant;
/**
* @Description: 支付方式常量
* @Author: junqiang.lu
* @Date: 2019/4/17
*/
public class PayTypeConst{
/**
* 支付方式
* 1 : 支付宝支付数字标识
* 11: 支付宝电脑网站支付
* 12: 支付宝手机网站支付
* 13: 支付宝 APP 支付
* AliPay: 支付宝支付文字说明
*
* 2: 微信支付标识
* 21: 微信 NATIVE 支付(二维码支付)
* 22: 微信 JSAPI 支付
* 23: 微信 H5 支付
* 24: 微信 APP 支付
* WxPay: 微信支付文字说明
*/
public static final int ORDER_PAY_TYPE_ALIPAY = 1;
public static final int ORDER_PAY_TYPE_ALIPAY_PC = 11;
public static final int ORDER_PAY_TYPE_ALIPAY_WAP = 12;
public static final int ORDER_PAY_TYPE_ALIPAY_APP = 13;
public static final String ORDER_PAY_TYPE_ALIPAY_NOTE = "AliPay";
public static final int ORDER_PAY_TYPE_WX = 2;
public static final int ORDER_PAY_TYPE_WX_NATIVE = 21;
public static final int ORDER_PAY_TYPE_WX_JSAPI = 22;
public static final int ORDER_PAY_TYPE_WX_H5 = 23;
public static final int ORDER_PAY_TYPE_WX_APP = 24;
public static final String ORDER_PAY_TYPE_WX_NOTE = "WxPay";
}
订单支付业务层示例
./src/main/java/com/ljq/demo/pay/service/impl/PayServiceImpl.java
package com.ljq.demo.pay.service.impl;
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import com.ljq.demo.pay.bean.PayBean;
import com.ljq.demo.pay.common.api.ApiResult;
import com.ljq.demo.pay.common.api.ResponseCode;
import com.ljq.demo.pay.common.constant.PayTypeConst;
import com.ljq.demo.pay.common.util.*;
import com.ljq.demo.pay.configure.AliPayConfig;
import com.ljq.demo.pay.configure.WxPayConfig;
import com.ljq.demo.pay.service.PayService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* @Description: 支付业务具体实现
* @Author: junqiang.lu
* @Date: 2018/7/10
*/
@Service("payService")
@Slf4j
public class PayServiceImpl implements PayService {
@Autowired
private WxPayConfig wxPayConfig;
@Autowired
private AliPayConfig aliPayConfig;
/**
* 创建支付订单
*
* @param payBean json 格式参数
* @return
*/
@Override
public ApiResult createPayOrder(PayBean payBean) throws Exception {
// 微信支付金额换算
int amountWxPay = CalculateUtil.multiply(Double.valueOf(payBean.getAmount()), 100, 2).intValue();
// 返回结果
Map<String, String> resultMap = new HashMap<>(16);
// 创建支付订单
switch (payBean.getPayType()) {
case PayTypeConst.ORDER_PAY_TYPE_ALIPAY_PC:
// 支付宝电脑网站支付
String aliPayPCForm = AliPayManager.createPCOrder(payBean.getOrderNo(),
String.valueOf(payBean.getAmount()), aliPayConfig);
if (!StringUtils.isEmpty(aliPayPCForm)) {
resultMap.put("prePayOrderInfo",aliPayPCForm);
return ApiResult.success(resultMap);
}
break;
case PayTypeConst.ORDER_PAY_TYPE_ALIPAY_WAP:
// 支付宝手机网站支付
String aliPayWapForm = AliPayManager.createWapOrder(payBean.getOrderNo(),
String.valueOf(payBean.getAmount()), aliPayConfig);
if (!StringUtils.isEmpty(aliPayWapForm)) {
resultMap.put("prePayOrderInfo",aliPayWapForm);
return ApiResult.success(resultMap);
}
break;
case PayTypeConst.ORDER_PAY_TYPE_ALIPAY_APP:
// 支付宝 APP 支付
String aliPayAppForm = AliPayManager.createAppOrder(payBean.getOrderNo(),
String.valueOf(payBean.getAmount()), aliPayConfig);
if (!StringUtils.isEmpty(aliPayAppForm)) {
resultMap.put("prePayOrderInfo",aliPayAppForm);
return ApiResult.success(resultMap);
}
break;
case PayTypeConst.ORDER_PAY_TYPE_WX_NATIVE:
// 微信 NATIVE 支付(二维码)
Map<String,String> wxPayNativeMap = WxPayManager.createNativeOrder(wxPayConfig,
payBean.getOrderNo() + PayTypeConst.ORDER_PAY_TYPE_WX_NATIVE,
amountWxPay, payBean.getIp());
if (wxPayNativeMap != null &&
Objects.equals(wxPayNativeMap.get("pre_pay_order_status"), wxPayConfig.getResponseSuccess())) {
resultMap.put("prePayOrderInfo",wxPayNativeMap.get("code_url"));
return ApiResult.success(resultMap);
}
break;
case PayTypeConst.ORDER_PAY_TYPE_WX_JSAPI:
// 微信 JsAPI 支付(公众号)
if (StringUtils.isEmpty(payBean.getOpenId())) {
return ApiResult.failure(ResponseCode.PAY_SUBMIT_ERROR);
}
Map<String, String> wxPayJsAPIMap = WxPayManager.createJsAPIOrder(wxPayConfig,
payBean.getOrderNo() + PayTypeConst.ORDER_PAY_TYPE_WX_JSAPI,
amountWxPay, payBean.getIp(), payBean.getOpenId());
if (wxPayJsAPIMap != null &&
Objects.equals(wxPayJsAPIMap.get("pre_pay_order_status"), wxPayConfig.getResponseSuccess())) {
return ApiResult.success(wxPayJsAPIMap);
}
break;
case PayTypeConst.ORDER_PAY_TYPE_WX_H5:
// 微信 H5 支付
Map<String, String> wxPayH5Map = WxPayManager.createH5Order(wxPayConfig,
payBean.getOrderNo() + PayTypeConst.ORDER_PAY_TYPE_WX_H5,
amountWxPay, payBean.getIp());
if (wxPayH5Map != null &&
Objects.equals(wxPayH5Map.get("pre_pay_order_status"), wxPayConfig.getResponseSuccess())) {
resultMap.put("prePayOrderInfo",wxPayH5Map.get("mweb_url"));
return ApiResult.success(resultMap);
}
break;
case PayTypeConst.ORDER_PAY_TYPE_WX_APP:
// 微信 APP 支付
Map<String, String> wxPayAppMap = WxPayManager.createAppOrder(wxPayConfig,
payBean.getOrderNo() + PayTypeConst.ORDER_PAY_TYPE_WX_APP,
amountWxPay, payBean.getIp());
if (wxPayAppMap != null &&
Objects.equals(wxPayAppMap.get("pre_pay_order_status"), wxPayConfig.getResponseSuccess())) {
return ApiResult.success(wxPayAppMap);
}
break;
case PayTypeConst.ORDER_PAY_TYPE_WX_MINI:
// 微信 小程序 支付
if (StringUtils.isEmpty(payBean.getOpenId())) {
return ApiResult.failure(ResponseCode.PAY_SUBMIT_ERROR);
}
Map<String, String> wxPayMiniMap = WxPayManager.createJsAPIOrder(wxPayConfig,
payBean.getOrderNo() + PayTypeConst.ORDER_PAY_TYPE_WX_MINI,
amountWxPay, payBean.getIp(), payBean.getOpenId());
if (wxPayMiniMap != null &&
Objects.equals(wxPayMiniMap.get("pre_pay_order_status"), wxPayConfig.getResponseSuccess())) {
return ApiResult.success(wxPayMiniMap);
}
break;
default:
return ApiResult.failure(ResponseCode.PAY_TYPE_ERROR);
}
return ApiResult.failure(ResponseCode.PAY_SUBMIT_ERROR);
}
/**
* (主动)获取支付结果
*
* @param payBean 订单信息(json 格式参数)
* @return
*/
@Override
public ApiResult getPayResult(PayBean payBean) throws Exception {
// 返回结果
Map<String, String> resultMap;
switch (payBean.getPayType()) {
case PayTypeConst.ORDER_PAY_TYPE_ALIPAY_PC:
case PayTypeConst.ORDER_PAY_TYPE_ALIPAY_WAP:
case PayTypeConst.ORDER_PAY_TYPE_ALIPAY_APP:
resultMap = AliPayManager.getPayResult(aliPayConfig, payBean.getOrderNo());
break;
case PayTypeConst.ORDER_PAY_TYPE_WX_NATIVE:
case PayTypeConst.ORDER_PAY_TYPE_WX_JSAPI:
case PayTypeConst.ORDER_PAY_TYPE_WX_H5:
case PayTypeConst.ORDER_PAY_TYPE_WX_APP:
case PayTypeConst.ORDER_PAY_TYPE_WX_MINI:
resultMap = WxPayManager.getPayResult(wxPayConfig, payBean.getOrderNo() + payBean.getPayType());
break;
default:
return ApiResult.failure(ResponseCode.PAY_TYPE_ERROR);
}
if (MapUtil.isEmpty(resultMap)) {
return ApiResult.failure(ResponseCode.PAY_STATUS_ERROR);
}
return ApiResult.success(resultMap);
}
/**
* 微信支付结果通知
*
* @param request 微信支付回调请求
* @return 支付结果
*/
@Override
public String wxPayNotify(HttpServletRequest request) {
String result = null;
try {
InputStream inputStream = request.getInputStream();
/**
* 读取通知参数
*/
String strXML = FileUtil.getStringFromStream(inputStream);
Map<String,String> reqMap = MapUtil.xml2Map(strXML);
if(MapUtil.isEmpty(reqMap)){
log.warn("request param is null");
return wxPayConfig.getResponseFail();
}
/**
* 校验签名
*/
if(!SignUtil.signValidate(reqMap, wxPayConfig.getKey(), wxPayConfig.getFieldSign())){
log.warn("wxPay sign is error");
return wxPayConfig.getResponseFail();
}
String orderNo = reqMap.get("out_trade_no").substring(0,reqMap.get("out_trade_no").length()-2);
log.debug("微信支付回调,订单编号: {}", orderNo);
// TODO 其他业务处理
Map<String, String> resultMap = new HashMap<>(16);
resultMap.put("return_code",wxPayConfig.getResponseSuccess());
resultMap.put("return_msg","OK");
result = MapUtil.map2Xml(resultMap);
} catch (IOException e) {
log.error("get request inputStream error",e);
return wxPayConfig.getResponseFail();
} catch (Exception e) {
log.error("resolve request param error",e);
return wxPayConfig.getResponseFail();
}
return result;
}
/**
* 支付宝支付结果通知
*
* @param request 支付宝回调请求
* @return
*/
@Override
public String aliPayNotify(HttpServletRequest request) {
// 读取通知参数
Map<String, String> params = AliPayManager.getNotifyParams(request.getParameterMap());
if(MapUtil.isEmpty(params)){
return aliPayConfig.getResponseFail();
}
try {
// 签名校验
if(!AlipaySignature.rsaCheckV1(params, aliPayConfig.getAlipayPublicKey(),
aliPayConfig.getCharset(), aliPayConfig.getSignType())){
return aliPayConfig.getResponseFail();
}
String orderNo = params.get("out_trade_no");
log.debug("支付宝回调,订单编号: {}",orderNo);
// TODO 其他业务处理
} catch (AlipayApiException e) {
log.error("支付宝回调验证失败",e);
return aliPayConfig.getResponseFail();
}
return aliPayConfig.getResponseSuccess();
}
/**
* 支付宝支付同步通知返回地址
* @param request
* @return
*/
@Override
public String aliPayReturnUrl(HttpServletRequest request) {
// 读取通知参数
Map<String, String> params = AliPayManager.getNotifyParams(request.getParameterMap());
if(MapUtil.isEmpty(params)){
return "alipay_fail_url";
}
try {
// 签名校验
if(!AlipaySignature.rsaCheckV1(params, aliPayConfig.getAlipayPublicKey(),
aliPayConfig.getCharset(), aliPayConfig.getSignType())){
return "alipay_fail_url";
}
} catch (AlipayApiException e) {
log.error("支付宝回调验证失败",e);
return aliPayConfig.getResponseFail();
}
return "alipay_success_url";
}
}
GitHub项目源码: https://github.com/Flying9001/pay
个人公众号:404Code,记录半个互联网人的技术与思考,感兴趣的可以关注.