feat 结算台和各种支付记录

This commit is contained in:
xxm1995
2023-06-21 16:51:16 +08:00
parent e3f7a1bc52
commit 3b06429b42
11 changed files with 92 additions and 52 deletions

View File

@@ -24,7 +24,7 @@ public interface PayStatusCode {
/** 支付取消(超时/手动取消/订单已经关闭,撤销支付单) */
String TRADE_CANCEL = "trade_cancel";
/** 退款中 */
/** 退款中(部分退款) */
String TRADE_REFUNDING = "trade_refunding";
/** 已退款 */

View File

@@ -43,11 +43,11 @@ public class CashierController {
return Res.ok(cashierService.combinationPay(param));
}
@Operation(summary = "扫码聚合支付(单道)")
@GetMapping("/aggregatePay/{mchAppCode}")
public ModelAndView aggregatePay(String key,@PathVariable String mchAppCode, @RequestHeader(USER_AGENT) String ua) {
@Operation(summary = "扫码聚合支付(单道)")
@GetMapping("/aggregatePay/{mchCode}/{mchAppCode}")
public ModelAndView aggregatePay(String key,@PathVariable String mchCode,@PathVariable String mchAppCode, @RequestHeader(USER_AGENT) String ua) {
try {
String url = cashierService.aggregatePay(key, mchAppCode, ua);
String url = cashierService.aggregatePay(key, mchCode, mchAppCode, ua);
return new ModelAndView("redirect:" + url);
}
catch (PayUnsupportedMethodException e) {
@@ -56,9 +56,9 @@ public class CashierController {
}
@Operation(summary = "微信jsapi支付(回调)")
@GetMapping("/wxJsapiPay/{mchAppCode}")
public ModelAndView wxJsapiPay(String code, @PathVariable String mchAppCode, String state) {
Map<String, String> map = cashierService.wxJsapiPay(code,mchAppCode,state);
@GetMapping("/wxJsapiPay/{mchCode}/{mchAppCode}")
public ModelAndView wxJsapiPay(String code, @PathVariable String mchCode, @PathVariable String mchAppCode, String state) {
Map<String, String> map = cashierService.wxJsapiPay(code,mchCode,mchAppCode,state);
// 跳转页面, 调起微信jsapi支付
return new ModelAndView("wechatJsapiPay").addAllObjects(map);
}

View File

@@ -12,6 +12,7 @@ import cn.bootx.platform.daxpay.core.aggregate.entity.AggregatePayInfo;
import cn.bootx.platform.daxpay.core.aggregate.service.AggregateService;
import cn.bootx.platform.daxpay.core.channel.wechat.dao.WeChatPayConfigManager;
import cn.bootx.platform.daxpay.core.channel.wechat.entity.WeChatPayConfig;
import cn.bootx.platform.daxpay.core.merchant.dao.MchAppManager;
import cn.bootx.platform.daxpay.core.pay.service.PayService;
import cn.bootx.platform.daxpay.dto.pay.PayResult;
import cn.bootx.platform.daxpay.exception.payment.PayFailureException;
@@ -22,6 +23,7 @@ import cn.bootx.platform.daxpay.param.pay.PayParam;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.util.PayWaylUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.ijpay.core.enums.SignType;
import com.ijpay.core.kit.WxPayKit;
import lombok.RequiredArgsConstructor;
@@ -54,13 +56,14 @@ public class CashierService {
private final WeChatPayConfigManager weChatPayConfigManager;
private final MchAppManager mchAppManager;
private final SystemParamManager systemParamManager;
/**
* 发起支付(单渠道支付)
*/
public PayResult singlePay(CashierSinglePayParam param) {
// 如果是聚合支付,存在付款码时特殊处理(聚合扫码支付不用额外处理)
if (Objects.equals(PayChannelEnum.AGGREGATION.getCode(), param.getPayChannel())) {
String payChannel = aggregateService.getPayChannel(param.getAuthCode()).getCode();
@@ -96,8 +99,9 @@ public class CashierService {
/**
* 扫码发起自动支付
*/
public String aggregatePay(String key, String mchAppCode, String ua) {
public String aggregatePay(String key, String mchCode, String mchAppCode, String ua) {
CashierSinglePayParam cashierSinglePayParam = new CashierSinglePayParam()
.setMchCode(mchCode)
.setMchAppCode(mchAppCode)
.setPayWay(PayWayEnum.QRCODE.getCode());
// 判断是哪种支付方式
@@ -106,7 +110,7 @@ public class CashierService {
}
else if (ua.contains(PayChannelEnum.UA_WECHAT_PAY)) {
// 跳转微信授权页面, 调用jsapi进行支付
return this.wxJsapiAuth(key,mchAppCode);
return this.wxJsapiAuth(key,mchCode,mchAppCode);
}
else {
throw new PayUnsupportedMethodException();
@@ -123,26 +127,27 @@ public class CashierService {
/**
* 微信jsapi支付 - 跳转到授权页面
*/
private String wxJsapiAuth(String key, String mchAppCode) {
private String wxJsapiAuth(String key, String mchCode, String mchAppCode) {
WeChatPayConfig config = weChatPayConfigManager.findByMchAppCode(mchAppCode)
.orElseThrow(() -> new PayFailureException("未找到启用的微信支付配置"));
WxMpService wxMpService = getWxMpService(config.getWxAppId(), config.getAppSecret());
// 回调地址为 结算台微信jsapi支付的回调地址
SystemParameter systemParameter = systemParamManager.findByParamKey(WeChatPayCode.JSAPI_REDIRECT_URL)
.orElseThrow(() -> new PayFailureException("微信支付回调地址参数不存在"));
String url = systemParameter.getValue() + "cashier/wxJsapiPay";
String url = StrUtil.format("{}cashier/wxJsapiPay/{}/{}",systemParameter.getValue(),mchCode,mchAppCode);
return wxMpService.getOAuth2Service().buildAuthorizationUrl(url, WxConsts.OAuth2Scope.SNSAPI_BASE, key);
}
/**
* 微信jsapi支付 - 回调发起预支付, 同时调起微信页面jsapi支付
* @param code 微信授权码, 用来获取id
* @param mchCode 商户编码
* @param mchAppCode 商户应用编码
* @param state 聚合支付参数记录的key
* @return 页面中调起jsapi支付的参数
*/
@SneakyThrows
public Map<String, String> wxJsapiPay(String code, String mchAppCode, String state) {
public Map<String, String> wxJsapiPay(String code, String mchCode, String mchAppCode, String state) {
WeChatPayConfig config = weChatPayConfigManager.findByMchAppCode(mchAppCode)
.orElseThrow(() -> new PayFailureException("未找到启用的微信支付配置"));
WxMpService wxMpService = this.getWxMpService(config.getWxAppId(), config.getAppSecret());
@@ -153,6 +158,8 @@ public class CashierService {
CashierSinglePayParam cashierSinglePayParam = new CashierSinglePayParam()
.setPayChannel(PayChannelEnum.WECHAT.getCode())
.setPayWay(PayWayEnum.JSAPI.getCode())
.setMchCode(mchCode)
.setMchAppCode(mchAppCode)
.setTitle(aggregatePayInfo.getTitle())
.setAmount(aggregatePayInfo.getAmount())
.setOpenId(openId)
@@ -188,6 +195,8 @@ public class CashierService {
}
// 发起支付
PayParam payParam = new PayParam().setTitle(param.getTitle())
.setMchCode(param.getMchCode())
.setMchAppCode(param.getMchAppCode())
.setBusinessId(param.getBusinessId())
.setPayWayList(param.getPayWayList());
PayResult payResult = payService.pay(payParam);

View File

@@ -23,8 +23,11 @@ import org.springframework.stereotype.Service;
import java.io.ByteArrayInputStream;
import java.math.BigDecimal;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import static cn.bootx.platform.daxpay.code.pay.PayStatusCode.REFUND_PROCESS_FAIL;
/**
* 微信支付关闭和退款
*
@@ -76,6 +79,12 @@ public class WeChatPayCancelService {
.build()
.createSign(weChatPayConfig.getApiKeyV2(), SignType.HMACSHA256);
// 获取证书文件流
if (Objects.isNull(weChatPayConfig.getP12())){
String errorMsg = "微信p.12证书未配置,无法进行退款";
AsyncRefundLocal.setErrorMsg(errorMsg);
AsyncRefundLocal.setErrorCode(REFUND_PROCESS_FAIL);
throw new PayFailureException(errorMsg);
}
byte[] fileBytes = uploadService.getFileBytes(weChatPayConfig.getP12());
ByteArrayInputStream inputStream = new ByteArrayInputStream(fileBytes);
// 证书密码为 微信商户号

View File

@@ -41,27 +41,28 @@ public class PaymentBuilder {
String ip = ServletUtil.getClientIP(request);
// 基础信息
payment.setBusinessId(payParam.getBusinessId())
.setMchCode(payParam.getMchCode())
.setMchAppCode(payment.getMchAppCode())
.setTitle(payParam.getTitle())
.setDescription(payParam.getDescription());
.setMchCode(payParam.getMchCode())
.setMchAppCode(payParam.getMchAppCode())
.setTitle(payParam.getTitle())
.setDescription(payParam.getDescription());
// 支付方式和状态
List<PayChannelInfo> payTypeInfos = buildPayTypeInfo(payParam.getPayWayList());
List<RefundableInfo> refundableInfos = buildRefundableInfo(payParam.getPayWayList());
// 计算总价
BigDecimal sumAmount = payTypeInfos.stream()
.map(PayChannelInfo::getAmount)
.filter(Objects::nonNull)
.reduce(BigDecimal::add)
.orElse(BigDecimal.ZERO);
.map(PayChannelInfo::getAmount)
.filter(Objects::nonNull)
.reduce(BigDecimal::add)
.orElse(BigDecimal.ZERO);
// 支付通道信息
payment.setPayChannelInfo(payTypeInfos)
.setRefundableInfo(refundableInfos)
.setPayStatus(PayStatusCode.TRADE_PROGRESS)
.setAmount(sumAmount)
.setClientIp(ip)
.setRefundableBalance(sumAmount);
.setRefundableInfo(refundableInfos)
.setPayStatus(PayStatusCode.TRADE_PROGRESS)
.setAmount(sumAmount)
.setCombinationPayMode(payTypeInfos.size()>1)
.setClientIp(ip)
.setRefundableBalance(sumAmount);
return payment;
}
@@ -88,16 +89,17 @@ public class PaymentBuilder {
PayParam payParam = new PayParam();
// 恢复 payModeList
List<PayWayParam> payWayParams = payment.getPayChannelInfo()
.stream()
.map(payTypeInfo -> new PayWayParam().setAmount(payTypeInfo.getAmount())
.setPayChannel(payTypeInfo.getPayChannel())
.setExtraParamsJson(payTypeInfo.getExtraParamsJson()))
.collect(Collectors.toList());
.stream()
.map(payTypeInfo -> new PayWayParam().setAmount(payTypeInfo.getAmount())
.setPayChannel(payTypeInfo.getPayChannel())
.setExtraParamsJson(payTypeInfo.getExtraParamsJson()))
.collect(Collectors.toList());
payParam.setPayWayList(payWayParams)
.setBusinessId(payment.getBusinessId())
.setTitle(payment.getTitle())
.setTitle(payment.getTitle())
.setDescription(payment.getDescription());
.setBusinessId(payment.getBusinessId())
.setTitle(payment.getTitle())
.setMchCode(payment.getMchCode())
.setMchAppCode(payment.getMchAppCode())
.setDescription(payment.getDescription());
return payParam;
}
@@ -112,21 +114,21 @@ public class PaymentBuilder {
paymentResult = new PayResult();
// 异步支付信息
paymentResult.setAsyncPayChannel(payment.getAsyncPayChannel())
.setAsyncPayMode(payment.isAsyncPayMode())
.setPayStatus(payment.getPayStatus());
.setAsyncPayMode(payment.isAsyncPayMode())
.setPayStatus(payment.getPayStatus());
List<PayChannelInfo> channelInfos = payment.getPayChannelInfo();
// 设置异步支付参数
List<PayChannelInfo> moneyPayTypeInfos = channelInfos.stream()
.filter(payTypeInfo -> PayChannelEnum.ASYNC_TYPE_CODE.contains(payTypeInfo.getPayChannel()))
.collect(Collectors.toList());
.filter(payTypeInfo -> PayChannelEnum.ASYNC_TYPE_CODE.contains(payTypeInfo.getPayChannel()))
.collect(Collectors.toList());
if (!CollUtil.isEmpty(moneyPayTypeInfos)) {
paymentResult.setAsyncPayInfo(AsyncPayInfoLocal.get());
}
// 清空线程变量
}
finally {
// 清空线程变量
AsyncPayInfoLocal.clear();
}
return paymentResult;

View File

@@ -4,7 +4,9 @@ import cn.bootx.mybatis.table.modify.annotation.DbColumn;
import cn.bootx.mybatis.table.modify.annotation.DbComment;
import cn.bootx.mybatis.table.modify.annotation.DbTable;
import cn.bootx.mybatis.table.modify.mybatis.mysq.annotation.DbMySqlFieldType;
import cn.bootx.mybatis.table.modify.mybatis.mysq.annotation.DbMySqlIndex;
import cn.bootx.mybatis.table.modify.mybatis.mysq.constants.MySqlFieldTypeEnum;
import cn.bootx.mybatis.table.modify.mybatis.mysq.constants.MySqlIndexType;
import cn.bootx.platform.common.core.annotation.BigField;
import cn.bootx.platform.common.core.function.EntityBaseFunction;
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
@@ -41,7 +43,8 @@ import java.util.List;
public class Payment extends MpBaseEntity implements EntityBaseFunction<PaymentDto> {
/** 关联的业务id */
@DbColumn(comment = "商户编码")
@DbMySqlIndex(comment = "业务Id索引",type = MySqlIndexType.UNIQUE)
@DbColumn(comment = "关联的业务id")
private String businessId;
/** 商户编码 */
@@ -53,30 +56,42 @@ public class Payment extends MpBaseEntity implements EntityBaseFunction<PaymentD
private String mchAppCode;
/** 标题 */
@DbColumn(comment = "标题")
private String title;
/** 描述 */
@DbColumn(comment = "描述")
private String description;
/** 是否是异步支付 */
@DbColumn(comment = "是否是异步支付")
private boolean asyncPayMode;
/** 是否是组合支付 */
@DbColumn(comment = "是否是组合支付")
private boolean combinationPayMode;
/**
* 异步支付通道
* @see cn.bootx.platform.daxpay.code.pay.PayChannelEnum#ALI
*/
@DbColumn(comment = "异步支付通道")
private String asyncPayChannel;
/** 金额 */
@DbColumn(comment = "金额")
private BigDecimal amount;
/** 可退款余额 */
@DbColumn(comment = "可退款余额")
private BigDecimal refundableBalance;
/** 错误码 */
@DbColumn(comment = "错误码")
private String errorCode;
/** 错误信息 */
@DbColumn(comment = "错误信息")
private String errorMsg;
/**
@@ -86,6 +101,7 @@ public class Payment extends MpBaseEntity implements EntityBaseFunction<PaymentD
@TableField(typeHandler = JacksonRawTypeHandler.class)
@BigField
@DbMySqlFieldType(MySqlFieldTypeEnum.LONGTEXT)
@DbColumn(comment = "支付通道信息列表")
private List<PayChannelInfo> payChannelInfo;
/**
@@ -95,21 +111,26 @@ public class Payment extends MpBaseEntity implements EntityBaseFunction<PaymentD
@TableField(typeHandler = JacksonRawTypeHandler.class)
@BigField
@DbMySqlFieldType(MySqlFieldTypeEnum.LONGTEXT)
@DbColumn(comment = "退款信息列表")
private List<RefundableInfo> refundableInfo;
/**
* 支付状态
* @see PayStatusCode#TRADE_PROGRESS
*/
@DbColumn(comment = "支付状态")
private String payStatus;
/** 支付时间 */
@DbColumn(comment = "支付时间")
private LocalDateTime payTime;
/** 支付终端ip */
@DbColumn(comment = "支付终端ip")
private String clientIp;
/** 过期时间 */
@DbColumn(comment = "过期时间")
private LocalDateTime expiredTime;
@Override

View File

@@ -28,10 +28,10 @@ public class PayNotifyRecordDto extends BaseDto implements Serializable {
private String notifyInfo;
@Schema(description = "支付通道")
private Integer payChannel;
private String payChannel;
@Schema(description = "处理状态")
private Integer status;
private String status;
@Schema(description = "提示信息")
private String msg;

View File

@@ -42,6 +42,9 @@ public class PaymentDto extends BaseDto implements Serializable {
@Schema(description = "是否是异步支付")
private boolean asyncPayMode;
@Schema(description = "是否是组合支付")
private boolean combinationPayMode;
/**
* @see cn.bootx.platform.daxpay.code.pay.PayChannelEnum#ASYNC_TYPE_CODE
*/

View File

@@ -58,7 +58,7 @@ public class RefundRecordDto extends BaseDto {
* @see PayStatusCode#REFUND_PROCESS_FAIL
*/
@Schema(description = "退款状态")
private int refundStatus;
private String refundStatus;
@Schema(description = "错误码")
private String errorCode;

View File

@@ -19,6 +19,9 @@ import java.util.List;
@Schema(title = "结算台组合支付参数")
public class CashierCombinationPayParam {
@Schema(description = "商户编码")
private String mchCode;
@Schema(description = "商户应用编码")
private String mchAppCode;

View File

@@ -1,7 +1,6 @@
package cn.bootx.platform.daxpay.param.channel.wechat;
import cn.bootx.platform.common.core.annotation.QueryParam;
import cn.bootx.platform.daxpay.code.paymodel.WeChatPayCode;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
@@ -38,12 +37,6 @@ public class WeChatPayConfigParam {
@Schema(description = "微信应用appId")
private String wxAppId;
/**
* @see WeChatPayCode#API_V2
*/
// @Schema(description = "api版本")
// private String apiVersion;
@Schema(description = "商户平台「API安全」中的 APIv2 密钥")
private String apiKeyV2;