微信后端代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1241 lines
49 KiB

package com.ynxbd.wx.wxfactory;
import com.alibaba.fastjson.JSONObject;
import com.ynxbd.common.bean.enums.MerchantEnum;
import com.ynxbd.common.bean.pay.Bill;
import com.ynxbd.common.bean.pay.Order;
import com.ynxbd.common.helper.cache.TextCache;
import com.ynxbd.common.helper.common.*;
import com.ynxbd.common.result.ResultEnum;
import com.ynxbd.common.result.ServiceException;
import com.ynxbd.wx.config.WeChatConfig;
import com.ynxbd.wx.wxfactory.bean.WxPayNotify;
import com.ynxbd.wx.wxfactory.bean.refund.WxRefundItem;
import com.ynxbd.wx.wxfactory.bean.refund.WxRefundQueryRoot;
import com.ynxbd.wx.wxfactory.utils.WxSignHelper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import weixin.popular.api.PayMchAPI;
import weixin.popular.bean.paymch.*;
import weixin.popular.client.LocalHttpClient;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
import static weixin.popular.api.PayMchAPI.payRefundquery;
/**
* @Author wsq
* @Date 2020/8/17 15:26
* @Copyright @ 2020 云南新八达科技有限公司 All rights reserved.
*/
@Slf4j
public class WxPayHelper {
// 证书名称
public static final String API_CLIENT_CERT_NAME = "apiclient_cert.p12";
public static final String ALL = "ALL"; // 查询全部订单
// 交易成功返回内容
public static final String REVOKED = "REVOKED"; // 订单已撤销
public static final String PAY_ERROR = "PAYERROR"; // 支付失败
public static final String USER_PAYING = "USERPAYING"; // 用户支付中
public static final String REFUND = "REFUND"; // 发生过退款
// 失败
public static final String FAIL = "FAIL";
// 成功
public static final String SUCCESS = "SUCCESS";
public static final String OK = "OK";
// 排重
public static final RepeatKeyHelper KEYS = new RepeatKeyHelper(60);
static {
try {
String mchId = WeChatConfig.MCH_ID;
if (!ObjectUtils.isEmpty(mchId)) {
String clazzPath = Objects.requireNonNull(WxPayHelper.class.getClassLoader().getResource("")).getPath();
if (clazzPath == null) {
log.info("【微信】未找到证书");
}
LocalHttpClient.initMchKeyStore(mchId, clazzPath + API_CLIENT_CERT_NAME);
}
} catch (Exception e) {
ErrorHelper.println(e);
}
}
/**
* 微信支付调用及通知
*
* @param request 请求
* @return 微信支付信息
*/
public static WxPayNotify payNotify(HttpServletRequest request) throws ServiceException {
try {
// 转换数据对象
Map<String, Object> paramsMap = WxSignHelper.getReqXmlParamsMap(request);
log.info("【微信回调】map={}", paramsMap);
if (paramsMap == null) {
throw new ServiceException("【微信】回调通知下单信息返回错误");
}
// 签名验证
if (!WxSignHelper.validateSign(paramsMap, WeChatConfig.MCH_KEY)) { // 验证未通过,通知支付失败
throw new ServiceException("【微信】回调通知签名验证未通过!");
}
String json = JsonHelper.toJsonString(paramsMap);
if ("".equals(json)) {
throw new ServiceException("【微信】回调通知下单信息为空");
}
WxPayNotify notifyInfo = JsonHelper.parseObject(json, WxPayNotify.class);
log.info("【微信回调】notifyInfo={}", notifyInfo);
if (notifyInfo == null) {
throw new ServiceException("【微信】回调通知下单信息转换失败");
}
String openid = notifyInfo.getOpenid();
String outTradeNo = notifyInfo.getOutTradeNo();
String bankTransNo = notifyInfo.getTransactionId();
BigDecimal totalFee = notifyInfo.getTotalFee();// 金额
String timeEnd = notifyInfo.getTimeEnd();
if (openid == null || outTradeNo == null || bankTransNo == null || totalFee == null || timeEnd == null) {
throw new ServiceException(String.format("【微信】下单信息返回错误 outTradeNo={%s}", outTradeNo));
}
// 已处理 去重
if (KEYS.isContainsKey(bankTransNo)) {
throw new ServiceException(ResultEnum.PAY_NOTIFY_REPEAT, String.format("【微信】[重复请求]下单信息去重 outTradeNo={%s}", outTradeNo));
}
String info = WxSignHelper.getMapInfo(paramsMap);
Map<String, String> dateMap = WxSignHelper.getDateMap(timeEnd);
notifyInfo.setPayDate(dateMap.get(WxSignHelper.DATE_KEY));
notifyInfo.setPayTime(dateMap.get(WxSignHelper.TIME_KEY));
notifyInfo.setPayInfo(info);
notifyInfo.setTotalFee(totalFee.movePointLeft(2));
return notifyInfo;
} catch (Exception e) {
if (e instanceof ServiceException) {
throw e;
}
ErrorHelper.println(e);
throw new ServiceException(String.format("【微信】支付通知异常:[%s]", e.getMessage()));
}
}
/**
* 商户创建订单
*
* @param merchantEnum 枚举
* @param title 商品标题
* @param totalFee 订单金额
* @param outTradeNo 订单号
* @param notifyType 回调类型
* @param ip ip地址
* @param openid openid
* @param patientId 患者id
* @return 成功返回 json, 否则返回null
*/
public static JSONObject createOrder(MerchantEnum merchantEnum, String title, String totalFee, String outTradeNo, String notifyType, String ip, String openid, String patientId) {
try {
if (ObjectUtils.isEmpty(openid) || ObjectUtils.isEmpty(outTradeNo)
|| merchantEnum == null || totalFee == null || notifyType == null) {
log.info("【微信】下单参数缺失");
return null;
}
// 金额转换
String cents;
try {
cents = new BigDecimal(totalFee).movePointRight(2).toString(); // 单位分
} catch (Exception e) {
ErrorHelper.println(e);
log.info("【微信】统一下单金额转换异常");
return null;
}
String appId = WeChatConfig.APP_ID;
String mchId = WeChatConfig.MCH_ID;
String mchKey = WeChatConfig.MCH_KEY;
String nonce = UUID.randomUUID().toString().replace("-", "");
Unifiedorder unifiedorder = new Unifiedorder();
unifiedorder.setAppid(appId);
unifiedorder.setMch_id(mchId);
unifiedorder.setNonce_str(nonce);
unifiedorder.setOpenid(openid);
unifiedorder.setOut_trade_no(outTradeNo);
unifiedorder.setBody(title);
unifiedorder.setTotal_fee(cents); // 金额分
unifiedorder.setSpbill_create_ip(ip);
unifiedorder.setNotify_url(WeChatConfig.getBaseUrl() + merchantEnum.NOTIFY_URL);
unifiedorder.setTrade_type("JSAPI");
// 额外参数
unifiedorder.setAttach(notifyType);
log.info(appId + ","
+ mchId + ","
+ nonce + ","
+ title + ","
+ outTradeNo + ","
+ totalFee + ","
+ ip + ","
+ merchantEnum.NOTIFY_URL);
// 微信统一下单
UnifiedorderResult unifiedorderResult = PayMchAPI.payUnifiedorder(unifiedorder, mchKey);
log.info("【微信】统一下单返回:{}", JsonHelper.toJsonString(unifiedorderResult));
if (unifiedorderResult == null) {
log.info("【微信】统一下单失败");
return null;
}
String result_code = unifiedorderResult.getResult_code();
// 下单是否成功
Boolean sign_status = unifiedorderResult.getSign_status();
if (!SUCCESS.equals(result_code) || sign_status == null || !sign_status) {
log.info("【微信】统一下单返回异常信息:sign_status={}, err_code=[ {} ], err_code_des=[ {} ]", sign_status, unifiedorderResult.getErr_code(), unifiedorderResult.getErr_code_des());
return null;
}
String json = weixin.popular.util.PayUtil.generateMchPayJsRequestJson(unifiedorderResult.getPrepay_id(), appId, mchKey);
if (json != null) {
return JsonHelper.parseObject(json);
}
} catch (Exception e) {
log.error("【微信】统一下单异常信息:" + e.getMessage());
ErrorHelper.println(e);
}
return null;
}
/**
* 微信退款
*
* @param outTradeNo 订单号
* @param outRefundNo 退款订单号(HIS订单号)
* @param refundMoney 本次退款金额
* @param totalFee 订单总金额
* @param desc 退款描述
* @return 退款信息
*/
public static Order refund(String outTradeNo, String outRefundNo, BigDecimal refundMoney, BigDecimal totalFee, String desc) {
Order order = new Order();
try {
int feeCents;
int totalFeeCents;
feeCents = refundMoney.movePointRight(2).intValue(); // 单位分
totalFeeCents = totalFee.movePointRight(2).intValue();
if (feeCents == 0 || totalFeeCents == 0) {
order.setErrorMsg(ResultEnum.REFUND_MONEY_IS_ZERO.message);
return order;
}
log.info("【微信】退款开始 outTradeNo={}, refundMoney={}, totalFee={}", outTradeNo, feeCents, totalFeeCents);
SecapiPayRefund refund = new SecapiPayRefund();
refund.setAppid(WeChatConfig.APP_ID);
refund.setMch_id(WeChatConfig.MCH_ID);
refund.setNonce_str(UUID.randomUUID().toString().replace("-", ""));
refund.setSign_type("MD5");
refund.setOut_trade_no(outTradeNo);
refund.setOut_refund_no(outRefundNo);
refund.setRefund_fee(feeCents);
refund.setTotal_fee(totalFeeCents);
refund.setRefund_fee_type("CNY");
refund.setRefund_account("REFUND_SOURCE_UNSETTLED_FUNDS");
if (desc.length() > 80) {
desc = desc.substring(0, 80);
}
desc = TextCache.textFilter(desc);
refund.setRefund_desc(desc);
SecapiPayRefundResult refundResult;
try {
refundResult = PayMchAPI.secapiPayRefund(refund, WeChatConfig.MCH_KEY);
if (refundResult == null) {
throw new Exception(ResultEnum.INTERFACE_WX_INVOKE_ERROR.message);
}
} catch (Exception e) {
log.error("【微信】退款接口调用异常 outTradeNo={}", outTradeNo);
order.setErrorMsg(ResultEnum.INTERFACE_WX_INVOKE_ERROR.message);
return order;
}
String code = refundResult.getReturn_code();
String message = refundResult.getReturn_msg();
String resultCode = refundResult.getResult_code();
if (!SUCCESS.equals(resultCode)) {
String errCode = refundResult.getErr_code();
String errCodeDesc = refundResult.getErr_code_des();
log.info("【微信】退款失败 code={}, message={}, resultCode={}, errCode={}, errCodeDesc={}", code, message, resultCode, errCode, errCodeDesc);
order.setErrorMsg(errCodeDesc);
order.setRefundResult(errCodeDesc); // 退款失败信息记录
return order;
}
log.info("【微信】退款返回 code={}, message={}, resultCode={}", code, message, resultCode);
order.setRefundResult(message); // 退款信息记录
if (!OK.equals(message)) { // 退款失败
order.setErrorMsg(ResultEnum.INTERFACE_WX_INVOKE_ERROR.message);
return order;
}
order.setSuccess(true);
return order;
} catch (Exception e) {
ErrorHelper.println(e);
order.setErrorMsg(ResultEnum.INTERFACE_WX_INVOKE_ERROR.message);
String refundResult = order.getRefundResult();
order.setRefundResult(ObjectUtils.isEmpty(refundResult) ? order.getErrorMsg() : refundResult);
return order;
}
}
/**
* 微信扫码盒子支付
*
* @param title 商品标题
* @param outTradeNo 商户订单号
* @param totalFee 总金额
* @param authCode 微信条码
* @param ip ip地址
* @return 执行返回
*/
public static Order payMicro(String title, String outTradeNo, BigDecimal totalFee, String authCode, String ip) {
Order order = new Order();
if (StringUtils.isEmpty(outTradeNo) || StringUtils.isEmpty(authCode) || ip == null || totalFee == null) {
order.setErrorMsg("【微信】[盒子支付] 参数缺失");
return order;
}
int totalFeeCents;
try {
totalFeeCents = totalFee.movePointRight(2).intValue();
String appId = WeChatConfig.APP_ID;
String mchId = WeChatConfig.MCH_ID;
String mchKey = WeChatConfig.MCH_KEY;
String nonce = UUID.randomUUID().toString().replace("-", "");
Micropay micropay = new Micropay();
micropay.setAppid(appId);
micropay.setMch_id(mchId);
micropay.setNonce_str(nonce); // 随机字符串
micropay.setOut_trade_no(outTradeNo); // 商户订单号
micropay.setBody(title);
micropay.setAuth_code(authCode);
micropay.setTotal_fee(totalFeeCents); // 金额分
micropay.setSpbill_create_ip(ip);
// micropay.setDetail("描述"); // 商品描述
MicropayResult micropayResult = PayMchAPI.payMicropay(micropay, mchKey);
if (micropayResult == null) {
order.setErrorMsg("【微信】盒子扫码支付请求失败");
return order;
}
log.info("【微信】[盒子支付]支付反馈 {}", JsonHelper.toJsonString(micropayResult));
String openid = micropayResult.getOpenid();
String bankTransNo = micropayResult.getTransaction_id(); // 银行流水号
String resultCode = micropayResult.getResult_code();
String errMsg = micropayResult.getErr_code_des();
String errCode = micropayResult.getErr_code();
order.setOpenid(openid);
order.setBankTransNo(bankTransNo);
// 调用失败
if (FAIL.equals(micropayResult.getReturn_code())) { // 失败
log.info("【微信】[盒子支付]失败 outTradeNo={}, bankTransNo={}, returnCode={}, returnMsg={}", outTradeNo, bankTransNo, micropayResult.getReturn_code(), micropayResult.getReturn_msg());
order.setErrorCode(micropayResult.getReturn_code());
order.setErrorMsg(micropayResult.getReturn_msg());
return order;
}
Order wxOrder;
if (FAIL.equals(resultCode)) { // 失败
if (USER_PAYING.equals(errCode)) { // 密码支付
log.info("【微信】[盒子支付]密码支付 outTradeNo={}, msg={}", outTradeNo, errMsg);
for (int i = 1; i <= 5; i++) {
try {
Thread.sleep(5000 + ((i - 1) * 1500)); // 总共35s
wxOrder = queryOrder(outTradeNo);
if (wxOrder.isSuccess()) {
return wxOrder;
}
// 支付失败
if (PAY_ERROR.equals(wxOrder.getTradeState())) {
if (i == 1 || i == 2) {
Thread.sleep(i == 2 ? 3000 : 9000); // 等够15s
}
// 撤销订单
Order cancelOrder = cancelOrder(wxOrder.getOutTradeNo(), wxOrder.getBankTransNo());
// 撤销成功
if (cancelOrder.isSuccess()) {
wxOrder.setTradeState(REVOKED);
wxOrder.setErrorMsg("订单已撤销");
}
return wxOrder;
}
// 其他失败原因
if (!USER_PAYING.equals(wxOrder.getTradeState())) {
return wxOrder;
}
} catch (Exception e) {
e.printStackTrace();
}
}
} else {
order.setErrorCode(errCode);
order.setErrorMsg(errMsg);
log.info("【微信】[盒子支付]失败 outTradeNo={}, bankTransNo={}, errCode={}, errMsg={}", outTradeNo, bankTransNo, errCode, errMsg);
return order;
}
}
// 免密支付
Thread.sleep(2000); // 2s
wxOrder = queryOrder(outTradeNo);
// 长时间未支付
if (USER_PAYING.equals(wxOrder.getTradeState()) || PAY_ERROR.equals(wxOrder.getTradeState())) {
// 撤销订单
Order cancelOrder = cancelOrder(wxOrder.getOutTradeNo(), wxOrder.getBankTransNo());
// 撤销成功
if (cancelOrder.isSuccess()) {
wxOrder.setTradeState(REVOKED);
wxOrder.setErrorMsg("订单已撤销");
}
}
return wxOrder;
} catch (Exception e) {
log.info("【微信】[盒子支付]异常 {}", e.getMessage());
order.setErrorMsg("网络异常或当前不支持微信支付");
ErrorHelper.println(e);
}
return order;
}
/**
* 撤销订单
*
* @param outTradeNo 商户订单号
* @param bankTransNo 银行流水号
* @return 撤销
*/
public static Order cancelOrder(String outTradeNo, String bankTransNo) {
log.info("【微信】撤销订单 outTradeNo={}, bankTransNo={}", outTradeNo, bankTransNo);
Order order = new Order();
try {
MchReverse mchReverse = new MchReverse();
String appId = WeChatConfig.APP_ID;
String mchId = WeChatConfig.MCH_ID;
String mchKey = WeChatConfig.MCH_KEY;
mchReverse.setAppid(appId);
mchReverse.setMch_id(mchId);
mchReverse.setOut_trade_no(outTradeNo);
if (!ObjectUtils.isEmpty(bankTransNo)) {
mchReverse.setTransaction_id(bankTransNo);
}
mchReverse.setNonce_str(CodeHelper.get32UUID());
mchReverse.setSign_type("MD5");
MchReverseResult wxResult = PayMchAPI.secapiPayReverse(mchReverse, mchKey);
if (wxResult == null) {
order.setErrorMsg("【微信】撤销订单失败");
return order;
}
// 调用错误
if (FAIL.equals(wxResult.getReturn_code())) {
order.setErrorCode(wxResult.getReturn_code());
order.setErrorMsg(wxResult.getReturn_msg());
return order;
}
// 业务错误
if (FAIL.equals(wxResult.getResult_code())) {
order.setErrorCode(wxResult.getErr_code());
order.setErrorMsg(wxResult.getErr_code_des());
return order;
}
// 交易成功
if (SUCCESS.equals(wxResult.getResult_code())) {
order.setSuccess(true); // 订单支付成功(判断条件)
}
} catch (Exception e) {
ErrorHelper.println(e);
order.setErrorMsg("异常信息:" + e.getMessage());
}
return order;
}
/**
* 查询退费订单集(含退费时间)
*
* @param outTradeNo 订单号
* @return 订单信息
*/
public static List<Bill> queryRefundInfoList(String outTradeNo) throws ServiceException {
Refundquery refund = new Refundquery();
refund.setAppid(WeChatConfig.APP_ID);
refund.setMch_id(WeChatConfig.MCH_ID);
refund.setOut_trade_no(outTradeNo);
refund.setNonce_str(CodeHelper.get32UUID());
WxRefundQueryRoot wxResult = WxFactory.Base.Refund().refundQuery(refund, WeChatConfig.MCH_KEY);
if (wxResult == null) {
throw new ServiceException(ResultEnum.INTERFACE_WX_INVOKE_ERROR);
}
if (!SUCCESS.equals(wxResult.getReturn_code())) {
log.info("[微信]查询退款失败{}", wxResult.getReturn_msg());
throw new ServiceException(ResultEnum.INTERFACE_WX_INVOKE_ERROR);
}
List<Bill> billList = new ArrayList<>();
if (FAIL.equals(wxResult.getResult_code()) && "REFUNDNOTEXIST".equals(wxResult.getErr_code())) {
return billList;
}
if (!SUCCESS.equals(wxResult.getResult_code())) {
throw new ServiceException(ResultEnum.INTERFACE_WX_INVOKE_ERROR);
}
List<WxRefundItem> refundOrderList = wxResult.getItems();
if (refundOrderList == null || refundOrderList.size() == 0) {
return billList;
}
BigDecimal totalFee = new BigDecimal(wxResult.getTotal_fee()).movePointLeft(2);
Bill bill;
String refundStatus;
for (WxRefundItem item : refundOrderList) {
refundStatus = item.getRefund_status();
if (SUCCESS.equals(refundStatus)) {
bill = new Bill();
bill.setTotalFee(totalFee);
bill.setMoney(new BigDecimal(item.getRefund_fee()).movePointLeft(2));
bill.setRefundId(item.getRefund_id());
bill.setOutRefundNo(item.getOut_refund_no());
bill.setRefundChannel(item.getRefund_channel());
bill.setRefundRecvAccout(item.getRefund_recv_accout());
bill.setRefundTime(item.getRefund_success_time());
billList.add(bill);
}
}
return billList;
}
/**
* 查询订单
*
* @param outTradeNo 订单号
* @return 订单信息
*/
public static List<Bill> queryRefundList(String outTradeNo) throws ServiceException {
Refundquery refund = new Refundquery();
refund.setAppid(WeChatConfig.APP_ID);
refund.setMch_id(WeChatConfig.MCH_ID);
refund.setOut_trade_no(outTradeNo);
refund.setNonce_str(CodeHelper.get32UUID());
RefundqueryResult wxResult = payRefundquery(refund, WeChatConfig.MCH_KEY);
if (wxResult == null) {
throw new ServiceException(ResultEnum.INTERFACE_WX_INVOKE_ERROR);
}
if (!SUCCESS.equals(wxResult.getReturn_code())) {
log.info("[微信]查询退款失败{}", wxResult.getReturn_msg());
throw new ServiceException(ResultEnum.INTERFACE_WX_INVOKE_ERROR);
}
List<Bill> billList = new ArrayList<>();
if (FAIL.equals(wxResult.getResult_code()) && "REFUNDNOTEXIST".equals(wxResult.getErr_code())) {
return billList;
}
if (!SUCCESS.equals(wxResult.getResult_code())) {
throw new ServiceException(ResultEnum.INTERFACE_WX_INVOKE_ERROR);
}
List<RefundqueryResultItem> refundOrderList = wxResult.getRefundqueryResultItems();
if (refundOrderList == null || refundOrderList.size() == 0) {
return billList;
}
BigDecimal totalFee = new BigDecimal(wxResult.getTotal_fee()).movePointLeft(2);
Bill bill;
String refundStatus;
for (RefundqueryResultItem item : refundOrderList) {
refundStatus = item.getRefund_status();
if (SUCCESS.equals(refundStatus)) {
bill = new Bill();
bill.setTotalFee(totalFee);
bill.setMoney(new BigDecimal(item.getRefund_fee()).movePointLeft(2));
bill.setRefundId(item.getRefund_id());
bill.setOutRefundNo(item.getOut_refund_no());
bill.setRefundChannel(item.getRefund_channel());
bill.setRefundRecvAccout(item.getRefund_recv_accout());
billList.add(bill);
}
}
return billList;
}
/**
* 查询退费订单
*
* @param outTradeNo 订单号
* @return 订单信息
*/
public static Bill queryRefund(String outTradeNo, String outRefundNo) throws ServiceException {
if (ObjectUtils.isEmpty(outTradeNo) || ObjectUtils.isEmpty(outRefundNo)) {
throw new ServiceException(ResultEnum.PARAM_IS_DEFECT);
}
List<Bill> billList = queryRefundList(outTradeNo);
if (billList.size() == 0) {
return null;
}
for (Bill bill : billList) {
if (outRefundNo.equals(bill.getOutRefundNo())) {
return bill;
}
}
return null;
}
/**
* 查询订单
*
* @param outTradeNo 订单号
* @return 订单信息
*/
public static Order queryOrder(String outTradeNo) {
Order order = new Order();
order.setOutTradeNo(outTradeNo);
try {
MchOrderquery mchOrderquery = new MchOrderquery();
String appId = WeChatConfig.APP_ID;
String mchId = WeChatConfig.MCH_ID;
String mchKey = WeChatConfig.MCH_KEY;
mchOrderquery.setAppid(appId);
mchOrderquery.setMch_id(mchId);
mchOrderquery.setOut_trade_no(outTradeNo);
mchOrderquery.setNonce_str(CodeHelper.get32UUID());
MchOrderInfoResult wxResult = PayMchAPI.payOrderquery(mchOrderquery, mchKey);
if (wxResult == null) {
order.setErrorMsg("【微信】查询订单失败");
return order;
}
// 调用错误
if (FAIL.equals(wxResult.getReturn_code())) {
order.setErrorCode(wxResult.getReturn_code());
order.setErrorMsg(wxResult.getReturn_msg());
return order;
}
// 业务错误
if (FAIL.equals(wxResult.getResult_code())) {
order.setErrorCode(wxResult.getErr_code());
order.setErrorMsg(wxResult.getErr_code_des());
return order;
}
String tradeState = wxResult.getTrade_state();
order.setTradeState(tradeState);
if (SUCCESS.equals(tradeState)) {
order.setSuccess(true); // 订单支付成功(判断条件)
} else if (REFUND.equals(tradeState)) {
order.setErrorMsg(wxResult.getTrade_state_desc());
order.setRefund(true); // 订单发生过退款
} else {
// 交易错误
order.setErrorCode(tradeState);
order.setErrorMsg(wxResult.getTrade_state_desc());
return order;
}
// 交易成功
order.setOpenid(wxResult.getOpenid());
order.setBankTransNo(wxResult.getTransaction_id());
Integer totalFee = wxResult.getTotal_fee();
if (totalFee != null) {
order.setTotalFee(new BigDecimal(totalFee).movePointLeft(2));
}
} catch (Exception e) {
ErrorHelper.println(e);
order.setErrorMsg("异常信息:" + e.getMessage());
}
return order;
}
/**
* 查询订单
*
* @param transId 支付单号
* @return 订单信息
*/
public static Order queryOrderByTransId(String transId) {
Order order = new Order();
order.setBankTransNo(transId);
try {
MchOrderquery mchOrderquery = new MchOrderquery();
mchOrderquery.setAppid(WeChatConfig.APP_ID);
mchOrderquery.setMch_id(WeChatConfig.MCH_ID);
mchOrderquery.setTransaction_id(transId);
mchOrderquery.setNonce_str(CodeHelper.get32UUID());
MchOrderInfoResult wxResult = PayMchAPI.payOrderquery(mchOrderquery, WeChatConfig.MCH_KEY);
if (wxResult == null) {
order.setErrorMsg("【微信】查询订单失败");
return order;
}
// 调用错误
if (FAIL.equals(wxResult.getReturn_code())) {
order.setErrorCode(wxResult.getReturn_code());
order.setErrorMsg(wxResult.getReturn_msg());
return order;
}
// 业务错误
if (FAIL.equals(wxResult.getResult_code())) {
order.setErrorCode(wxResult.getErr_code());
order.setErrorMsg(wxResult.getErr_code_des());
return order;
}
String tradeState = wxResult.getTrade_state();
order.setTradeState(tradeState);
if (SUCCESS.equals(tradeState)) {
order.setSuccess(true); // 订单支付成功(判断条件)
} else if (REFUND.equals(tradeState)) {
order.setErrorMsg(wxResult.getTrade_state_desc());
order.setRefund(true); // 订单发生过退款
} else {
// 交易错误
order.setErrorCode(tradeState);
order.setErrorMsg(wxResult.getTrade_state_desc());
return order;
}
// 交易成功
order.setOpenid(wxResult.getOpenid());
order.setBankTransNo(wxResult.getTransaction_id());
Integer totalFee = wxResult.getTotal_fee();
if (totalFee != null) {
order.setTotalFee(new BigDecimal(totalFee).movePointLeft(2));
}
} catch (Exception e) {
ErrorHelper.println(e);
order.setErrorMsg("异常信息:" + e.getMessage());
}
return order;
}
//
// /**
// * 通知成功
// *
// * @param response 响应流
// */
// public static void notifySuccess(HttpServletResponse response) {
// notifyEnd(response, "SUCCESS", "");
// }
//
// public static void notifyEnd(HttpServletResponse response, String returnCode, String returnMsg) {
// try (PrintWriter writer = response.getWriter()) {
// String xml = "<xml><return_code><![CDATA[" + returnCode + "]]></return_code><return_msg><![CDATA[" + returnMsg + "]]></return_msg></xml>"
// writer.write(xml);
// writer.flush();
// } catch (Exception e) {
// e.printStackTrace();
// ErrorHelper.println(e);
// }
// }
/**
* 查询商户账单
*
* @return 订单信息
*/
public static List<Bill> queryBill(String billType, String date, boolean isCompose) throws ServiceException {
List<Bill> billList = new ArrayList<>();
if (!DateHelper.isValidDate(date, DateHelper.DateEnum.yyyy_MM_dd)) {
throw new ServiceException(ResultEnum.PARAM_DATE_ERROR);
}
Date dateTime = DateHelper.strToDate(date, DateHelper.DateEnum.yyyy_MM_dd);
if (dateTime == null) {
throw new ServiceException(ResultEnum.PARAM_DATE_ERROR);
}
date = DateHelper.dateToStr(dateTime, DateHelper.DateEnum.yyyyMMdd);
if (date == null) {
throw new ServiceException(ResultEnum.PARAM_DATE_ERROR);
}
if (!ALL.equals(billType) && !REFUND.equals(billType) && !SUCCESS.equals(billType)) {
throw new ServiceException(ResultEnum.PARAM_IS_INVALID);
}
MchDownloadbill mchDownloadbill = new MchDownloadbill();
mchDownloadbill.setAppid(WeChatConfig.APP_ID);
mchDownloadbill.setMch_id(WeChatConfig.MCH_ID);
mchDownloadbill.setNonce_str(CodeHelper.get32UUID());
mchDownloadbill.setBill_date(date);
mchDownloadbill.setBill_type(billType);
DownloadbillResult downloadbillResult = PayMchAPI.payDownloadbill(mchDownloadbill, WeChatConfig.MCH_KEY);
if (downloadbillResult == null) {
throw new ServiceException(ResultEnum.INTERFACE_WX_INVOKE_ERROR);
}
String code = downloadbillResult.getReturn_code();
String msg = downloadbillResult.getReturn_msg();
if (FAIL.equals(code)) {
if ("invalid bill_date".equals(msg)) {
throw new ServiceException("日期无效,或超过账单的可查询日期范围[invalid bill_date]");
}
log.info("[微信]下载对账单错误[{}][{}]", code, msg);
throw new ServiceException(ResultEnum.INTERFACE_WX_INVOKE_ERROR.message + "[" + msg + "]");
}
String data = downloadbillResult.getData();
if (data == null) {
throw new ServiceException(ResultEnum.INTERFACE_WX_INVOKE_ERROR);
}
String[] dataArr = data.split("\\n");
if (dataArr.length == 0) {
return billList;
}
int index;
Bill billItem;
String money;
String outRefundNo;
String[] titleArr = dataArr[0].split(",");
if (titleArr.length == 0) {
return billList;
}
String[] item;
String tradeState;
for (int i = 1; i < (dataArr.length - 2); i++) {
item = dataArr[i].split(","); //27
billItem = new Bill();
billItem.setTradeTime(dataHandle(item[0]));
billItem.setOutTradeNo(dataHandle(item[6]));
billItem.setBankTransNo(dataHandle(item[5]));
// billItem.setOpenid(dataHandle(item[7]));
// billItem.setPayType(dataHandle(item[8]));
tradeState = dataHandle(item[9]);
billItem.setTradeState(tradeState);
if (WxPayHelper.REFUND.equals(tradeState)) { // 退款
index = findIndex(titleArr, "退款金额");
if (index != -1) {
money = dataHandle(item[index]);
if (money != null) {
billItem.setMoney(new BigDecimal(money).negate());
}
}
index = findIndex(titleArr, "商户退款单号");
if (index != -1) {
outRefundNo = dataHandle(item[index]);
if (outRefundNo != null) {
billItem.setOutRefundNo(outRefundNo);
}
}
// index = findIndex(titleArr, "退款类型");
// if (index != -1) {
// order.setRefundType(dataHandle(item[index]));
// }
//
// index = findIndex(titleArr, "退款状态");
// if (index != -1) {
// order.setRefundResult(dataHandle(item[index]));
// }
} else if (WxPayHelper.SUCCESS.equals(tradeState)) { // 成功
index = findIndex(titleArr, "应结订单金额");
if (index != -1) {
money = dataHandle(item[index]);
if (money != null) {
billItem.setMoney(new BigDecimal(money));
}
}
}
billList.add(billItem);
}
if (ALL.equals(billType) && isCompose) {
List<Bill> refundList = billList.stream().filter(o -> REFUND.equals(o.getTradeState())).collect(Collectors.toList());
List<Bill> successList = billList.stream().filter(o -> SUCCESS.equals(o.getTradeState())).collect(Collectors.toList());
String outTradeNo;
for (Bill refundItem : refundList) {
outTradeNo = refundItem.getOutTradeNo();
for (Bill successItem : successList) {
if (outTradeNo.equals(successItem.getOutTradeNo())) {
if (successItem.getHisBills() == null) {
successItem.setRefundBills(new ArrayList<>());
}
refundItem.setBankTransNo(null);
refundItem.setOutTradeNo(null);
successItem.getRefundBills().add(refundItem);
break;
}
}
}
return successList;
}
return billList;
}
//
// /**
// * 查询商户账单
// *
// * @return 订单信息
// */
// public static List<Bill> queryBill(String billType, String date) throws ServiceException {
// List<Bill> billList = new ArrayList<>();
// if (!DateHelper.isValidDate(date, DateHelper.DateEnum.yyyy_MM_dd)) {
// throw new ServiceException(ResultEnum.PARAM_DATE_ERROR);
// }
//
// Date dateTime = DateHelper.strToDate(date, DateHelper.DateEnum.yyyy_MM_dd);
// if (dateTime == null) {
// throw new ServiceException(ResultEnum.PARAM_DATE_ERROR);
// }
// date = DateHelper.dateToStr(dateTime, DateHelper.DateEnum.yyyyMMdd);
// if (date == null) {
// throw new ServiceException(ResultEnum.PARAM_DATE_ERROR);
// }
//
// if (!ALL.equals(billType) && !REFUND.equals(billType) && !SUCCESS.equals(billType)) {
// throw new ServiceException(ResultEnum.PARAM_IS_INVALID);
// }
//
// MchDownloadbill mchDownloadbill = new MchDownloadbill();
// String appId = WeChatConfig.getAppId();
// String mchId = WeChatConfig.getMchId();
// String mchKey = WeChatConfig.getMchKey();
//
// mchDownloadbill.setAppid(appId);
// mchDownloadbill.setMch_id(mchId);
// mchDownloadbill.setNonce_str(CodeHelper.get32UUID());
// mchDownloadbill.setBill_date(date);
// mchDownloadbill.setBill_type(billType);
// DownloadbillResult downloadbillResult = PayMchAPI.payDownloadbill(mchDownloadbill, mchKey);
//
// if (downloadbillResult == null) {
// throw new ServiceException(ResultEnum.INTERFACE_WX_INVOKE_ERROR);
// }
//
// String data = downloadbillResult.getData();
// if (data == null) {
// throw new ServiceException(ResultEnum.INTERFACE_WX_INVOKE_ERROR);
// }
// String[] dataArr = data.split("\\n");
// if (dataArr.length == 0) {
// return billList;
// }
//
// int index;
// Bill billItem;
// String money;
// String[] titleArr = dataArr[0].split(",");
// if (titleArr.length == 0) {
// return billList;
// }
//
// System.out.println(JSONArray.toJSONString(titleArr));
// for (int i = 1; i < (dataArr.length - 2); i++) {
// String[] item = dataArr[i].split(","); //27
// System.out.println(JSONArray.toJSONString(item));
// billItem = new Bill();
// billItem.setTradeTime(dataHandle(item[0]));
// billItem.setOutTradeNo(dataHandle(item[6]));
// billItem.setBankTransNo(dataHandle(item[5]));
//// billItem.setOpenid(dataHandle(item[7]));
//// billItem.setPayType(dataHandle(item[8]));
// String tradeState = dataHandle(item[9]);
// billItem.setTradeState(tradeState);
// if (WxPayHelper.REFUND.equals(tradeState)) { // 退款
// index = findIndex(titleArr, "退款金额");
// if (index != -1) {
// money = dataHandle(item[index]);
// if (money != null) {
// billItem.setMoney(new BigDecimal(money).negate());
// }
// }
//
//// index = findIndex(titleArr, "退款类型");
//// if (index != -1) {
//// order.setRefundType(dataHandle(item[index]));
//// }
////
//// index = findIndex(titleArr, "退款状态");
//// if (index != -1) {
//// order.setRefundResult(dataHandle(item[index]));
//// }
//
// } else if (WxPayHelper.SUCCESS.equals(tradeState)) { // 成功
// index = findIndex(titleArr, "应结订单金额");
// if (index != -1) {
// money = dataHandle(item[index]);
// if (money != null) {
// billItem.setMoney(new BigDecimal(money));
// }
// }
// }
// billList.add(billItem);
// }
// return billList;
// }
// /**
// * 微信支付调用及通知
// *
// * @param request 请求
// * @param response 响应
// * @return 微信支付信息
// */
// public static PayResult oldPayNotify(HttpServletRequest request, HttpServletResponse response) {
// PayResult orderInfo = null;
// InputStream in = null;
// OutputStream out = null;
// try {
// in = request.getInputStream();
// out = response.getOutputStream();
//
// // 获取请求数据
// String wxXmlData = StreamUtils.copyToString(in, StandardCharsets.UTF_8);
//
// // 转换数据对象
// MchPayNotify wxPayNotify = XMLConverUtil.convertToObject(MchPayNotify.class, wxXmlData);
// String openid = wxPayNotify.getOpenid();
// String outTradeNo = wxPayNotify.getOut_trade_no();
// String bankTransNo = wxPayNotify.getTransaction_id();
// Integer totalFee = wxPayNotify.getTotal_fee(); // 金额
// String timeEnd = wxPayNotify.getTime_end();
// String attach = wxPayNotify.getAttach();
//
// if (openid == null || outTradeNo == null || timeEnd == null || bankTransNo == null) {
// log.info("【微信】下单信息返回错误 outTradeNo={}", outTradeNo);
// payNotifyError(out);
// return null;
// }
//
// // 已处理 去重
// if (KEYS.isContainsKey(bankTransNo)) {
// log.info("【微信】[重复请求]下单信息去重 outTradeNo={}", outTradeNo);
// payNotifySuccess(out);
// return null;
// }
//
// // 将XML转为MAP,确保所有字段都参与签名验证
// Map<String, String> wxMapData = XMLConverUtil.convertToMap(wxXmlData);
// StringBuilder wxPayInfo = new StringBuilder();
// for (String key : wxMapData.keySet()) {
// wxPayInfo.append(key).append("=").append(wxMapData.get(key)).append(";"); // 拼接微信返回的信息
// }
//
// // @since 2.8.5
// wxPayNotify.buildDynamicField(wxMapData);
// // 签名验证
// if (!SignatureUtil.validateSign(wxMapData, WeChatConfig.MCH_KEY)) { // 验证未通过,通知支付失败
// log.info("【微信】签名验证未通过,通知支付失败!");
// payNotifyError(out);
// return null;
// }
//
// // 验证通过
// payNotifySuccess(out);
//
// // 保存支付结果
// orderInfo = new PayResult();
// orderInfo.setOpenid(openid);
// orderInfo.setOutTradeNo(outTradeNo);
// orderInfo.setTimeEnd(timeEnd);
// orderInfo.setTotalFee(new BigDecimal(totalFee).movePointLeft(2));
// orderInfo.setTransId(bankTransNo);
// orderInfo.setInfo(wxPayInfo.toString());
// orderInfo.setAttach(attach);
//
// } catch (Exception e) {
// log.info("【微信】支付通知异常:" + e.getMessage());
// payNotifySuccess(out);
// ErrorHelper.println(e);
// } finally {
// try {
// if (in != null) in.close();
//
// if (out != null) {
// if (orderInfo == null) {
// payNotifySuccess(out);
// }
// out.flush();
// out.close();
// }
// } catch (Exception e) {
// ErrorHelper.println(e);
// }
// }
// return orderInfo;
// }
/**
* 商户创建订单
*
* @param merchantEnum 枚举
* @param title 商品标题
* @param totalFee 订单金额
* @param outTradeNo 订单号
* @param notifyType 回调类型
* @param ip ip地址
* @return 成功返回 json, 否则返回null
*/
public static String createNativeOrder(MerchantEnum merchantEnum, String title, BigDecimal totalFee, String outTradeNo, String notifyType, String ip) {
try {
if (ObjectUtils.isEmpty(outTradeNo) || merchantEnum == null || totalFee == null || notifyType == null) {
log.info("【微信】下单参数缺失");
return null;
}
// 金额转换
String cents;
try {
cents = totalFee.movePointRight(2).toString(); // 单位分
} catch (Exception e) {
ErrorHelper.println(e);
log.info("【微信】统一下单金额转换异常");
return null;
}
String appId = WeChatConfig.APP_ID;
String mchId = WeChatConfig.MCH_ID;
String mchKey = WeChatConfig.MCH_KEY;
String nonce = UUID.randomUUID().toString().replace("-", "");
Unifiedorder unifiedorder = new Unifiedorder();
unifiedorder.setAppid(appId);
unifiedorder.setMch_id(mchId);
unifiedorder.setNonce_str(nonce);
unifiedorder.setOut_trade_no(outTradeNo);
unifiedorder.setBody(title);
unifiedorder.setTotal_fee(cents); // 金额分
unifiedorder.setSpbill_create_ip(ip);
unifiedorder.setNotify_url(WeChatConfig.getBaseUrl() + merchantEnum.QR_NOTIFY_URL);
unifiedorder.setTrade_type("NATIVE");
// 额外参数
unifiedorder.setAttach(notifyType);
log.info(appId + ","
+ mchId + ","
+ nonce + ","
+ title + ","
+ outTradeNo + ","
+ totalFee + ","
+ ip + ","
+ merchantEnum.NOTIFY_URL);
// 微信统一下单
UnifiedorderResult unifiedorderResult = PayMchAPI.payUnifiedorder(unifiedorder, mchKey);
log.info("【微信】统一下单返回:{}", JsonHelper.toJsonString(unifiedorderResult));
if (unifiedorderResult == null) {
log.info("【微信】统一下单失败");
return null;
}
String result_code = unifiedorderResult.getResult_code();
// 下单是否成功
Boolean sign_status = unifiedorderResult.getSign_status();
if (!SUCCESS.equals(result_code) || sign_status == null || !sign_status) {
log.info("【微信】统一下单返回异常信息:sign_status={}, err_code=[ {} ], err_code_des=[ {} ]", sign_status, unifiedorderResult.getErr_code(), unifiedorderResult.getErr_code_des());
return null;
}
String codeUrl = unifiedorderResult.getCode_url();
log.info("codeUrl={}", codeUrl);
if (codeUrl != null) {
return codeUrl;
}
} catch (Exception e) {
log.error("【微信】统一下单异常信息:" + e.getMessage());
ErrorHelper.println(e);
}
return null;
}
public static String dataHandle(String data) {
if (ObjectUtils.isEmpty(data)) {
return null;
}
return data.substring(1);
}
public static int findIndex(String[] titleArr, String title) {
if (titleArr == null || titleArr.length == 0) {
return -1;
}
for (int i = 0; i < titleArr.length; i++) {
if (title.equals(titleArr[i])) {
return i;
}
}
return -1;
}
}