This commit is contained in:
磷叶 2025-01-08 18:09:55 +08:00
parent d35137bfcc
commit 970ff655f8
14 changed files with 259 additions and 59 deletions

View File

@ -1,5 +1,6 @@
package com.ruoyi.common.pay;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
@ -15,13 +16,13 @@ public interface PayApi {
void closeByOutTradeNo(String outTradeNo, Object config);
// 查询支付单
Object queryByOutTradeNo(String outTradeNo, Object config);
Object queryByOutTradeNo(String outTradeNo, LocalDate orderDate, Object config);
// 退款
Object refund(Refundable refundAble, Object config);
// 是否支付成功
boolean isPaySuccessByOutTradeNo(String outTradeNo, Object config);
boolean isPaySuccessByOutTradeNo(String outTradeNo, LocalDate orderDate, Object config);
// 是否支付成功
boolean isPaySuccess(Object result);

View File

@ -20,9 +20,15 @@ public interface Refundable {
// 退款原因
String refundReason();
// 退款金额
// 退款金额对象单位
AmountReq refundAmount();
// 退款金额()
default BigDecimal refundAmountFen() {
return BigDecimal.valueOf(refundAmount().getRefund());
}
// 退款金额
default BigDecimal refundAmountYuan() {
return BigDecimal.valueOf(refundAmount().getRefund())
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);

View File

@ -25,6 +25,7 @@ import com.ruoyi.common.utils.ServiceUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Map;
@ -97,7 +98,7 @@ public class AliPayService implements PayApi {
}
public AlipayTradeQueryResponse queryByOutTradeNo(String outTradeNo, Object config) {
public AlipayTradeQueryResponse queryByOutTradeNo(String outTradeNo, LocalDate orderDate, Object config) {
// 初始化支付宝客户端
AliConfig aliConfig = (AliConfig) config;
AlipayClient alipayClient = aliConfig.alipayClient();
@ -158,8 +159,8 @@ public class AliPayService implements PayApi {
}
@Override
public boolean isPaySuccessByOutTradeNo(String outTradeNo, Object config) {
AlipayTradeQueryResponse res = this.queryByOutTradeNo(outTradeNo, config);
public boolean isPaySuccessByOutTradeNo(String outTradeNo, LocalDate orderDate, Object config) {
AlipayTradeQueryResponse res = this.queryByOutTradeNo(outTradeNo, orderDate, config);
return isPaySuccess(res);
}

View File

@ -15,6 +15,7 @@ import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPayment
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.TreeMap;
@ -100,7 +101,7 @@ public class SybPayService implements PayApi {
/**
* 使用商户自定义订单编号查询支付订单
*/
public Map<String, String> queryByOutTradeNo(String outTradeNo, Object config) {
public Map<String, String> queryByOutTradeNo(String outTradeNo, LocalDate orderDate, Object config) {
try {
return sybPayClient.query(outTradeNo, "");
} catch (Exception e) {
@ -129,8 +130,8 @@ public class SybPayService implements PayApi {
}
@Override
public boolean isPaySuccessByOutTradeNo(String outTradeNo, Object config) {
return isPaySuccess(this.queryByOutTradeNo(outTradeNo, config));
public boolean isPaySuccessByOutTradeNo(String outTradeNo, LocalDate orderDate, Object config) {
return isPaySuccess(this.queryByOutTradeNo(outTradeNo, orderDate, config));
}
@Override

View File

@ -17,6 +17,7 @@ import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPayment
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@ -33,7 +34,7 @@ public class TmPayService implements PayApi {
/**
* 订单查询
*/
public TmTradeInfo queryByOutTradeNo(String outTradeNo, Object tmConfig) {
public TmTradeInfo queryByOutTradeNo(String outTradeNo, LocalDate orderDate, Object tmConfig) {
// 初始化配置
TmPayConfig config = (TmPayConfig) tmConfig;
@ -83,8 +84,8 @@ public class TmPayService implements PayApi {
}
@Override
public boolean isPaySuccessByOutTradeNo(String outTradeNo, Object config) {
TmTradeInfo result = queryByOutTradeNo(outTradeNo, config);
public boolean isPaySuccessByOutTradeNo(String outTradeNo, LocalDate orderDate, Object config) {
TmTradeInfo result = queryByOutTradeNo(outTradeNo, orderDate, config);
return isPaySuccess(result);
}

View File

@ -7,8 +7,6 @@ import com.ruoyi.common.pay.Refundable;
import com.ruoyi.common.pay.wx.config.WxPayConfig;
import com.ruoyi.common.pay.wx.util.WxPayUtil;
import com.ruoyi.common.utils.DateUtils;
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.refund.RefundService;
@ -19,6 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
@ -30,15 +29,6 @@ import java.time.LocalDateTime;
@Slf4j
public class WxPayService implements PayApi {
@Autowired
private JsapiService defaultJsapiService;
@Autowired
private JsapiServiceExtension defaultJsapiServiceExtension;
@Autowired
private WxPayConfig defaultWxPayConfig;
@Autowired
private RefundService refundService;
@ -79,7 +69,7 @@ public class WxPayService implements PayApi {
wxPayConfig.jsapiService().closeOrder(request);
}
public Transaction queryByOutTradeNo(String billNo, Object config) {
public Transaction queryByOutTradeNo(String billNo, LocalDate orderDate, Object config) {
// 初始化配置
WxPayConfig wxPayConfig = (WxPayConfig) config;
@ -112,8 +102,8 @@ public class WxPayService implements PayApi {
}
@Override
public boolean isPaySuccessByOutTradeNo(String outTradeNo, Object config) {
return isPaySuccess(this.queryByOutTradeNo(outTradeNo, config));
public boolean isPaySuccessByOutTradeNo(String outTradeNo, LocalDate orderDate, Object config) {
return isPaySuccess(this.queryByOutTradeNo(outTradeNo, orderDate, config));
}
@Override

View File

@ -9,6 +9,9 @@ import lombok.Data;
@Data
public class XyPayConfig {
// 微信支付appId
private String appId;
// 公钥
private String publicKey;

View File

@ -15,4 +15,18 @@ public class XyPayConstants {
// 支付方式银联
public static final String PAY_WAY_UNION = "3";
/**
* 交易方式公众号
*/
public static final Object TRA_TYPE_MP = "5";
/**
* 交易方式小程序
*/
public static final Object TRA_TYPE_MINI_PROGRAM = "8";
/**
* 订单类型微信
*/
public static final String TAG_WX = "2";
}

View File

@ -1,6 +1,7 @@
package com.ruoyi.common.pay.xy.service;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.pay.PayApi;
import com.ruoyi.common.pay.Payable;
import com.ruoyi.common.pay.Refundable;
@ -14,7 +15,9 @@ import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPayment
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Objects;
import java.util.TreeMap;
@ -40,6 +43,8 @@ public class XyWxPayService implements PayApi {
params.put("txamt", payable.payableFen());
params.put("openid", payable.payableOpenId());
params.put("payWay", XyPayConstants.PAY_WAY_WX);
params.put("traType", XyPayConstants.TRA_TYPE_MINI_PROGRAM);
params.put("wxAppid", payConfig.getAppId());
params.put("ip", payable.payableIp());
params.put("timeStamp", DateUtils.format(LocalDateTime.now(), "yyyyMMddHHmmss"));
params.put("version", "1.0.0");
@ -53,53 +58,179 @@ public class XyWxPayService implements PayApi {
ServiceUtil.assertion(response == null, "响应结果为空");
JSONObject json = JSONObject.parseObject(response);
String code = json.getString("code");
if (Objects.equals("000000", code)) {
JSONObject data = json.getJSONObject("data");
ServiceUtil.assertion(data == null, "响应数据为空");
PrepayWithRequestPaymentResponse result = new PrepayWithRequestPaymentResponse();
result.setAppId(data.getString("jsapiAppid"));
result.setTimeStamp(data.getString("jsapiTimestamp"));
result.setNonceStr(data.getString("jsapiNoncestr"));
result.setPackageVal(data.getString("jsapiPackage"));
result.setSignType(data.getString("jsapiSignType"));
result.setPaySign(data.getString("jsapiPaySign"));
return result;
}
} catch (Exception e) {
log.error("支付请求失败:" + e.getMessage());
return null;
}
ServiceUtil.assertion(!Objects.equals("000000", code), json.getString("msg"));
return null;
JSONObject data = json.getJSONObject("data");
ServiceUtil.assertion(data == null, "响应数据为空");
PrepayWithRequestPaymentResponse result = new PrepayWithRequestPaymentResponse();
result.setAppId(data.getString("jsapiAppid"));
result.setTimeStamp(data.getString("jsapiTimestamp"));
result.setNonceStr(data.getString("jsapiNoncestr"));
result.setPackageVal(data.getString("jsapiPackage"));
result.setSignType(data.getString("jsapiSignType"));
result.setPaySign(data.getString("jsapiPaySign"));
return result;
} catch (Exception e) {
throw new ServiceException("支付请求失败:" + e.getMessage());
}
}
@Override
public void closeByOutTradeNo(String outTradeNo, Object config) {
XyPayConfig payConfig = (XyPayConfig) config;
// 请求地址
String url = payConfig.getHost() + "/yyfsevr/order/closePay";
// 请求参数
TreeMap<String, Object> params = new TreeMap<>();
params.put("agetId", payConfig.getAgetId());
params.put("custId", payConfig.getCustId());
params.put("threeOrderNO", outTradeNo);
params.put("timeStamp", DateUtils.format(LocalDateTime.now(), "yyyyMMddHHmmss"));
params.put("version", "1.0.0");
// 参数签名
params.put("sign", XyPayUtil.encrypt(params, payConfig.getPublicKey()));
// 发送请求
try {
String response = HttpUtil.postData(url, params);
ServiceUtil.assertion(response == null, "响应结果为空");
JSONObject json = JSONObject.parseObject(response);
String code = json.getString("code");
ServiceUtil.assertion(!Objects.equals("000000", code), json.getString("msg"));
JSONObject data = json.getJSONObject("data");
ServiceUtil.assertion(data == null, "响应数据为空");
ServiceUtil.assertion(!Objects.equals(data.getString("closeFlag"), "1"), "尝试关单失败");
} catch (Exception e) {
throw new ServiceException("关单失败:" + e.getMessage());
}
}
@Override
public Object queryByOutTradeNo(String outTradeNo, Object config) {
return null;
public Object queryByOutTradeNo(String outTradeNo, LocalDate orderDate, Object config) {
XyPayConfig payConfig = (XyPayConfig) config;
// 请求地址
String url = payConfig.getHost() + "/yyfsevr/order/orderQuery";
// 请求参数
TreeMap<String, Object> params = new TreeMap<>();
params.put("agetId", payConfig.getAgetId());
params.put("custId", payConfig.getCustId());
params.put("orderNo", outTradeNo);
params.put("orderTime", DateUtils.format(orderDate, "yyyyMMdd"));
params.put("timeStamp", DateUtils.format(LocalDateTime.now(), "yyyyMMddHHmmss"));
params.put("version", "1.0.0");
// 参数签名
params.put("sign", XyPayUtil.encrypt(params, payConfig.getPublicKey()));
// 发送请求
try {
String response = HttpUtil.postData(url, params);
ServiceUtil.assertion(response == null, "响应结果为空");
JSONObject json = JSONObject.parseObject(response);
String code = json.getString("code");
ServiceUtil.assertion(!Objects.equals("000000", code), json.getString("msg"));
return json.getJSONObject("data");
} catch (Exception e) {
throw new ServiceException("查询支付单失败:" + e.getMessage());
}
}
@Override
public Object refund(Refundable refundAble, Object config) {
return null;
XyPayConfig payConfig = (XyPayConfig) config;
// 请求地址
String url = payConfig.getHost() + "/yyfsevr/order/refund";
// 请求参数
TreeMap<String, Object> params = new TreeMap<>();
params.put("agetId", payConfig.getAgetId());
params.put("custId", payConfig.getCustId());
params.put("orderNo", refundAble.refundOutRefundNo());
params.put("oldTOrderNo", refundAble.refundOutTradeNo());
params.put("refundAmount", refundAble.refundAmountFen());
params.put("tag", XyPayConstants.TAG_WX);
params.put("remark", refundAble.refundReason());
params.put("timeStamp", DateUtils.format(LocalDateTime.now(), "yyyyMMddHHmmss"));
params.put("version", "1.0.0");
// 参数签名
params.put("sign", XyPayUtil.encrypt(params, payConfig.getPublicKey()));
// 发送请求
try {
String response = HttpUtil.postData(url, params);
ServiceUtil.assertion(response == null, "响应结果为空");
JSONObject json = JSONObject.parseObject(response);
String code = json.getString("code");
ServiceUtil.assertion(!Objects.equals("000000", code), json.getString("msg"));
return json.getJSONObject("data");
} catch (Exception e) {
throw new ServiceException("退款失败:" + e.getMessage());
}
}
@Override
public boolean isPaySuccessByOutTradeNo(String outTradeNo, Object config) {
return false;
public boolean isPaySuccessByOutTradeNo(String outTradeNo, LocalDate orderDate, Object config) {
Object result = this.queryByOutTradeNo(outTradeNo, orderDate, config);
return isPaySuccess(result);
}
@Override
public boolean isPaySuccess(Object result) {
return false;
return result != null;
}
@Override
public LocalDateTime getPayTime(Object result) {
return null;
if (result == null) {
return null;
}
JSONObject json = (JSONObject) result;
return DateUtils.toLocalDateTime(json.getDate("orderTime"));
}
/**
* 关联商户号和机构
* @return
*/
public boolean connectCust(XyPayConfig config) {
// 请求地址
String url = config.getHost() + "/yyfsevr/order/connectCust";
// 请求参数
TreeMap<String, Object> params = new TreeMap<>();
params.put("agetId", config.getAgetId());
params.put("custId", config.getCustId());
params.put("timeStamp", DateUtils.format(LocalDateTime.now(), "yyyyMMddHHmmss"));
params.put("version", "1.0.0");
// 参数签名
params.put("sign", XyPayUtil.encrypt(params, config.getPublicKey()));
// 发送请求
try {
String response = HttpUtil.postData(url, params);
ServiceUtil.assertion(response == null, "响应结果为空");
JSONObject json = JSONObject.parseObject(response);
String code = json.getString("code");
// 000003 已关联请勿重复关联000000 成功
ServiceUtil.assertion(!Arrays.asList("000003", "000000").contains(code), json.getString("msg"));
return true;
} catch (Exception e) {
log.error("请求失败:" + e.getMessage());
return false;
}
}
}

View File

@ -8,8 +8,6 @@ import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
import static org.apache.commons.lang3.time.DateFormatUtils.format;
/**
* 时间工具类
*
@ -228,7 +226,7 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils
return org.apache.commons.lang3.time.DateFormatUtils.format(date, format);
}
private static String format(LocalDate localDate, String format) {
public static String format(LocalDate localDate, String format) {
if (localDate == null) {
return null;
}

View File

@ -74,6 +74,7 @@ public class ChannelConverterImpl implements ChannelConverter {
// 国通星驿
else if (ChannelApiType.XY_WX.getType().equals(channel.getApiType())) {
XyPayConfig config = new XyPayConfig();
config.setAppId(channelConfig.getAppId());
config.setPublicKey(channelConfig.getPublicKey());
config.setAgetId(channelConfig.getAgetId());
config.setCustId(channelConfig.getCustId());

View File

@ -1,17 +1,21 @@
package com.ruoyi.ss.channel.service.impl;
import com.ruoyi.common.pay.xy.config.XyPayConfig;
import com.ruoyi.common.pay.xy.service.XyWxPayService;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.ServiceUtil;
import com.ruoyi.common.utils.collection.CollectionUtils;
import com.ruoyi.ss.account.service.AccountService;
import com.ruoyi.ss.channel.domain.Channel;
import com.ruoyi.ss.channel.domain.ChannelQuery;
import com.ruoyi.ss.channel.domain.ChannelVO;
import com.ruoyi.ss.channel.domain.enums.ChannelApiType;
import com.ruoyi.ss.channel.domain.enums.ChannelType;
import com.ruoyi.ss.channel.mapper.ChannelMapper;
import com.ruoyi.ss.channel.service.ChannelConverter;
import com.ruoyi.ss.channel.service.ChannelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
import java.util.Collections;
import java.util.List;
@ -32,7 +36,13 @@ public class ChannelServiceImpl implements ChannelService
private ChannelMapper channelMapper;
@Autowired
private AccountService accountService;
private XyWxPayService xyWxPayService;
@Autowired
private ChannelConverter channelConverter;
@Autowired
private TransactionTemplate transactionTemplate;
/**
* 查询充值渠道
@ -68,7 +78,18 @@ public class ChannelServiceImpl implements ChannelService
public int insertSmChannel(Channel channel)
{
channel.setCreateTime(DateUtils.getNowDate());
return channelMapper.insertSmChannel(channel);
Integer result = transactionTemplate.execute(status -> {
int insert = channelMapper.insertSmChannel(channel);
if (insert == 1) {
ChannelVO vo = selectSmChannelByChannelId(channel.getChannelId());
this.connectCust(vo);
}
return insert;
});
return result == null ? 0 : result;
}
/**
@ -80,7 +101,29 @@ public class ChannelServiceImpl implements ChannelService
@Override
public int updateSmChannel(Channel channel)
{
return channelMapper.updateSmChannel(channel);
Integer result = transactionTemplate.execute(status -> {
int update = channelMapper.updateSmChannel(channel);
if (update == 1) {
ChannelVO vo = selectSmChannelByChannelId(channel.getChannelId());
this.connectCust(vo);
}
return update;
});
return result == null ? 0 :result;
}
/**
* 关联商户和机构
*/
private boolean connectCust(ChannelVO vo) {
ServiceUtil.assertion(vo == null, "渠道不存在");
if (ChannelApiType.XY_WX.getType().equals(vo.getApiType())) {
return xyWxPayService.connectCust((XyPayConfig) channelConverter.toConfig(vo));
}
return true;
}
/**

View File

@ -241,7 +241,7 @@ public class PayBillServiceImpl implements PayBillService
payApi.closeByOutTradeNo(bill.getPayNo(), channelConverter.toConfig(channel));
} catch (Exception e) {
// 关闭失败尝试查询订单信息判断是否已经支付成功
if (payApi.isPaySuccessByOutTradeNo(bill.getPayNo(), channelConverter.toConfig(channel))) {
if (payApi.isPaySuccessByOutTradeNo(bill.getPayNo(), DateUtils.toLocalDate(bill.getCreateTime()) , channelConverter.toConfig(channel))) {
throw new ServiceException("当前交易已成功,无法关闭");
} else {
log.error("关闭支付订单失败: payNo = {} 原因:{}", bill.getPayNo(), e.getMessage());
@ -539,7 +539,7 @@ public class PayBillServiceImpl implements PayBillService
}
// 获取支付结果
Object result = payApi.queryByOutTradeNo(bill.getPayNo(), channelConverter.toConfig(channel));
Object result = payApi.queryByOutTradeNo(bill.getPayNo(), DateUtils.toLocalDate(bill.getCreateTime()), channelConverter.toConfig(channel));
if (payApi.isPaySuccess(result)) {
return PayResultVO.success(payApi.getPayTime(result), result);
} else {

View File

@ -303,4 +303,14 @@ public class AppPayController extends BaseController {
return ResponseEntity.status(HttpStatus.OK).body(null);
}
@ApiOperation("星驿支付通知")
@PostMapping("/notify/xy")
@Anonymous
public String xyPayNotify(HttpServletRequest request, @PathVariable Long channelId) {
log.info("星驿支付通知" + request);
return "success";
}
}