支付接口更换

This commit is contained in:
墨大叔 2024-09-10 18:25:24 +08:00
parent 5284c604f7
commit d61b9e9391
17 changed files with 294 additions and 23 deletions

View File

@ -131,6 +131,7 @@ public class SmUser extends BaseEntity
private BigDecimal serviceRate;
@ApiModelProperty("是否设备管理员")
@JsonView(JsonViewProfile.AppMch.class)
private Boolean deviceAdmin;
@ApiModelProperty("服务费收取方式")

View File

@ -43,4 +43,7 @@ public class SybConfig {
// 通联平台sm2公钥用于请求返回或者通联通知的验签
private String sm2TlPubKey;
// 通知地址
private String notifyUrl;
}

View File

@ -0,0 +1,26 @@
package com.ruoyi.common.pay.syb.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 收银宝交易类型
* @author wjh
* 2024/9/10
*/
@Getter
@AllArgsConstructor
public enum SybTrxCode {
WX_PAY("VSP501", "微信支付"),
WX_REFUND("VSP503", "微信支付退款");
private final String code;
private final String msg;
public boolean equalsCode(String code) {
return this.getCode().equals(code);
}
}

View File

@ -0,0 +1,42 @@
package com.ruoyi.common.pay.syb.enums;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
/**
* 交易结果
* @author wjh
* 2024/9/10
*/
@Getter
@AllArgsConstructor
public enum SybTrxStatus {
SUCCESS("0000", "交易成功"),
PROCESSING("2000", "交易处理中"),
NOT_EXIST("1001", "交易不存在"),
REPEAT("3888", "流水号重复"),
CHANNEL_MERCHANT_ERROR("3099", "渠道商户错误"),
TRANSACTION_AMOUNT_LESS_THAN_SERVICE_CHARGE("3014", "交易金额小于应收手续费"),
CHECK_REAL_NAME_INFO_FAILED("3031", "校验实名信息失败"),
TRANSACTION_NOT_PAID("3088", "交易未支付"),
TRANSACTION_REVERSED("3089", "撤销异常"),
TRANSACTION_REVOKED("3050", "交易已被撤销");
public static boolean isSuccess(String code) {
return SUCCESS.getCode().equals(code);
}
public static SybTrxStatus getByCode(String code) {
for (SybTrxStatus value : SybTrxStatus.values()) {
if (value.getCode().equals(code)) {
return value;
}
}
return null;
}
private final String code;
private final String msg;
}

View File

