diff --git a/electripper-admin/src/main/java/com/ruoyi/web/controller/app/AppVerifyController.java b/electripper-admin/src/main/java/com/ruoyi/web/controller/app/AppVerifyController.java index a9e5294..52268de 100644 --- a/electripper-admin/src/main/java/com/ruoyi/web/controller/app/AppVerifyController.java +++ b/electripper-admin/src/main/java/com/ruoyi/web/controller/app/AppVerifyController.java @@ -343,7 +343,7 @@ public class AppVerifyController extends BaseController @PostMapping("/order/withdraw") public AjaxResult withdraw() { - + logger.info("【提现请求】-----------------"); //根据订单号查询订单信息 EtOrder etOrder = new EtOrder(); etOrder.setUserId(getUserId()); diff --git a/electripper-admin/src/main/java/com/ruoyi/web/controller/common/CallbackController.java b/electripper-admin/src/main/java/com/ruoyi/web/controller/common/CallbackController.java index 6a08936..96445bd 100644 --- a/electripper-admin/src/main/java/com/ruoyi/web/controller/common/CallbackController.java +++ b/electripper-admin/src/main/java/com/ruoyi/web/controller/common/CallbackController.java @@ -4,11 +4,10 @@ import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import com.ruoyi.common.constant.ServiceConstants; import com.ruoyi.common.exception.ServiceException; -import com.ruoyi.common.pay.syb.enums.SybTrxCode; -import com.ruoyi.common.pay.syb.enums.SybTrxStatus; -import com.ruoyi.common.pay.syb.service.SybPayService; -import com.ruoyi.common.pay.syb.util.SybUtil; +import com.ruoyi.common.pay.tm.TmPayService; +import com.ruoyi.common.pay.tm.enums.PayStatus; import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.http.HttpUtils; import com.ruoyi.system.domain.EtCallbackLog; import com.ruoyi.system.domain.vo.AttachVo; import com.ruoyi.system.mapper.EtCallbackLogMapper; @@ -25,7 +24,7 @@ import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; -import java.util.TreeMap; +import java.util.Map; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -41,7 +40,7 @@ public class CallbackController { private CallbackService callbackService; @Autowired - private SybPayService sybPayService; + private TmPayService tmPayService; @Autowired private ScheduledExecutorService scheduledExecutorService; @@ -84,42 +83,59 @@ public class CallbackController { } /** - * 通联微信支付回调 + * 太米微信支付回调 */ - @ApiOperation(value = "通联微信支付回调") - @RequestMapping(value = "/tlwx", method = RequestMethod.POST) - public String tlwx(HttpServletRequest request) { + @ApiOperation(value = "太米微信支付回调") + @RequestMapping(value = "/tmwx", method = RequestMethod.POST) + public String tmwx(HttpServletRequest request) { try { - request.setCharacterEncoding("UTF-8");//通知传输的编码为GBK - // 获取所有参数 - TreeMap params = SybUtil.getParams(request); - boolean sign = sybPayService.validSign(params); - + String body = HttpUtils.getBody(request); + log.info("【太米微信支付回调】接收对象 : " + body); + // 先把body转成map + Map params = JSON.parseObject(body, Map.class); + // 验证签名 + boolean sign = tmPayService.validSign(params); if (sign) { EtCallbackLog etCallbackLog = new EtCallbackLog(); - etCallbackLog.setBody(JSON.toJSONString(params)); + etCallbackLog.setBody(body); etCallbackLog.setType("1"); - - String trxCode = params.get("trxcode"); // 交易类型 - String outTradeNo = params.get("cusorderid"); // 商户自定义订单号 - String trxStatus = params.get("trxstatus"); // 交易结果 - String remark = params.get("trxreserved"); // 附加信息,用于判断业务类型 - AttachVo attachVo = JSONObject.parseObject(remark,AttachVo.class); - log.info("【通联微信支付回调】回调参数--附加信息 : 【{}】",JSON.toJSONString(attachVo)); - - if (SybTrxStatus.isSuccess(trxStatus) && SybTrxCode.WX_PAY.equalsCode(trxCode)) { + JSONObject tradeInfo = (JSONObject)params.get("tradeInfo"); + String payType = (String)tradeInfo.get("payType"); // 交易类型 + String outTradeNo = (String)tradeInfo.get("outTradeId"); // 商户自定义订单号 + String trxStatus = (String)tradeInfo.get("payStatus"); // 交易结果 + // 构建attachVo + AttachVo attach = generateAttach(outTradeNo); + log.info("【太米微信支付回调】创建的attachVo : " + JSON.toJSONString(attach)); + if(PayStatus.isSuccess(trxStatus) && payType.equals("wx_pay")) { // 新版支付订单 - callbackService.businessHandle(outTradeNo,attachVo, ServiceConstants.PAY_TYPE_TLWX); + callbackService.businessHandle(outTradeNo,attach, ServiceConstants.PAY_TYPE_TMWX); } //异步保存回调日志 - asynchronousSaveCallbackLog(etCallbackLog); + asynchronousSaveCallbackLog(etCallbackLog);// tradeInfo -> {JSONObject@14208} size = 34 + }else { + throw new ServiceException("签名验证失败"); } - return "error"; + return "{\"result\":\"SUCCESS\"}"; } catch (Exception e) { throw new ServiceException(e.getMessage()); } } + private AttachVo generateAttach(String outTradeNo) { + AttachVo attachVo = new AttachVo(); + // 如果outTradeNo以tmwx开头,则说明是attachVo.setType(1) + if(outTradeNo.startsWith("tmwx")){ + attachVo.setType("1"); + }else if (outTradeNo.startsWith("tmyj")){ + attachVo.setType("4"); + }else if(outTradeNo.startsWith("tmyhj")){ + attachVo.setType("3"); + }else{ + throw new ServiceException("订单号格式错误"); + } + return attachVo; + } + private void asynchronousSaveCallbackLog(EtCallbackLog etCallbackLog) { //开异步线程保存回调参数 scheduledExecutorService.schedule(() -> { @@ -131,4 +147,6 @@ public class CallbackController { } }, 0, TimeUnit.SECONDS); } + + } diff --git a/electripper-common/src/main/java/com/ruoyi/common/constant/ServiceConstants.java b/electripper-common/src/main/java/com/ruoyi/common/constant/ServiceConstants.java index a7d7cd5..5e48959 100644 --- a/electripper-common/src/main/java/com/ruoyi/common/constant/ServiceConstants.java +++ b/electripper-common/src/main/java/com/ruoyi/common/constant/ServiceConstants.java @@ -147,9 +147,9 @@ public class ServiceConstants { public static final String PAY_TYPE_WX = "wx"; /** - * 支付方式: tlwx-通联微信 + * 支付方式: tmwx-太米微信 */ - public static final String PAY_TYPE_TLWX = "tlwx"; + public static final String PAY_TYPE_TMWX = "tmwx"; /** * 支付方式: sys-系统 免费骑行时,订单金额为0 diff --git a/electripper-common/src/main/java/com/ruoyi/common/enums/PayChannel.java b/electripper-common/src/main/java/com/ruoyi/common/enums/PayChannel.java index a6bdc28..a16b4ef 100644 --- a/electripper-common/src/main/java/com/ruoyi/common/enums/PayChannel.java +++ b/electripper-common/src/main/java/com/ruoyi/common/enums/PayChannel.java @@ -13,7 +13,8 @@ import lombok.Getter; @AllArgsConstructor public enum PayChannel { CT_WX("ctwx", "创特微信支付"), - TL_WX("tlwx", "通联微信支付"), +// TL_WX("tlwx", "通联微信支付"), + TM_WX("tmwx", "太米微信支付"), YS_WX("yswx", "嵛山岛微信支付"); private final String code; diff --git a/electripper-common/src/main/java/com/ruoyi/common/pay/PaymentResult.java b/electripper-common/src/main/java/com/ruoyi/common/pay/PaymentResult.java index 06da388..186a07a 100644 --- a/electripper-common/src/main/java/com/ruoyi/common/pay/PaymentResult.java +++ b/electripper-common/src/main/java/com/ruoyi/common/pay/PaymentResult.java @@ -1,14 +1,15 @@ package com.ruoyi.common.pay; +import com.ruoyi.common.pay.tm.vo.TmTradeInfo; import com.wechat.pay.java.service.payments.model.Transaction; import lombok.Data; -import java.util.Map; @Data public class PaymentResult { + private Transaction transaction; - private Map result; + private TmTradeInfo tradeInfo; } diff --git a/electripper-common/src/main/java/com/ruoyi/common/pay/tm/Application.java b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/Application.java new file mode 100644 index 0000000..76a4e15 --- /dev/null +++ b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/Application.java @@ -0,0 +1,96 @@ +package com.ruoyi.common.pay.tm; + +import com.alibaba.fastjson2.JSON; +import com.ruoyi.common.utils.http.HttpUtils; +import com.ruoyi.common.utils.uuid.IdUtils; + +import java.util.HashMap; + +public class Application { + + private final static String HTTP = "https://pos.weixincore.com"; + private final static String SIGNKEY = "ac6d97e67b444b7a43edfc9182634786"; + + public static void main(String[] args) { + orderQuery("tmwx1727172604248"); +// miniPay(); + } + + /** + * 订单查询 + */ + public static void orderQuery(String outTradeId) { + HashMap body = new HashMap<>(); + body.put("outTradeId", outTradeId); + body.put("terminalType", "1"); + body.put("shopId", "488"); + doPost("/open/Pay/orderQuery", body); + } + + /** + * 退款 + */ + public static void refund() { + HashMap body = new HashMap<>(); + body.put("refundFee", "0.01"); + body.put("terminalType", "1"); + body.put("tradeId", "1"); + body.put("shopId", "488"); + doPost("/open/Pay/refund", body); + } + + +// /** +// * jsapi支付 +// */ +// public static void pay() { +// HashMap body = new HashMap(); +// body.put("payAmount", "1"); +// body.put("terminalType", "1"); +// body.put("shopId", "488"); +// // 填充必填字段 +// body.put("payType", "wx_pay"); // 支付方式,可以是 wx.pay, ali.pay, union.online +// body.put("outTradeId", "tradeId123"); // 商户订单号 +// body.put("body", "商品描述"); // 商品描述 +// body.put("notifyUrl", "https://yourdomain.com/notify"); // 异步回调URL +// body.put("frontUrl", "https://yourdomain.com/front"); // 前端页面跳转URL +// doPost("/open/Pay/unifiedOrder", body); +// } + + + /** + * 小程序支付 + */ + public static void miniPay() { + HashMap body = new HashMap<>(); + body.put("payAmount", "1"); + body.put("terminalType", "1"); + body.put("shopId", "488"); + // 填充必填字段 + body.put("payType", "wx_pay"); // 支付方式,可以是 wx.pay, ali.pay, union.online + body.put("outTradeId", IdUtils.getOrderNo("tmwx")); // 商户订单号 + System.out.println("----------------------------------:"+body.get("outTradeId")); + body.put("openid", "o6yEK7Z0OdWM2N_d8ehItn-5NBH8"); // openid + body.put("body", "商品描述"); // 商品描述 + body.put("notifyUrl", "https://yourdomain.com/notify"); // 异步回调URL + body.put("frontUrl", "https://yourdomain.com/front"); // 前端页面跳转URL + doPost("/open/Pay/miniPay", body); + } + + private static void doPost(String url, HashMap body) { + body.put("developerId", "100001"); + body.put("version", "1.0"); + body.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000)); + body.put("nonceStr", TmPayUtil.getRandomString(16)); + String bodyStr = TmPayUtil.getAsciiSort(body); + String sign = TmPayUtil.getMD5Code(bodyStr+"&key="+SIGNKEY).toUpperCase(); + body.put("sign", sign); + + HashMap headerData = new HashMap(); + headerData.put("Content-Type", "application/json"); + + String response = HttpUtils.sendPostWithHeaders(HTTP + url, headerData,JSON.toJSONString(body)); + System.out.println("API Response: " + response); + } + +} diff --git a/electripper-common/src/main/java/com/ruoyi/common/pay/tm/IChannelInfo.java b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/IChannelInfo.java new file mode 100644 index 0000000..9cdcaf8 --- /dev/null +++ b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/IChannelInfo.java @@ -0,0 +1,16 @@ +package com.ruoyi.common.pay.tm; + +public interface IChannelInfo { + + String getDeveloperId(); + + String getShopId(); + + String getHttpUrl(); + + String getSignKey(); + + String getNotifyUrl(); + + String getSn(); +} diff --git a/electripper-common/src/main/java/com/ruoyi/common/pay/tm/TmPayService.java b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/TmPayService.java new file mode 100644 index 0000000..cd9476b --- /dev/null +++ b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/TmPayService.java @@ -0,0 +1,148 @@ +package com.ruoyi.common.pay.tm; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.pay.tm.vo.RefundInfo; +import com.ruoyi.common.pay.tm.vo.TmTradeInfo; +import com.ruoyi.common.pay.wx.Payable; +import com.ruoyi.common.pay.wx.RefundAble; +import com.ruoyi.common.utils.http.HttpUtils; +import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse; +import org.springframework.stereotype.Service; +import java.util.HashMap; +import java.util.Map; + +/** + * 太米支付 + */ +@Service +public class TmPayService { + + private final static String SIGNKEY = "b4ixpiogfj5vu3tbkv23gj0dvo2j2ksz"; + + /** + * 订单查询 + */ + public static TmTradeInfo orderQuery(IChannelInfo channel, String outTradeNo) { + HashMap body = new HashMap<>(); + body.put("outTradeId", outTradeNo); + body.put("terminalType", "1"); + body.put("shopId", channel.getShopId()); + String response = doPost(channel.getHttpUrl() + "/open/Pay/orderQuery", body,channel); + if (com.ruoyi.common.utils.StringUtils.hasText(response)) { + JSONObject jsonResponse = JSON.parseObject(response); + if (jsonResponse.getInteger("errCode") == 0 && "ok".equals(jsonResponse.getString("errMsg"))) { + JSONObject tradeInfo = jsonResponse.getJSONObject("tradeInfo"); + TmTradeInfo tmTradeInfo = tradeInfo.toJavaObject(TmTradeInfo.class); + return tmTradeInfo; + } else { + throw new ServiceException("订单查询失败: " + jsonResponse.getString("errMsg")); + } + } + throw new ServiceException("订单查询数据为空"); + } + + /** + * 退款 + */ + public static RefundInfo refund(IChannelInfo channel,RefundAble refundAble) { + HashMap body = new HashMap<>(); + body.put("refundFee", String.valueOf(refundAble.getAmount())); + body.put("terminalType", "1"); + body.put("outTradeId", refundAble.getOutTradeNo()); + body.put("shopId", channel.getShopId()); + String response = doPost(channel.getHttpUrl() + "/open/Pay/refund", body,channel); + if (com.ruoyi.common.utils.StringUtils.hasText(response)) { + JSONObject jsonResponse = JSON.parseObject(response); + if (jsonResponse.getInteger("errCode") == 0 && "退款成功".equals(jsonResponse.getString("errMsg"))) { + JSONObject tradeInfo = jsonResponse.getJSONObject("refundInfo"); + RefundInfo refundInfo = tradeInfo.toJavaObject(RefundInfo.class); + return refundInfo; + } else { + throw new ServiceException("退款失败: " + jsonResponse.getString("errMsg")); + } + } + throw new ServiceException("退款数据为空"); + } + + /** + * 关闭订单 + */ + public static void closeOrder(IChannelInfo channel, String outTradeNo) { + HashMap body = new HashMap<>(); + body.put("outTradeId", outTradeNo); + body.put("terminalType", "1"); + body.put("shopId", channel.getShopId()); + doPost(channel.getHttpUrl() + "/open/Pay/orderClose", body,channel); + } + + /** + * 小程序支付 + */ + public static PrepayWithRequestPaymentResponse pay(IChannelInfo channel, Payable payable) { + HashMap body = new HashMap<>(); + body.put("payAmount", String.valueOf(payable.getAmount())); + body.put("terminalType", "1"); + body.put("shopId", channel.getShopId()); // 从渠道获取shopId + body.put("sn", channel.getSn()); + body.put("payType", "wx_pay"); + body.put("outTradeId", payable.getOutTradeNo()); + body.put("body", payable.getDescription()); + body.put("notifyUrl", channel.getNotifyUrl()); + body.put("openid", payable.getOpenid()); + String response = doPost(channel.getHttpUrl() + "/open/Pay/miniPay", body, channel); + + if (com.ruoyi.common.utils.StringUtils.hasText(response)) { + // 解析 response,提取 "params" 字段 + JSONObject jsonResponse = JSON.parseObject(response); + if (jsonResponse.getInteger("errCode") == 0 && "ok".equals(jsonResponse.getString("errMsg"))) { + // 提取 "params" 并转成 PrepayWithRequestPaymentResponse 对象 + JSONObject params = jsonResponse.getJSONObject("params"); + PrepayWithRequestPaymentResponse paymentResponse = params.toJavaObject(PrepayWithRequestPaymentResponse.class); + return paymentResponse; + } else { + throw new ServiceException("支付失败: " + jsonResponse.getString("errMsg")); + } + } + throw new ServiceException("支付数据为空"); + } + + + + private static String doPost(String url, HashMap body, IChannelInfo channel) { + body.put("developerId", channel.getDeveloperId()); + body.put("version", "1.0"); + body.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000)); + body.put("nonceStr", TmPayUtil.getRandomString(16)); + + String bodyStr = TmPayUtil.getAsciiSort(body); + String sign = TmPayUtil.getMD5Code(bodyStr + "&key=" + channel.getSignKey()).toUpperCase(); + body.put("sign", sign); + + HashMap headerData = new HashMap<>(); + headerData.put("Content-Type", "application/json"); + + String response = HttpUtils.sendPostWithHeaders(url, headerData, JSON.toJSONString(body)); + return response; + } + + public boolean validSign(Map params) { + // 获取传递过来的签名 + String receivedSign = (String)params.get("sign"); + // 获取签名字段 + System.out.println("获取到的签名-------------:"+receivedSign); + if (receivedSign == null) { + return false; // 如果没有传递签名,验签失败 + } + // 移除签名字段后,重新生成签名 + params.remove("sign"); + // 按照请求时的签名逻辑,生成签名字符串 + String paramsStr = TmPayUtil.getAsciiSort(params); // 按ASCII排序 + String generatedSign = TmPayUtil.getMD5Code(paramsStr + "&key=" + SIGNKEY).toUpperCase(); // 重新生成签名 + System.out.println("新生成的签名-----------:"+generatedSign); + // 比较签名是否一致 + return generatedSign.equals(receivedSign); + } + +} diff --git a/electripper-common/src/main/java/com/ruoyi/common/pay/tm/TmPayUtil.java b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/TmPayUtil.java new file mode 100644 index 0000000..4f375c6 --- /dev/null +++ b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/TmPayUtil.java @@ -0,0 +1,131 @@ +package com.ruoyi.common.pay.tm; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; +import java.util.Map.Entry; + +public class TmPayUtil { + // 全局数组 + private final static String[] strDigits = { "0", "1", "2", "3", "4", "5", + "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; + + private static Random random = null; + + // 返回形式为数字跟字符串 + public static String byteToArrayString(byte bByte) { + int iRet = bByte; + if (iRet < 0) { + iRet += 256; + } + int iD1 = iRet / 16; + int iD2 = iRet % 16; + return strDigits[iD1] + strDigits[iD2]; + } + + // 返回形式只为数字 + public static String byteToNum(byte bByte) { + int iRet = bByte; + if (iRet < 0) { + iRet += 256; + } + return String.valueOf(iRet); + } + + // 转换字节数组为16进制字串 + public static String byteToString(byte[] bByte) { + StringBuffer sBuffer = new StringBuffer(); + for (int i = 0; i < bByte.length; i++) { + sBuffer.append(byteToArrayString(bByte[i])); + } + return sBuffer.toString(); + } + + /** + * md5 加密 + * @param strObj + * @return + */ + public static String getMD5Code(String strObj) { + String resultString = null; + try { + resultString = new String(strObj); + MessageDigest md = MessageDigest.getInstance("MD5"); + // md.digest() 该函数返回值为存放哈希值结果的byte数组 + resultString = byteToString(md.digest(strObj.getBytes())); + } catch (NoSuchAlgorithmException ex) { + ex.printStackTrace(); + } + return resultString; + } + + + + /** + * 获取随机数 + * @param length + * @return + */ + public static String getRandomString(int length) { + // 定义一个字符串(A-Z,a-z1-9)即62位; + String str = "zxcvbnmlkjhgfdsaqwertyuiopQWERTYUIOPASDFGHJKLZXCVBNM1234567890"; + // 由Random生成随机数 + if (random == null) { + random = new Random(); + } + StringBuffer sb = new StringBuffer(); + // 长度为几就循环几次 + for (int i = 0; i < length; ++i) { + // 产生0-61的数字 + int number = random.nextInt(62); + // 将产生的数字通过length次承载到sb中 + sb.append(str.charAt(number)); + } + // 将承载的字符转换成字符串 + return sb.toString(); + } + + public static String getUUID() { + return UUID.randomUUID().toString(); + } + + public static String getUUIDNoLine() { + String s = UUID.randomUUID().toString(); + return s.substring(0, 8) + s.substring(9, 13) + s.substring(14, 18) + s.substring(19, 23) + s.substring(24); + } + + /** + * 参数名ASCII码从小到大排序(字典序) + * @param map + * @return + */ + public static String getAsciiSort(Map map) { + List> infoIds = new ArrayList<>(map.entrySet()); + + // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序) + Collections.sort(infoIds, new Comparator>() { + public int compare(Entry o1, Entry o2) { + return o1.getKey().compareToIgnoreCase(o2.getKey()); + } + }); + + // 构造签名键值对的格式 + StringBuilder sb = new StringBuilder(); + for (Entry item : infoIds) { + String key = item.getKey(); + Object val = item.getValue(); + + if (key != null && !key.isEmpty() && val != null) { + sb.append(key).append("=").append(val.toString()).append("&"); + } + } + + if (sb.length() > 0 && sb.charAt(sb.length() - 1) == '&') { + sb.deleteCharAt(sb.length() - 1); + } + + return sb.toString(); + } + + +} diff --git a/electripper-common/src/main/java/com/ruoyi/common/pay/tm/enums/PayStatus.java b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/enums/PayStatus.java new file mode 100644 index 0000000..ec1b55a --- /dev/null +++ b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/enums/PayStatus.java @@ -0,0 +1,63 @@ +package com.ruoyi.common.pay.tm.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 交易结果 + * @author qzz + * 2024/9/24 + */ +@Getter +@AllArgsConstructor +public enum PayStatus { + + SUCCESS("SUCCESS", "支付成功"), + NOTPAY("NOTPAY", "未支付"), + CLOSE("CLOSE", "已关闭"), + REVOKED("REVOKED", "已撤销"), + USERPAYING("USERPAYING", "用户支付中"), + PAYERROR("PAYERROR", "支付失败"), + REFUND("REFUND", "转入退款"), + OPERATE_SUCCESS("OPERATE_SUCCESS", "预授权请求操作成功"), + OPERATE_FAIL("OPERATE_FAIL", "预授权请求操作失败"), + OPERATE_SETTLING("OPERATE_SETTLING", "押金消费已受理"), + REVOKED_SUCCESS("REVOKED_SUCCESS", "预授权请求撤销成功"); + + private final String code; + private final String description; + + /** + * 判断支付状态是否成功 + * @param code 支付状态码 + * @return 是否成功 + */ + public static boolean isSuccess(String code) { + return SUCCESS.getCode().equals(code); + } + + /** + * 判断支付状态是否转入退款 + * @param code 支付状态码 + * @return 是否成功 + */ + public static boolean isRefund(String code) { + return REFUND.getCode().equals(code); + } + + /** + * 根据状态码获取对应的支付状态 + * @param code 支付状态码 + * @return 对应的PayStatus枚举 + */ + public static PayStatus getByCode(String code) { + for (PayStatus status : PayStatus.values()) { + if (status.getCode().equals(code)) { + return status; + } + } + return null; + } +} + + diff --git a/electripper-common/src/main/java/com/ruoyi/common/pay/tm/enums/RefundStatus.java b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/enums/RefundStatus.java new file mode 100644 index 0000000..5f99ef7 --- /dev/null +++ b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/enums/RefundStatus.java @@ -0,0 +1,42 @@ +package com.ruoyi.common.pay.tm.enums; + +import lombok.Getter; +import lombok.AllArgsConstructor; + +/** + * 退款状态枚举 + * @author + * 2024/09/24 + */ +@Getter +@AllArgsConstructor +public enum RefundStatus { + FAILED(0, "退款失败"), + REFUNDED(1, "转入退款"); + + private final int code; + private final String description; + + /** + * 根据状态码判断是否退款成功 + * @param code 状态码 + * @return 是否退款成功 + */ + public static boolean isSuccess(int code) { + return REFUNDED.getCode() == code; + } + + /** + * 根据状态码获取对应的退款状态枚举 + * @param code 状态码 + * @return 对应的 RefundStatus 枚举 + */ + public static RefundStatus getByCode(int code) { + for (RefundStatus status : RefundStatus.values()) { + if (status.getCode() == code) { + return status; + } + } + return null; + } +} diff --git a/electripper-common/src/main/java/com/ruoyi/common/pay/tm/vo/RefundInfo.java b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/vo/RefundInfo.java new file mode 100644 index 0000000..87d0285 --- /dev/null +++ b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/vo/RefundInfo.java @@ -0,0 +1,31 @@ +package com.ruoyi.common.pay.tm.vo; + +import com.ruoyi.common.pay.tm.enums.RefundStatus; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 退款信息对象 + * @author + * 2024/09/24 + */ +@Data +public class RefundInfo { + private String id; // 太米系统退款记录Id + private Long tradeInfoId; // 太米系统流水Id + private String module; // 模块: pay, mall, recharge, become_member, eatIn, takeOut, selfTake, payment_card, times_card + private String remark; // 备注 + private Long merchantId; // 品牌Id + private String shopId; // 门店Id + private Long merchantUserId; // 门店员工Id + private Long codeId; // 款台码Id + private Long memberInfoId; // 会员Id + private Long workRecordId; // 交班记录Id + private String fromType; // 订单来源: wx, alipay, web, mini, pos, pc, desktop, api + private String refundTime; // 退款成功时间 + private String refundMessage; // 退款失败原因 + private RefundStatus refundStatus; // 退款状态: RefundStatus 枚举 + private String refundAmount; // 已退款金额 + private String createTime; // 创建时间 +} diff --git a/electripper-common/src/main/java/com/ruoyi/common/pay/tm/vo/TmTradeInfo.java b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/vo/TmTradeInfo.java new file mode 100644 index 0000000..d6a1fbd --- /dev/null +++ b/electripper-common/src/main/java/com/ruoyi/common/pay/tm/vo/TmTradeInfo.java @@ -0,0 +1,181 @@ +package com.ruoyi.common.pay.tm.vo; + +import com.ruoyi.common.pay.tm.enums.PayStatus; +import lombok.Data; + +@Data +public class TmTradeInfo { + + /** + * 太米系统流水Id + */ + private Integer id; + + /** + * 第三方内部流水号 + */ + private String outTradeId; + + /** + * 微信或支付宝或银联的订单号。个别支付渠道可能无此参数 + */ + private String transactionId; + + /** + * 太米商户订单号 + */ + private String orderNum; + + /** + * 支付凭证条码,个别支付渠道可能返回空 + */ + private String barCode; + + /** + * 模块:pay:收银;mall:优选卡券货架;recharge:充值; + * become_member:会员购买;eatIn:店内下单; + * takeOut:外送订单;selfTake:预约自取; + * payment_card:付费卡券;times_card:次/月卡 + */ + private String module; + + /** + * 备注 + */ + private String remark; + + /** + * 品牌Id + */ + private Integer merchantId; + + /** + * 门店Id + */ + private Integer shopId; + + /** + * 门店员工id + */ + private Integer merchantUserId; + + /** + * 款台码Id + */ + private Integer codeId; + + /** + * 会员Id + */ + private Integer memberInfoId; + + /** + * 交班记录id + */ + private Integer workRecordId; + + /** + * 订单来源:wx:微信收款码;alipay:支付宝收款码; + * web:Web页面;mini:小程序;pos:POS机; + * pc:PC;desktop:台式消费机;api:开放平台接口 + */ + private String fromType; + + /** + * 订单总额,单位分 + */ + private Integer orderAmount; + + /** + * 会员优惠,单位分 + */ + private Integer memberCoupon; + + /** + * 活动优惠,单位分 + */ + private Integer activityCoupon; + + /** + * 卡券优惠,单位分 + */ + private Integer cardCoupon; + + /** + * 积分抵扣,单位分 + */ + private Integer pointDeduction; + + /** + * 实收金额,单位分 + */ + private Integer incomeAmount; + + /** + * 支付方式:wx_pay:微信支付;ali_pay:支付宝; + * union_offline:银行卡;union_qrcode:银联扫码; + * union_online:银联钱包;member_wallet:会员钱包;cash:现金 + */ + private String payType; + + /** + * 1、已支付 0、未支付 + */ + private Integer isPaid; + + /** + * 支付状态: + * SUCCESS:支付成功;NOTPAY:未支付;CLOSE:已关闭; + * REVOKED:已撤销;USERPAYING:用户支付中; + * PAYERROR:支付失败;REFUND:转入退款; + * OPERATE_SUCCESS:预授权请求操作成功、OPERATE_FAIL:预授权请求操作失败; + * OPERATE_SETTLING:押金消费已受理; + * REVOKED_SUCCESS:预授权请求撤销成功 + */ + private PayStatus payStatus; + + /** + * 支付时间 + */ + private String payTime; + + /** + * 支付失败原因 + */ + private String payError; + + /** + * 退款状态:0表示未退款,1表示转入退款 + */ + private Integer refundStatus; + + /** + * 已退款金额,单位分 + */ + private String refundAmount; + + /** + * 终端sn号 + */ + private String sn; + + /** + * 应结订单金额(微信代金券用) + */ + private String settlementTotalFee; + + /** + * 0 : 普通订单 1:刷脸设备刷脸支付 2 : 刷脸设备扫码支付 3 : 刷脸设备会员钱包支付 + */ + private String isFromFacePay; + + /** + * 总代金券金额(微信代金券用) + */ + private String couponFee; + + /** + * 创建时间 + */ + private String createTime; +} diff --git a/electripper-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java b/electripper-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java index 9f75549..1725d41 100644 --- a/electripper-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java +++ b/electripper-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java @@ -11,6 +11,7 @@ import java.net.URL; import java.net.URLConnection; import java.nio.charset.StandardCharsets; import java.security.cert.X509Certificate; +import java.util.Map; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; @@ -211,6 +212,66 @@ public class HttpUtils } } + /** + * 向指定 URL 发送 POST 方法的请求,并支持自定义请求头和 JSON 请求体 + * + * @param url 发送请求的 URL + * @param headerData 请求头信息,键值对形式 + * @param body 请求体,通常为 JSON 格式的字符串 + * @return 所代表远程资源的响应结果 + */ + public static String sendPostWithHeaders(String url, Map headerData, String body) { + log.info("body------- - {}", body); + PrintWriter out = null; + BufferedReader in = null; + StringBuilder result = new StringBuilder(); + try { + log.info("sendPostWithHeaders - {}", url); + URL realUrl = new URL(url); + URLConnection conn = realUrl.openConnection(); + + // 设置请求头信息 + for (Map.Entry entry : headerData.entrySet()) { + conn.setRequestProperty(entry.getKey(), entry.getValue()); + } + conn.setDoOutput(true); + conn.setDoInput(true); + // 发送 POST 请求体数据 + out = new PrintWriter(conn.getOutputStream()); + out.print(body); // 发送 JSON 格式的 body + out.flush(); + + // 读取响应数据 + in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)); + String line; + while ((line = in.readLine()) != null) { + result.append(line); + } + log.info("recv - {}", result); + } catch (ConnectException e) { + log.error("调用HttpUtils.sendPostWithHeaders ConnectException, url=" + url + ", body=" + body, e); + } catch (SocketTimeoutException e) { + log.error("调用HttpUtils.sendPostWithHeaders SocketTimeoutException, url=" + url + ", body=" + body, e); + } catch (IOException e) { + log.error("调用HttpUtils.sendPostWithHeaders IOException, url=" + url + ", body=" + body, e); + } catch (Exception e) { + log.error("调用HttpUtils.sendPostWithHeaders Exception, url=" + url + ", body=" + body, e); + } finally { + try { + if (out != null) { + out.close(); + } + if (in != null) { + in.close(); + } + } catch (IOException ex) { + log.error("调用in.close Exception, url=" + url + ", body=" + body, ex); + } + } + return result.toString(); + } + + /** * 向指定 URL 发送POST方法的请求 * diff --git a/electripper-system/src/main/java/com/ruoyi/system/domain/Channel.java b/electripper-system/src/main/java/com/ruoyi/system/domain/Channel.java index 2ad85f6..9a9a430 100644 --- a/electripper-system/src/main/java/com/ruoyi/system/domain/Channel.java +++ b/electripper-system/src/main/java/com/ruoyi/system/domain/Channel.java @@ -64,4 +64,16 @@ public class Channel extends BaseEntity /** appid */ private String appid; + private String developerId; + + /** 门店Id */ + private String shopId; + + private String httpUrl; + + private String signKey; + + /** 终端sn */ + private String sn; + } diff --git a/electripper-system/src/main/java/com/ruoyi/system/domain/ChannelVO.java b/electripper-system/src/main/java/com/ruoyi/system/domain/ChannelVO.java index 4917c5f..538834c 100644 --- a/electripper-system/src/main/java/com/ruoyi/system/domain/ChannelVO.java +++ b/electripper-system/src/main/java/com/ruoyi/system/domain/ChannelVO.java @@ -1,5 +1,6 @@ package com.ruoyi.system.domain; +import com.ruoyi.common.pay.tm.IChannelInfo; import lombok.Data; /** @@ -7,5 +8,6 @@ import lombok.Data; * 2024/7/28 */ @Data -public class ChannelVO extends Channel{ +public class ChannelVO extends Channel implements IChannelInfo { + } diff --git a/electripper-system/src/main/java/com/ruoyi/system/service/impl/CallbackServiceImpl.java b/electripper-system/src/main/java/com/ruoyi/system/service/impl/CallbackServiceImpl.java index c63622d..5c09d83 100644 --- a/electripper-system/src/main/java/com/ruoyi/system/service/impl/CallbackServiceImpl.java +++ b/electripper-system/src/main/java/com/ruoyi/system/service/impl/CallbackServiceImpl.java @@ -187,8 +187,8 @@ public class CallbackServiceImpl implements CallbackService { EtOrder order = orderService.selectEtOrderByOutTradeNo(outTradeNo); EtOrder order1 = new EtOrder(); order1.setOrderId(order.getOrderId()); - logger.info("【微信支付回调】订单信息 : " + JSON.toJSONString(order)); - logger.info("【微信支付回调】========== orderId : " + order.getOrderId()); + logger.info("【微信/太米支付回调】订单信息 : " + JSON.toJSONString(order)); + logger.info("【微信/太米支付回调】========== orderId : " + order.getOrderId()); AsUser asUser = asUserMapper.selectUserById(order.getUserId()); /** 支付回调逻辑 1. 处理预约还是开锁 电压 */ @@ -197,13 +197,13 @@ public class CallbackServiceImpl implements CallbackService { asDevice = asDeviceMapper.selectAsDeviceBySn(order.getSn()); } if(attachVo.getType().equals(ServiceConstants.BUSINESS_TYPE_RIDING)){ - logger.info("【微信支付回调】骑行支付"); + logger.info("【微信/太米支付回调】骑行支付"); // 1-骑行支付 关锁 EtOperatingArea area = etOperatingAreaService.selectEtOperatingAreaByAreaId(order.getAreaId()); order1.setMark("骑行支付"); logger.info("=================【微信支付回调】11111111=================="); if(ServiceConstants.RETURN_VERIFY_YES.equals(area.getReturnVerify())){ - logger.info("【微信支付回调】还车-----需要-----拍照审核"); + logger.info("【微信/太米支付回调】还车-----需要-----拍照审核"); order1.setStatus(ServiceConstants.ORDER_STATUS_TO_BE_AUDIT);//如果还车需要拍照审核,状态为待审核 BigDecimal amount = order.getPayFee(); @@ -212,13 +212,13 @@ public class CallbackServiceImpl implements CallbackService { asynchronousMsg(order, amount); } }else{ - logger.info("【微信支付回调】还车-----不需要-----拍照审核"); + logger.info("【微信/太米支付回调】还车-----不需要-----拍照审核"); order1.setStatus(ServiceConstants.ORDER_STATUS_ORDER_END); // 还车结算___小时后自动退押金---创建一个定时器TimerTask,计算出退还时间后,执行退款操作 - logger.info("=================【微信支付回调】22222222=================="); + logger.info("=================【微信/太米支付回调】22222222=================="); // 退还押金处理 refundDeposit(area.getDeposit(), order, asUser); - logger.info("=================【微信支付回调】33333333=================="); + logger.info("=================【微信/太米支付回调】33333333=================="); // 用户付款通知 if("1".equals(area.getMsgSwitch())){ asynchronousMsg2(order); @@ -228,14 +228,14 @@ public class CallbackServiceImpl implements CallbackService { asDevice.setLockStatus(ServiceConstants.LOCK_STATUS_CLOSE); // 新增资金流水记录 EtCapitalFlow capitalFlow = capitalFlowRecords(order, ServiceConstants.FLOW_TYPE_INCOME, ServiceConstants.ORDER_TYPE_RIDING, ServiceConstants.OWNER_TYPE_OPERATOR, null, ServiceConstants.PAY_TYPE_WX); - logger.info("=================【骑行支付回调-新增资金流水记录后】=================={}",JSON.toJSON(capitalFlow)); + logger.info("=================【微信/太米支付回调-新增资金流水记录后】=================={}",JSON.toJSON(capitalFlow)); order1.setHandlingCharge(capitalFlow.getHandlingCharge()); order1.setPlatformServiceFee(capitalFlow.getPlatformServiceFee()); order1.setOperatorDividend(capitalFlow.getOperatorDividend()); order1.setCost(getCost(order.getPayFee())); - logger.info("=================【微信支付回调】4444444=================="); + logger.info("=================【微信/太米支付回调】4444444=================="); }else if(attachVo.getType().equals(ServiceConstants.BUSINESS_TYPE_APPOINTMENT)){ - logger.info("【微信支付回调】取消预约支付"); + logger.info("【微信/太米支付回调】取消预约支付"); // 2-取消预约支付 order1.setStatus(ServiceConstants.ORDER_STATUS_ORDER_END); order1.setMark("取消预约支付"); @@ -243,34 +243,34 @@ public class CallbackServiceImpl implements CallbackService { asDevice.setLockStatus(ServiceConstants.LOCK_STATUS_CLOSE); }else if(attachVo.getType().equals(ServiceConstants.ORDER_TYPE_COUPON)){ /** 优惠券订单 */ - logger.info("【微信支付回调】优惠券支付"); + logger.info("【微信/太米支付回调】优惠券支付"); // 3-优惠券支付 order1.setStatus(ServiceConstants.ORDER_STATUS_ORDER_END); order1.setMark("优惠券支付"); // 优惠券成功处理逻辑 couponSuccessHandle(order); }else if(attachVo.getType().equals(ServiceConstants.BUSINESS_TYPE_DEPOSIT)){ - logger.info("【微信支付回调】押金支付"); + logger.info("【微信/太米支付回调】押金支付"); // 4-押金支付 order1.setStatus(ServiceConstants.ORDER_STATUS_ORDER_END); asUser.setBalance(order.getTotalFee()); order1.setMark("押金支付"); // 删除用户缓存 String token = attachVo.getToken(); - logger.info("【微信支付回调】删除用户缓存:"+token); + logger.info("【微信/太米支付回调】删除用户缓存:"+token); if (StringUtils.isNotNull(token)) { redisCache.deleteObject(CacheConstants.LOGIN_TOKEN_KEY + token); } }else{ - logger.error("【微信支付回调】 : 支付场景不存在"); - throw new ServiceException("【微信支付回调】支付场景不存在"); + logger.error("【微信/太米支付回调】 : 支付场景不存在"); + throw new ServiceException("【微信/太米支付回调】支付场景不存在"); } if(ObjectUtil.isNotNull(asDevice)){ int device = asDeviceService.updateAsDevice(asDevice); if(device==0){ - logger.error("【微信支付回调】更新车辆状态失败"); - throw new ServiceException("【微信支付回调】更新车辆状态失败"); + logger.error("【微信/太米支付回调】更新车辆状态失败"); + throw new ServiceException("【微信/太米支付回调】更新车辆状态失败"); } } @@ -283,27 +283,27 @@ public class CallbackServiceImpl implements CallbackService { if(ObjectUtil.isNotNull(order.getLogId())){ EtCouponUserLog couponUserLog = etCouponClaimLogMapper.selectEtCouponClaimLogByLogId(order.getLogId()); EtCoupon etCoupon = etCouponMapper.selectEtCouponByCouponId(couponUserLog.getCouponId()); - logger.info("【微信支付回调】优惠券信息 : " + JSON.toJSONString(etCoupon)); + logger.info("【微信/太米支付回调】优惠券信息 : " + JSON.toJSONString(etCoupon)); if(ObjectUtil.isNotNull(etCoupon) && (etCoupon.getType().equals(ServiceConstants.COUPON_TYPE_DISCOUNT_CARD) || etCoupon.getType().equals(ServiceConstants.COUPON_TYPE_VOUCHER)) && couponUserLog.getLimitNum() > 0){ etCouponClaimLogMapper.deductLimitNum(couponUserLog.getLogId()); - logger.info("【微信支付回调】优惠券使用次数-1"); + logger.info("【微信/太米支付回调】优惠券使用次数-1"); } } - logger.info("=================【微信支付回调】开始更新订单信息=================={}",JSON.toJSON(order1)); + logger.info("=================【微信/太米支付回调】开始更新订单信息=================={}",JSON.toJSON(order1)); int updateEtOrder = orderService.updateEtOrder(order1); if(updateEtOrder==0){ - logger.error("【微信支付回调】更新订单信息失败"); - throw new ServiceException("【微信支付回调】更新订单信息失败"); + logger.error("【微信/太米支付回调】更新订单信息失败"); + throw new ServiceException("【微信/太米支付回调】更新订单信息失败"); } if(attachVo.getType().equals(ServiceConstants.BUSINESS_TYPE_DEPOSIT)){ - logger.info("=================【微信支付回调】开始更新用户信息=================="); + logger.info("=================【微信/太米支付回调】开始更新用户信息=================="); int updateUser = userService.updateUser(asUser); if(updateUser==0){ - logger.error("【微信支付回调】更新用户押金失败"); - throw new ServiceException("【微信支付回调】更新用户押金失败"); + logger.error("【微信/太米支付回调】更新用户押金失败"); + throw new ServiceException("【微信/太米支付回调】更新用户押金失败"); } } - logger.info("=================【微信支付回调】全部结束!!!!!=================="); + logger.info("=================【微信/太米支付回调】全部结束!!!!!=================="); return Boolean.TRUE; }); if(!execute)throw new ServiceException("管理员开锁失败"); diff --git a/electripper-system/src/main/java/com/ruoyi/system/service/impl/EtOrderServiceImpl.java b/electripper-system/src/main/java/com/ruoyi/system/service/impl/EtOrderServiceImpl.java index 9538f09..d91c999 100644 --- a/electripper-system/src/main/java/com/ruoyi/system/service/impl/EtOrderServiceImpl.java +++ b/electripper-system/src/main/java/com/ruoyi/system/service/impl/EtOrderServiceImpl.java @@ -13,8 +13,9 @@ import com.ruoyi.common.core.domain.entity.SysDept; import com.ruoyi.common.enums.BusinessStatus; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.pay.PaymentResult; -import com.ruoyi.common.pay.syb.enums.SybTrxStatus; -import com.ruoyi.common.pay.syb.service.SybPayService; +import com.ruoyi.common.pay.tm.TmPayService; +import com.ruoyi.common.pay.tm.enums.PayStatus; +import com.ruoyi.common.pay.tm.vo.TmTradeInfo; import com.ruoyi.common.utils.CommonUtil; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.PageUtils; @@ -131,7 +132,7 @@ public class EtOrderServiceImpl implements IEtOrderService private EtChannelService etChannelService; @Autowired - private SybPayService sybPayService; + private TmPayService tmPayService; /** * 查询订单 @@ -531,13 +532,13 @@ public class EtOrderServiceImpl implements IEtOrderService log.info("【押金抵扣】订单【{}】,有outTradeNo = 【{}】,查询订单未支付,关闭订单:{}", order.getOrderNo(),outTradeNo,b); } }else{ - Map result = paymentResult.getResult(); - if(SybTrxStatus.isSuccess(result.get("trxstatus"))) { + TmTradeInfo tradeInfo = paymentResult.getTradeInfo(); + if(PayStatus.isSuccess(tradeInfo.getPayStatus().getCode())) { handleSuccess(order); return 1; }else{ // 没有支付,则关闭订单 - sybPayService.closeOrderWx(outTradeNo); + tmPayService.closeOrder(channelVO,outTradeNo); log.info("【押金抵扣】订单【{}】,有outTradeNo = 【{}】,查询订单未支付,关闭订单:{}", order.getOrderNo(),outTradeNo); } } diff --git a/electripper-system/src/main/java/com/ruoyi/system/service/impl/WxPayService.java b/electripper-system/src/main/java/com/ruoyi/system/service/impl/WxPayService.java index 35cde1d..2cc3c22 100644 --- a/electripper-system/src/main/java/com/ruoyi/system/service/impl/WxPayService.java +++ b/electripper-system/src/main/java/com/ruoyi/system/service/impl/WxPayService.java @@ -12,8 +12,11 @@ import com.ruoyi.common.core.redis.RedisLock; import com.ruoyi.common.enums.PayChannel; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.pay.PaymentResult; -import com.ruoyi.common.pay.syb.enums.SybTrxStatus; -import com.ruoyi.common.pay.syb.service.SybPayService; +import com.ruoyi.common.pay.tm.TmPayService; +import com.ruoyi.common.pay.tm.enums.PayStatus; +import com.ruoyi.common.pay.tm.enums.RefundStatus; +import com.ruoyi.common.pay.tm.vo.RefundInfo; +import com.ruoyi.common.pay.tm.vo.TmTradeInfo; import com.ruoyi.common.pay.wx.Payable; import com.ruoyi.common.pay.wx.RefundAble; import com.ruoyi.common.utils.DateUtils; @@ -33,14 +36,11 @@ import com.wechat.pay.java.service.payments.jsapi.JsapiService; import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension; import com.wechat.pay.java.service.payments.jsapi.model.*; import com.wechat.pay.java.service.payments.model.Transaction; -import com.wechat.pay.java.service.profitsharing.ProfitsharingService; -import com.wechat.pay.java.service.profitsharing.model.*; import com.wechat.pay.java.service.refund.RefundService; import com.wechat.pay.java.service.refund.model.AmountReq; import com.wechat.pay.java.service.refund.model.CreateRequest; import com.wechat.pay.java.service.refund.model.QueryByOutRefundNoRequest; import com.wechat.pay.java.service.refund.model.Refund; -import com.wechat.pay.java.service.transferbatch.TransferBatchService; import com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferRequest; import com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferResponse; import com.wechat.pay.java.service.transferbatch.model.TransferDetailInput; @@ -50,9 +50,7 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.math.BigDecimal; -import java.math.RoundingMode; import java.util.List; -import java.util.Map; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -84,7 +82,7 @@ public class WxPayService implements IWxPayService { private AsUserMapper asUserMapper; @Autowired - private SybPayService sybPayService; + private TmPayService tmPayService; @Autowired private EtChannelService etChannelService; @@ -119,11 +117,20 @@ public class WxPayService implements IWxPayService { throw new ServiceException("运营商【"+sysDept.getDeptName()+"】没有支付渠道"); } ChannelVO channelVO = etChannelService.selectSmChannelByChannelId(payChannel); + log.info("支付渠道------"+JSON.toJSON(channelVO)); String outTradeNo = null; if(PayChannel.CT_WX.equalsCode(channelVO.getCode()) || PayChannel.YS_WX.equalsCode(channelVO.getCode())){ outTradeNo = IdUtils.getOrderNo("wx"); - }else if(PayChannel.TL_WX.equalsCode(channelVO.getCode())){ - outTradeNo = IdUtils.getOrderNo("tlwx"); + }else if(PayChannel.TM_WX.equalsCode(channelVO.getCode())){ + if(payType.equals(ServiceConstants.ORDER_TYPE_RIDING)){ + outTradeNo = IdUtils.getOrderNo("tmwx"); + } else if(payType.equals(ServiceConstants.BUSINESS_TYPE_DEPOSIT)){ + outTradeNo = IdUtils.getOrderNo("tmyj"); + }else if(payType.equals(ServiceConstants.ORDER_TYPE_COUPON)){ + outTradeNo = IdUtils.getOrderNo("tmyhj"); + }else{ + throw new ServiceException("暂不支持该支付场景"); + } } String type = order.getType(); String description = type.equals(ServiceConstants.ORDER_TYPE_RIDING) ? "骑行订单-"+billNo : "押金充值-"+billNo; @@ -147,7 +154,6 @@ public class WxPayService implements IWxPayService { request.setMchid(channelVO.getMerchantId()); request.setAttach(JSON.toJSONString(new AttachVo(payType,user.getUserId(), ""))); request.setDescription(description); - log.info("支付渠道------"+JSON.toJSON(channelVO)); request.setNotifyUrl(channelVO.getNotifyUrl()); request.setPayer(getPayer(user.getWxopenid())); JsapiServiceExtension jsapiServiceExtension = getJsapiServiceExtension(channelVO); @@ -162,12 +168,12 @@ public class WxPayService implements IWxPayService { jsapiServiceExtension.closeOrder(closeOrderRequest); } return res; - }else if(PayChannel.TL_WX.equalsCode(channelVO.getCode())){ - log.info("----------{}-------------","通联微信支付"); + }else if(PayChannel.TM_WX.equalsCode(channelVO.getCode())){ + log.info("----------{}-------------","太米微信支付"); if(StrUtil.isNotBlank(order.getOutTradeNo())){ // 关闭订单 - sybPayService.closeOrderWx(order.getOutTradeNo()); + tmPayService.closeOrder(channelVO,order.getOutTradeNo()); } Payable payable = new Payable(); payable.setAmount(order.getPayFee().multiply(new BigDecimal(100)).longValue()); @@ -176,7 +182,7 @@ public class WxPayService implements IWxPayService { payable.setDescription(description); payable.setOpenid(user.getWxopenid()); payable.setAppid(channelVO.getAppid()); - PrepayWithRequestPaymentResponse res = sybPayService.prepayWxApp(payable); + PrepayWithRequestPaymentResponse res = tmPayService.pay(channelVO,payable); return res; }else{ throw new ServiceException("支付渠道【"+channelVO.getCode()+"】暂不支持"); @@ -247,13 +253,13 @@ public class WxPayService implements IWxPayService { } return res; - }else if(PayChannel.TL_WX.equalsCode(channelVO.getCode())){ - log.info("----优惠券------{}-------------","通联微信支付"); + }else if(PayChannel.TM_WX.equalsCode(channelVO.getCode())){ + log.info("----优惠券------{}-------------","太米微信支付"); // 获取JSAPI所需参数 if(StrUtil.isNotBlank(order.getOutTradeNo())){ // 关闭订单 - sybPayService.closeOrderWx(order.getOutTradeNo()); + tmPayService.closeOrder(channelVO,order.getOutTradeNo()); } Payable payable = new Payable(); @@ -262,7 +268,7 @@ public class WxPayService implements IWxPayService { payable.setAttach(JSON.toJSONString(new AttachVo(order.getType(),user.getUserId(), ""))); payable.setDescription(description); payable.setOpenid(user.getWxopenid()); - PrepayWithRequestPaymentResponse res = sybPayService.prepayWxApp(payable); + PrepayWithRequestPaymentResponse res = tmPayService.pay(channelVO,payable); return res; }else{ throw new ServiceException("支付渠道【"+channelVO.getCode()+"】暂不支持"); @@ -313,13 +319,10 @@ public class WxPayService implements IWxPayService { log.info("微信查询订单信息outTradeNo={}-----【{}】",outTradeNo,JSON.toJSON(transaction)); paymentResult1.setTransaction(transaction); return paymentResult1; - }else if(PayChannel.TL_WX.equalsCode(channelVO.getCode())){ - Map result = sybPayService.queryOrderByOutTradeNo(order.getOutTradeNo()); - if(SybTrxStatus.isSuccess(result.get("trxstatus"))) { - paymentResult1.setResult(result); - return paymentResult1; - } - return null; + }else if(PayChannel.TM_WX.equalsCode(channelVO.getCode())){ + TmTradeInfo tmTradeInfo = tmPayService.orderQuery(channelVO, order.getOutTradeNo()); + paymentResult1.setTradeInfo(tmTradeInfo); + return paymentResult1; }else{ throw new ServiceException("支付渠道【"+channelVO.getCode()+"】暂不支持"); } @@ -362,35 +365,16 @@ public class WxPayService implements IWxPayService { // 订单未支付并且微信支付结果是成功的情况下,更新订单状态和用户余额 if(transaction.getTradeState().equals(Transaction.TradeStateEnum.SUCCESS)){ if(order.getPaid().equals(ServiceConstants.ORDER_PAY_STATUS_NON_PAYMENT)){ - order.setPaid("1"); - order.setPayTime(DateUtils.parseTime(transaction.getSuccessTime())); - order.setPayType(ServiceConstants.PAY_TYPE_WX); - log.info("【主动查询】押金支付"); - order.setStatus(ServiceConstants.ORDER_STATUS_ORDER_END); - if(ServiceConstants.ORDER_TYPE_RIDING.equals(order.getType())){ - order.setMark("主动查询-骑行支付"); - }else{ - order.setMark("押金支付"); - // 更新用户余额 - AsUser asUser = asUserMapper.selectUserById(order.getUserId()); - asUser.setBalance(order.getTotalFee()); - int updateUser = asUserMapper.updateUser(asUser); - if(updateUser==0){ - log.error("【微信支付回调】更新用户押金失败"); - throw new ServiceException("【微信支付回调】更新用户押金失败"); - } - } - int updateEtOrder = etOrderService.updateEtOrder(order); - if(updateEtOrder==0){ - log.error("【微信支付回调】更新订单信息失败"); - throw new ServiceException("【微信支付回调】更新订单信息失败"); - } + handlePaySuccess(order, transaction.getSuccessTime()); } return true; } - }else if(PayChannel.TL_WX.equalsCode(channelVO.getCode())){ - Map result = sybPayService.queryOrderByOutTradeNo(order.getOutTradeNo()); - if(SybTrxStatus.isSuccess(result.get("trxstatus"))) { + }else if(PayChannel.TM_WX.equalsCode(channelVO.getCode())){ + TmTradeInfo tmTradeInfo = tmPayService.orderQuery(channelVO, order.getOutTradeNo()); + if(PayStatus.isSuccess(tmTradeInfo.getPayStatus().getCode())) { + if(order.getPaid().equals(ServiceConstants.ORDER_PAY_STATUS_NON_PAYMENT)){ + handlePaySuccess(order, tmTradeInfo.getPayTime()); + } return true; } }else{ @@ -399,6 +383,32 @@ public class WxPayService implements IWxPayService { return false; } + private void handlePaySuccess(EtOrder order, String payTime) { + order.setPaid("1"); + order.setPayTime(DateUtils.parseTime(payTime)); + order.setPayType(ServiceConstants.PAY_TYPE_WX); + log.info("【主动查询】押金支付"); + order.setStatus(ServiceConstants.ORDER_STATUS_ORDER_END); + if(ServiceConstants.ORDER_TYPE_RIDING.equals(order.getType())){ + order.setMark("主动查询-骑行支付"); + }else{ + order.setMark("押金支付"); + // 更新用户余额 + AsUser asUser = asUserMapper.selectUserById(order.getUserId()); + asUser.setBalance(order.getTotalFee()); + int updateUser = asUserMapper.updateUser(asUser); + if(updateUser==0){ + log.error("【微信支付回调】更新用户押金失败"); + throw new ServiceException("【微信支付回调】更新用户押金失败"); + } + } + int updateEtOrder = etOrderService.updateEtOrder(order); + if(updateEtOrder==0){ + log.error("【微信支付回调】更新订单信息失败"); + throw new ServiceException("【微信支付回调】更新订单信息失败"); + } + } + private JsapiService getJsapiService(ChannelVO channelVO) { // 初始化商户配置 Config config = new RSAAutoCertificateConfig.Builder() @@ -439,23 +449,19 @@ public class WxPayService implements IWxPayService { RefundService refundService = getRefundService(channelVO); Refund refund = refundService.create(request); log.info("【退款】微信返回结果:【{}】",JSON.toJSONString(refund)); - }else if(PayChannel.TL_WX.equalsCode(channelVO.getCode())){ - log.info("----------{}-------------","通联微信退款"); + }else if(PayChannel.TM_WX.equalsCode(channelVO.getCode())){ + log.info("----------{}-------------","太米微信退款"); RefundAble refundAble = new RefundAble(); refundAble.setOutTradeNo(etOrder.getOutTradeNo()); refundAble.setOutRefundNo(outRefundNo); refundAble.setReason(reason); refundAble.setAmount(amount.multiply(new BigDecimal(100)).longValue()); - Map refundResult = sybPayService.refundWx(refundAble); - String trxStatus = refundResult.get("trxstatus"); - // 当状态不为空,则判断是否处理退款成功 - if (trxStatus != null) { - ServiceUtil.assertion(!SybTrxStatus.isSuccess(trxStatus), "发起退款失败:" + refundResult.get("errmsg")); - // 通联退款是同步通知,直接处理退款成功 - scheduledExecutorService.schedule(() -> { - handleRefundSuccess(outRefundNo); - }, 0, TimeUnit.SECONDS); - } + log.info("【退款】请求太米参数:【{}】",JSON.toJSONString(refundAble)); + RefundInfo refund = tmPayService.refund(channelVO, refundAble); + ServiceUtil.assertion(!RefundStatus.isSuccess(refund.getRefundStatus().getCode()), "发起退款失败:" + refund.getRefundMessage()); + scheduledExecutorService.schedule(() -> { + handleRefundSuccess(outRefundNo); + }, 0, TimeUnit.SECONDS); }else{ throw new ServiceException("支付渠道【"+channelVO.getCode()+"】暂不支持"); } diff --git a/electripper-system/src/main/resources/mapper/system/EtChannelMapper.xml b/electripper-system/src/main/resources/mapper/system/EtChannelMapper.xml index 1fdad4d..7b97a4a 100644 --- a/electripper-system/src/main/resources/mapper/system/EtChannelMapper.xml +++ b/electripper-system/src/main/resources/mapper/system/EtChannelMapper.xml @@ -22,7 +22,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" sc.private_key_path, sc.merchant_serial_number, sc.refund_notify_url, - sc.appid + sc.appid, + sc.developer_id, + sc.shop_id, + sc.http_url, + sc.sign_key, + sc.sn from et_channel sc @@ -58,6 +63,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" enabled, cost_rate, picture, + merchant_id, + api_v3_key, + notify_url, + private_key_path, + merchant_serial_number, + refund_notify_url, + appid, + developer_id, + shop_id, + http_url, + sign_key, + sn, #{channelId}, @@ -66,6 +83,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{enabled}, #{costRate}, #{picture}, + #{merchantId}, + #{apiV3Key}, + #{notifyUrl}, + #{privateKeyPath}, + #{merchantSerialNumber}, + #{refundNotifyUrl}, + #{appid}, + #{developerId}, + #{shopId}, + #{httpUrl}, + #{signKey}, + #{sn}, @@ -84,6 +113,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" merchant_serial_number = #{data.merchantSerialNumber}, refund_notify_url = #{data.refundNotifyUrl}, appid = #{data.appid}, + developer_id = #{data.developerId}, + shop_id = #{data.shopId}, + http_url = #{data.httpUrl}, + sign_key = #{data.signKey}, + sn = #{data.sn}, where channel_id = #{data.channelId}