com.thoughtworks.xstream
xstream
1.4.11.1
cn.hutool
hutool-all
5.3.4
下面我们开始写一个微信小程序支付的demo
1.新建三个对象用于接收微信返回xml报文的解析内容
①.WxPayNotifyReq
/**
* 支付结果通知报文
* @author wuhx 2020-05-14 11:55:56
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class WxPayNotifyReq {
@XStreamAlias("appid")
private String appId;
@XStreamAlias("bank_type")
private String bankType;
@XStreamAlias("cash_fee")
private float cashFee;
@XStreamAlias("fee_type")
private String feeType;
@XStreamAlias("is_subscribe")
private String isSubscribe;
@XStreamAlias("mch_id")
private String mchId;
@XStreamAlias("nonce_str")
private String nonceStr;
@XStreamAlias("openid")
private String openId;
@XStreamAlias("out_trade_no")
private String outTradeNo;
@XStreamAlias("return_code")
private String resultCode;
@XStreamAlias("result_code")
private String returnCode;
@XStreamAlias("sign")
private String sign;
@XStreamAlias("time_end")
private String timeEnd;
@XStreamAlias("total_fee")
private String totalFee;
@XStreamAlias("trade_type")
private String tradeType;
@XStreamAlias("transaction_id")
private String transactionId;
}
②WxPayNotifyResp
/**
* 支付异步通知报文
* @author wuhx 2020-05-14 13:54:50
*/
@Data
public class WxPayNotifyResp {
private String returnCode;
private String returnMsg;
}
③WxPayResp
/**
* 解析预支付(统一下单接口)
* 具体字段参数见 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1&index=1
* XStreamAlias的原因,解析XML的参数只能多不能少
* @author wuhx 2020-05-14 15:46:37
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@XStreamAlias("xml")
public class WxPayResp {
@XStreamAlias("err_code")
private String errCode;
@XStreamAlias("err_code_des")
private String errCodeDes;
@XStreamAlias("code_url")
private String codeUrl;
@XStreamAlias("device_info")
private String deviceInfo;
@XStreamAlias("openid")
private String openId;
@XStreamAlias("result_code")
private String resultCode;
@XStreamAlias("return_code")
private String returnCode;
@XStreamAlias("return_msg")
private String returnMsg;
/**
* 随机字符串
*/
@XStreamAlias("nonce_str")
private String noticeStr;
/**
* 预支付交易会话标识
*/
@XStreamAlias("prepay_id")
private String prepayId;
@XStreamAlias("sign")
private String sign;
@XStreamAlias("appid")
private String appId;
@XStreamAlias("mch_id")
private String mchId;
/**
* 交易类型
*/
@XStreamAlias("trade_type")
private String tradeType;
}
2.编写配置文件信息
3.自定义异常模块(用自己的也行,只要能捕抓到异常)
/**
* @author wuhx 2020-05-12 13:48:44
*/
public class BaseError {
public enum ErrorCodeEnum{
SYS_ERROR("10001","系统错误,请重试"),
UNKNOWN_ERROR("10002","未知异常,请重试"),
/*用户错误*/
USER_NOT_LOGGED_IN("20001", "用户未登录,请先登录"),
USER_LOGIN_ERROR("20002", "账号不存在或密码错误"),
/*HTTP异常*/
HTTP_SEND_GET_ERROR("30001","发送GET请求失败"),
HTTP_SEND_POST_ERROR("30002","发送POST请求失败"),
/* 权限错误:70001-79999 */
PERMISSION_UNAUTHENTICATED("70001","此操作需要登陆系统!"),
PERMISSION_UNAUTHORISE("70002","权限不足,无权操作!"),
PERMISSION_EXPIRE("70003","登录状态过期!"),
PERMISSION_TOKEN_EXPIRED("70004", "token已过期"),
PERMISSION_LIMIT("70005", "访问次数受限制"),
PERMISSION_TOKEN_INVALID("70006", "无效token"),
PERMISSION_SIGNATURE_ERROR("70007", "签名失败"),
PERMISSION_TOKEN_NULL("70008","token为空"),
ILLEGAL_ARGS("70009","参数异常,请检查参数"),
/*微信返回的错误 80001-89999*/
WX_MSG_ERROR("80001","获取微信信息异常"),
WX_SYS_ERROR("80002","系统繁忙"),
WX_PAY_SENDXML_ERROR("80003","报文发送失败"),
WX_PAY_READRESP_ERROR("80004","解析响应内容失败"),
WX_PAY_ERROR("80005","微信支付请求失败"),
WX_PAY_SIGN_ERROR("80006","签名失败"),
/*XML错误 90001-99999*/
XML_PARSE_ERROR("90001","XML解析错误")
;
private String code;
private String desc;
public String getCode() {
return code;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
ErrorCodeEnum(String code,String desc){
this.code = code;
this.desc = desc;
}
public static ErrorCodeEnum getEnumByCode(String code){
for (ErrorCodeEnum result : values()){
if (StrUtil.equals(result.getCode(),code)){
return result;
}
}
return null;
}
}
}
最后调用就行了
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.XmlUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.HttpUtil;
import com.thoughtworks.xstream.XStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import zhiyan.applets.base.execption.BaseError;
import zhiyan.applets.base.execption.HttpException;
import zhiyan.applets.base.execption.WxPayException;
import zhiyan.applets.base.execption.XmlException;
import zhiyan.applets.base.utils.LogUtils;
import zhiyan.applets.entity.HttpResult;
import zhiyan.applets.tencent.login.model.WxInfo;
import zhiyan.applets.tencent.pay.model.WxPayResp;
import java.util.*;
/**
* @author wuhx 2020-05-14 14:01:34
*/
public class WxPay {
private static final Logger log = LoggerFactory.getLogger(WxPay.class);
/**
* 下单地址
*/
private static final String URL_UNIFIED_ORDER = "https://api.mch.weixin.qq.com/pay/unifiedorder";
private static final String SUCCESS = "SUCCESS";
/**
* 加密方式 默认 md5
*/
private static final String SIGN_TYPE = "MD5";
private final XStream xStream;
public WxPay(XStream xStream) {
this.xStream = xStream;
}
/**
* 下单(预支付)
* @param orderNumber 订单编号
* @param payMoney 金额 单位分
* @param body 描述
* @param openId 用户openid
* @param notifyUrl 异步回调地址(配合内网穿透使用)
* @param ip ip地址
* @param wxInfo 微信小程序配置信息
* @return wxPayResp
*/
public WxPayResp unifiedOrder(String orderNumber, Double payMoney, String body, String openId, String notifyUrl,String ip, WxInfo wxInfo){
Map map = new HashMap<>(11);
map.put("appid",wxInfo.getAppId());
map.put("mch_id", wxInfo.getMchId());
map.put("nonce_str", RandomUtil.randomString(16));
map.put("body",wxInfo.getPayTitle()+body);
map.put("out_trade_no", orderNumber);
map.put("total_fee", StrUtil.toString(payMoney));
map.put("spbill_create_ip",ip);
map.put("notify_url",notifyUrl);
map.put("trade_type","JSAPI");
map.put("sign",sign(map,wxInfo.getAppKey()));
try {
String result = HttpUtil.post(URL_UNIFIED_ORDER, XmlUtil.mapToXmlStr(map));
return (WxPayResp) xStream.fromXML(result);
}catch (WxPayException e){
log.error(LogUtils.text(e));
throw new WxPayException(BaseError.ErrorCodeEnum.WX_PAY_ERROR);
}catch (XmlException e){
log.error(LogUtils.text(e));
throw new XmlException(BaseError.ErrorCodeEnum.XML_PARSE_ERROR);
}catch (HttpException e){
log.error(LogUtils.text(e));
throw new HttpException(BaseError.ErrorCodeEnum.HTTP_SEND_POST_ERROR);
}
}
/**
* 支付
* @param wxPayResp
* @return
*/
public HttpResult wxPay(WxPayResp wxPayResp){
Map resMap = new HashMap<>();
if (StrUtil.equalsIgnoreCase(wxPayResp.getReturnCode(),SUCCESS)){
//注意 这个I 要大写
resMap.put("appId",wxPayResp.getAppId());
resMap.put("nonceStr",wxPayResp.getNoticeStr());
resMap.put("package","prepay_id="+wxPayResp.getPrepayId());
resMap.put("signType",SIGN_TYPE);
resMap.put("timeStamp", System.currentTimeMillis());
resMap.put("paySign",wxPayResp.getSign());
return HttpResult.mapSuccess(resMap);
}
return HttpResult.error("微信请求支付失败");
}
/**
* 按照微信规定格式签名
*
* @author lingting 2019-08-06 15:23:54
*/
private static String sign(Map map,String appKey) {
// 对参数的 key 按照指定规则排序
String[] keys = asciiSort(map.keySet());
// 按照排序后的key拼接成字符串
String mapString = join(map, keys,appKey);
// 获取签名
String sign = SecureUtil.md5(mapString).toUpperCase();
if (StrUtil.isNotEmpty(sign)) {
return sign;
} else {
throw new WxPayException(BaseError.ErrorCodeEnum.WX_PAY_SIGN_ERROR);
}
}
private static String join(Map map, String[] keys,String appKey) {
StringBuilder str = new StringBuilder();
for (String key : keys) {
str.append(key).append("=").append(map.get(key)).append("&");
}
// 拼接秘钥
str.append("key=").append(appKey);
return str.toString();
}
/**
* 按照 首字符 ASCII 码排序
*
* @author wuhx 2020-05-14 14:37
*/
public static String[] asciiSort(Set set) {
return asciiSort(set.toArray(new String[0]));
}
public static String[] asciiSort(String[] keys) {
List list = new ArrayList<>(Arrays.asList(keys));
Collections.sort(list);
return list.toArray(new String[0]);
}
}
接下来最后一步就是写异步回调地址,如果线上测试的话不需要内网穿透,本地测试的话要有内网穿透。并且这个接口不能有任何的权限(POST),然后根据解析的WxPayNotifyReq 内容,处理你的
业务逻辑就行了。
/**
* 回调处理
* @param request
* @param response
* @return
* @throws IOException
*/
public WxPayNotifyReq asyNotice(HttpServletRequest request, HttpServletResponse response) throws IOException {
try (PrintWriter writer = response.getWriter()) {
String result = parseRequest(request);
log.info(LogUtils.text("通知报文:" + result));
//结果返回,微信要求如果成功需要返回return_code "SUCCESS"
return (WxPayNotifyReq) xStream.fromXML(result);
} catch (WxPayException e) {
throw new WxPayException(BaseError.ErrorCodeEnum.WX_MSG_ERROR);
}
}
public static String parseRequest(HttpServletRequest request) throws UnsupportedEncodingException {
request.setCharacterEncoding("utf-8");
String body = "";
try {
ServletInputStream inputStream = request.getInputStream();
// 设置编码格式“utf-8”否则获取中文为乱码
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
while (true) {
String info = br.readLine();
if (info == null) {
break;
}
if (StrUtil.isNotEmpty(body)) {
body = info;
} else {
body += info;
}
}
} catch (IOException e) {
e.printStackTrace();
}
return body;
}
最后处理完业务逻辑后给微信返回return_code “SUCCESS”