@ -121,6 +121,36 @@ public class SybPayClient {
return map;
}
/**
* 关闭支付订单
*/
public Map<String,String> close(String oldtrxid,String oldreqsn) throws Exception{
HttpConnectionUtil http = new HttpConnectionUtil(sybConfig.getApiUrl()+"/close");
http.init();
TreeMap<String,String> params = new TreeMap<String,String>();
if(!SybUtil.isEmpty(sybConfig.getOrgId()))
params.put("orgid", sybConfig.getOrgId());
params.put("cusid", sybConfig.getCusId());
params.put("appid", sybConfig.getAppId());
params.put("version", "11");
params.put("oldtrxid", oldtrxid);
params.put("oldreqsn", oldreqsn);
params.put("randomstr", SybUtil.getValidatecode(8));
params.put("signtype", sybConfig.getSignType());
String appkey = "";
if(sybConfig.getSignType().equals("RSA"))
appkey = sybConfig.getRsaCusPriKey();
else if(sybConfig.getSignType().equals("SM2"))
appkey = sybConfig.getSm2CusPriKey();
else
appkey = sybConfig.getMd5AppKey();
params.put("sign", SybUtil.unionSign(params,appkey,sybConfig.getSignType()));
byte[] bys = http.postParams(params, true);
String result = new String(bys,"UTF-8");
Map<String,String> map = handleResult(result);
return map;
}
public Map<String,String> refund(long trxamt,String reqsn,String oldtrxid,String oldreqsn) throws Exception{
HttpConnectionUtil http = new HttpConnectionUtil(sybConfig.getApiUrl()+"/refund");
http.init();

View File

@ -2,13 +2,19 @@ package com.ruoyi.common.pay.syb.service;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.pay.syb.config.SybConfig;
import com.ruoyi.common.pay.syb.util.SybUtil;
import com.ruoyi.common.pay.wx.domain.Payable;
import com.ruoyi.common.pay.wx.domain.RefundAble;
import com.ruoyi.common.utils.StringUtils;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.model.Refund;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.TreeMap;
/**
* @author wjh
@ -20,6 +26,9 @@ public class SybPayService {
@Autowired
private SybPayClient sybPayClient;
@Autowired
private SybConfig sybConfig;
/**
* 微信小程序预下单
*/
@ -33,7 +42,7 @@ public class SybPayService {
payable.payableDescription(),
payable.payableOpenId(),
"",
"",
sybConfig.getNotifyUrl(),
"",
"",
"",
@ -54,11 +63,65 @@ public class SybPayService {
return JSON.parseObject(payInfo, PrepayWithRequestPaymentResponse.class);
}
}
return null;
throw new ServiceException("支付数据为空");
} catch(Exception e) {
throw new ServiceException("调起支付失败:" + e.getMessage());
}
}
public boolean validSign(TreeMap<String, String> params) {
try {
String appkey = "";
if("RSA".equals(params.get("signtype"))) {
appkey = sybConfig.getRsaTlPubKey();
} else if("SM2".equals(params.get("signtype"))) {
appkey = sybConfig.getSm2TlPubKey();
} else {
appkey = sybConfig.getMd5AppKey();
}
boolean isSign = SybUtil.validSign(params, appkey, params.get("signtype"));// 接受到推送通知,首先验签
System.out.println("验签结果:"+isSign);
return isSign;
} catch (Exception e) {
throw new ServiceException("验签异常:" + e.getMessage());
}
}
/**
* 使用商户自定义订单编号查询支付订单
*/
public Map<String, String> queryOrderByOutTradeNo(String outTradeNo) {
try {
return sybPayClient.query(outTradeNo, "");
} catch (Exception e) {
throw new ServiceException("查询支付订单异常:" + e.getMessage());
}
}
/**
* 微信退款
*/
public Map<String, String> refundWx(RefundAble refundAble) {
try {
return sybPayClient.refund(
refundAble.refundAmount().getRefund().longValue(),
refundAble.refundOutRefundNo(),
"",
refundAble.refundOutTradeNo()
);
} catch (Exception e) {
throw new ServiceException("发起退款异常:" + e.getMessage());
}
}
/**
* 关闭微信支付订单
*/
public void closeOrderWx(String outTradeNo) {
try {
sybPayClient.close("", outTradeNo);
} catch (Exception e) {
throw new ServiceException("关闭支付订单异常:" + e.getMessage());
}
}
}

View File

@ -2,6 +2,7 @@ package com.ruoyi.common.pay.syb.util;
import org.apache.commons.codec.binary.Base64;
import javax.servlet.http.HttpServletRequest;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
@ -209,4 +210,21 @@ public class SybUtil {
return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
}
/**
* 动态遍历获取所有收到的参数,此步非常关键,因为收银宝以后可能会加字段,动态获取可以兼容由于收银宝加字段而引起的签名异常
* @param request
* @return
*/
public static TreeMap<String, String> getParams(HttpServletRequest request){
TreeMap<String, String> map = new TreeMap<String, String>();
Map reqMap = request.getParameterMap();
for(Object key:reqMap.keySet()){
String value = ((String[])reqMap.get(key))[0];
System.out.println(key+";"+value);
map.put(key.toString(),value);
}
return map;
}
}

View File

@ -17,7 +17,7 @@ public interface RefundAble {
// 退款原因
String refundReason();
// 退款金额
// 退款金额
AmountReq refundAmount();
}

View File

@ -307,4 +307,13 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
public static LocalDate toLocalDate(Date date) {
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
}
/**
* 字符串转LocalDateTime
* @param timeStr 字符串
* @param format 格式化
*/
public static LocalDateTime toLocalDate(String timeStr, String format) {
return LocalDateTime.parse(timeStr, DateTimeFormatter.ofPattern(format));
}
}

View File

@ -149,7 +149,7 @@ public class PayBillConverterImpl implements PayBillConverter {
po.setChannelCost(this.calcChannelCost(channel, order.getSuitPrice()));
// 支付人
if (TransactionBillPayType.WECHAT.getType().equals(channel.getChannelId())) {
if (TransactionBillPayType.wxList().contains(channel.getChannelId())) {
po.setAccount(user.getWxOpenId());
}

View File

@ -4,15 +4,16 @@ import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.ruoyi.common.core.redis.RedisLock;
import com.ruoyi.common.core.redis.enums.RedisLockKey;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.pay.syb.enums.SybTrxStatus;
import com.ruoyi.common.pay.syb.service.SybPayService;
import com.ruoyi.common.pay.wx.util.WxPayUtil;
import com.ruoyi.common.pay.yst.service.YstPayService;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.ServiceUtil;
import com.ruoyi.common.utils.SnowFlakeUtil;
@ -210,9 +211,15 @@ public class PayBillServiceImpl implements PayBillService
// 关闭订单
for (PayBillVO bill : payingList) {
// 关闭支付中的订单
if (PayBillStatus.PAYING.getStatus().equals(bill.getStatus())
&& TransactionBillPayType.WECHAT.getType().equals(bill.getChannelId())) {
wxPayService.closeOrder(bill.getPayNo());
if (PayBillStatus.PAYING.getStatus().equals(bill.getStatus())) {
// 微信
if (TransactionBillPayType.WECHAT.getType().equals(bill.getChannelId())) {
wxPayService.closeOrder(bill.getPayNo());
}
// 通联微信
else if (TransactionBillPayType.TL_WX.getType().equals(bill.getChannelId())) {
sybPayService.closeOrderWx(bill.getPayNo());
}
}
}
@ -459,6 +466,14 @@ public class PayBillServiceImpl implements PayBillService
} else {
return PayResultVO.fail("暂未支付成功");
}
} else if (TransactionBillPayType.TL_WX.getType().equals(bill.getChannelId())) {
// 通联微信支付
Map<String, String> result = sybPayService.queryOrderByOutTradeNo(bill.getPayNo());
if (SybTrxStatus.isSuccess(result.get("trxstatus"))) {
return PayResultVO.success(DateUtils.toLocalDate(result.get("fintime"), "yyyyMMddHHmmss"));
} else {
return PayResultVO.fail("暂未支付成功");
}
}
return PayResultVO.fail("暂不支持该支付方式");
}

View File

@ -1,8 +1,13 @@
package com.ruoyi.ss.refund.service.impl;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.pay.syb.enums.SybTrxStatus;
import com.ruoyi.common.pay.syb.service.SybPayService;
import com.ruoyi.common.pay.wx.service.WxPayService;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.ServiceUtil;
@ -50,6 +55,13 @@ public class RefundServiceImpl implements RefundService
@Autowired
private PayBillService payBillService;
@Autowired
private SybPayService sybPayService;
@Autowired
private ScheduledExecutorService scheduledExecutorService;
/**
* 查询退款订单
*
@ -181,8 +193,22 @@ public class RefundServiceImpl implements RefundService
RefundVO refundVO = this.selectRefundByRefundNo(refund.getRefundNo());
// 发起退款
// 微信
if (TransactionBillPayType.WECHAT.getType().equals(refund.getChannelId())) {
wxPayService.refund(refundVO);
}
// 通联微信
else if (TransactionBillPayType.TL_WX.getType().equals(refund.getChannelId())) {
Map<String, String> refundResult = sybPayService.refundWx(refundVO);
String trxStatus = refundResult.get("trxstatus");
// 当状态不为空则判断是否处理退款成功
if (trxStatus != null) {
ServiceUtil.assertion(!SybTrxStatus.isSuccess(trxStatus), "发起退款失败:" + refundResult.get("errmsg"));
// 通联退款是同步通知直接处理退款成功
scheduledExecutorService.schedule(() -> {
this.handleRefundSuccess(refund.getRefundNo());
}, 0, TimeUnit.SECONDS);
}
} else {
throw new ServiceException("当前支付方式不支持退款");
}

View File

@ -290,11 +290,13 @@ public class TransactionBillServiceImpl implements TransactionBillService, After
else if(ServiceType.PERCENT.getType().equals(serviceType)){
// 服务费
BigDecimal serviceCharge = serviceRate.multiply(order.getMoney()).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
// 最低服务费
BigDecimal minService = sysConfigService.getBigDecimal(ConfigKey.RECHARGE_MIN_SERVICE);
ServiceUtil.assertion(order.getMoney().compareTo(minService) < 0, "当前套餐金额小于最低服务费,请联系商户处理");
if (serviceCharge.compareTo(minService) < 0) {
serviceCharge = minService;
// 非分时订单处理最低服务费
if (!SuitFeeType.timingList().contains(order.getSuitFeeType())) {
BigDecimal minService = sysConfigService.getBigDecimal(ConfigKey.RECHARGE_MIN_SERVICE);
ServiceUtil.assertion(order.getMoney().compareTo(minService) < 0, "当前套餐金额小于最低服务费,请联系商户处理");
if (serviceCharge.compareTo(minService) < 0) {
serviceCharge = minService;
}
}
order.setServiceCharge(serviceCharge);
@ -929,7 +931,7 @@ public class TransactionBillServiceImpl implements TransactionBillService, After
} else {
throw new ServiceException("计算金额出错,套餐收费类型不支持");
}
// 最低0.01元
// 最低金额
if (totalAmount.compareTo(BigDecimal.valueOf(0.01)) < 0) {
totalAmount = BigDecimal.valueOf(0.01);
}

View File

@ -250,7 +250,8 @@ public class TransactionBillValidatorImpl extends BaseValidator implements Trans
}
@Override
public ValidateResult prePay(RechargePayBO bo) {
public ValidateResult
prePay(RechargePayBO bo) {
TransactionBillVO order = bo.getOrder();
if (order == null || order.getBillId() == null) {
return error("订单不存在");

View File

@ -30,11 +30,4 @@ public class AppChannelController extends BaseController {
return success(channelService.selectEnabledRechargeList());
}
@ApiOperation("获取提现渠道列表")
@JsonView(JsonViewProfile.App.class)
@GetMapping("/enabledWithdrawList")
public AjaxResult getWithdrawEnabledList() {
return error("本接口即将弃用,请使用/app/channel/withdraw/enabledList");
}
}

View File

@ -4,6 +4,11 @@ import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
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.wx.domain.enums.WxNotifyEventType;
import com.ruoyi.common.pay.wx.domain.enums.WxTransferBatchStatus;
import com.ruoyi.common.pay.wx.util.WxPayUtil;
@ -33,6 +38,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.TreeMap;
/**
* @author
@ -53,6 +59,9 @@ public class AppPayController extends BaseController {
@Autowired
private PayBillService payBillService;
@Autowired
private SybPayService sybPayService;
@Autowired
private TransactionBillConverter transactionBillConverter;
@ -152,4 +161,35 @@ public class AppPayController extends BaseController {
return ResponseEntity.status(HttpStatus.OK).body(null);
}
@PostMapping("/notify/tl")
@Anonymous
public String tlPayNotify(HttpServletRequest request) {
try {
request.setCharacterEncoding("UTF-8");//通知传输的编码为GBK
// 获取所有参数
TreeMap<String, String> params = SybUtil.getParams(request);
boolean sign = sybPayService.validSign(params);
if (sign) {
String trxCode = params.get("trxcode"); // 交易类型
String cusOrderId = params.get("cusorderid"); // 商户自定义订单号
String trxStatus = params.get("trxstatus"); // 交易结果
if (SybTrxStatus.isSuccess(trxStatus)) {
// 微信支付通知
if (SybTrxCode.WX_PAY.equalsCode(trxCode)) {
// 新版支付订单
payBillService.handleSuccess(cusOrderId, DateUtils.toLocalDate(params.get("paytime"), "yyyyMMddHHmmss"));
}
// 微信退款通知
else if (SybTrxCode.WX_REFUND.equalsCode(trxCode)) {
refundService.handleRefundSuccess(cusOrderId);
}
}
}
return "error";
} catch (Exception e) {
throw new ServiceException(e.getMessage());
}
}
}

View File

@ -128,3 +128,5 @@ syb:
sm2CusPriKey: MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgaPcLc/a1wqUa9wpDetd0PtjbPv+ldPq8vPc7UeDmlWegCgYIKoEcz1UBgi2hRANCAASo8eWV80+/3elREuKbSukOwkP+tekaq1bsk9zrreR14RAzQrDJWrq8PBso8Ctpvew51w6aVhFubw+oDFFNLE/w
# 通联平台sm2公钥用于请求返回或者通联通知的验签
sm2TlPubKey: MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEBQicgWm0KAMqhO3bdqMUEDrKQvYg8cCXHhdGwq7CGE6oJDzJ1P/94HpuVdBf1KidmPxr7HOH+0DAnpeCcx9TcQ==
# 支付通知地址
notifyUrl: http://124.221.246.124:2290/app/pay/notify/tl