@@ -61,6 +61,9 @@ public class CommonPayConfig extends StringPropertyUUIDEntity { | |||
@Column(name = "UPDATE_TIME") | |||
private LocalDateTime updateTime = LocalDateTime.now();//更新时间 | |||
@Column(name = "WX_SERVICE_TYPE") | |||
private Integer wxServiceType;//微信商户下单类型,1普通商户,2服务商 | |||
@@ -0,0 +1,23 @@ | |||
package cn.com.taiji.core.entity.dict.pay; | |||
import lombok.AllArgsConstructor; | |||
import lombok.Getter; | |||
/** | |||
* @Author:ChenChao | |||
* @Date:2025/5/20 20:54 | |||
* @Filename:RefundAccount | |||
* @description:wx退款资金账户 | |||
*/ | |||
@AllArgsConstructor | |||
@Getter | |||
public enum RefundAccount { | |||
UNSETTLED("未结算资金"), | |||
AVAILABLE("可用余额"), | |||
UNAVAILABLE("不可用余额"), | |||
OPERATION("运营账户"), | |||
BASIC("基本账户(含可用余额和不可用余额)"), | |||
ECNY_BASIC("数字人民币基本账户"), | |||
; | |||
private String name; | |||
} |
@@ -0,0 +1,22 @@ | |||
package cn.com.taiji.core.entity.dict.pay; | |||
import lombok.AllArgsConstructor; | |||
import lombok.Getter; | |||
/** | |||
* @Author:ChenChao | |||
* @Date:2025/5/20 20:47 | |||
* @Filename:RefundChannel | |||
* @description: wx退款渠道 | |||
*/ | |||
@AllArgsConstructor | |||
@Getter | |||
public enum RefundChannel { | |||
ORIGINAL("原路退款"), | |||
BALANCE("退回余额"), | |||
OTHER_BALANCE("原账户异常退到其他余额账户"), | |||
OTHER_BANKCARD(" 原银行卡异常退到其他银行卡"),//(发起异常退款成功后返回) | |||
; | |||
private String name; | |||
} |
@@ -0,0 +1,25 @@ | |||
package cn.com.taiji.core.entity.dict.pay; | |||
import lombok.AllArgsConstructor; | |||
import lombok.Getter; | |||
/** | |||
* @Author:ChenChao | |||
* @Date:2025/5/20 20:51 | |||
* @Filename:RefundStatus | |||
* @description:wx退款状态 | |||
*/ | |||
@AllArgsConstructor | |||
@Getter | |||
public enum RefundStatus { | |||
SUCCESS("退款成功"), | |||
CLOSED("退款关闭"), | |||
PROCESSING("退款处理中"), | |||
ABNORMAL("退款异常"), | |||
; | |||
private String name; | |||
} |
@@ -0,0 +1,66 @@ | |||
package cn.com.taiji.core.model.comm.protocol.ats.weiXin; | |||
import cn.com.taiji.core.model.comm.protocol.ats.AbstractAtsRequest; | |||
import cn.com.taiji.core.model.comm.protocol.constraint.IntegerConstant; | |||
import cn.com.taiji.core.model.comm.protocol.valid.ErrorMsgBuilder; | |||
import lombok.Getter; | |||
import lombok.Setter; | |||
import javax.validation.constraints.NotBlank; | |||
import javax.validation.constraints.NotNull; | |||
/** | |||
* @Author:ChenChao | |||
* @Date:2025/5/20 19:57 | |||
* @Filename:AtsRefundRequest | |||
* @description: 退款申请 | |||
*/ | |||
@Setter | |||
@Getter | |||
public class AtsRefundV2Request extends AbstractAtsRequest<AtsRefundV2Response> { | |||
public AtsRefundV2Request() { | |||
super(WeiXinServiceCmd.REFUNDV2); | |||
} | |||
/** | |||
* 1单独下单url-普通商户下单 | |||
* 2服务商下单 | |||
* 3合并下单url | |||
* 目前只提供单独下单和服务商下单 | |||
*/ | |||
@IntegerConstant(values = "1,2") | |||
@NotNull | |||
private Integer createType; | |||
@NotBlank | |||
private String outTradeNo;//商户订单号 | |||
@NotBlank | |||
private String outRefundNo;//商户退款单号(由自己系统生成) | |||
@NotNull | |||
private Integer totalFee;//订单金额 | |||
@NotNull | |||
private Integer refundFee;//退款金额 | |||
private String refundFeeType = "CNY";//退款货币种类 | |||
private String refundDesc;//退款原因 | |||
private String notifyUrl;//退款结果通知url | |||
@NotBlank | |||
private String appId;//服务商的APPID | |||
@NotBlank | |||
private String mchId; | |||
private String subAppId; | |||
private String subMchId; | |||
@NotBlank | |||
private String mchKey; | |||
@Override | |||
protected void validate(ErrorMsgBuilder builder) { | |||
if (createType == 2){ | |||
builder.validFieldNotBlank("subMchId", subMchId); | |||
} | |||
} | |||
} |
@@ -0,0 +1,43 @@ | |||
package cn.com.taiji.core.model.comm.protocol.ats.weiXin; | |||
import cn.com.taiji.core.entity.dict.pay.RefundAccount; | |||
import cn.com.taiji.core.entity.dict.pay.RefundChannel; | |||
import cn.com.taiji.core.entity.dict.pay.RefundStatus; | |||
import cn.com.taiji.core.model.comm.protocol.ats.AbstractAtsResponse; | |||
import lombok.Getter; | |||
import lombok.Setter; | |||
/** | |||
* @Author:ChenChao | |||
* @Date:2025/5/20 19:57 | |||
* @Filename:AtsRefundResponse | |||
* @description: 退款申请响应 | |||
*/ | |||
@Getter | |||
@Setter | |||
public class AtsRefundV2Response extends AbstractAtsResponse { | |||
private String transactionId;//微信支付订单号 是 | |||
private String outTradeNo;//商户订单号 是 | |||
private String outRefundNo;//商户退款单号 是 | |||
private String refundId;//微信退款单号 是 | |||
private Integer refundFee;//退款金额 是 | |||
private Integer settlementRefundFee;//应结退款金额 否 | |||
private Integer totalFee;//订单金额 是 | |||
private Integer settlementTotalFee;//应结订单金额 否 | |||
private String feeType;//货币类型 否 | |||
private Integer cashFee;//现金支付金额 是 | |||
private Integer cashRefundFee;//现金退款金额 否 | |||
} |
@@ -0,0 +1,67 @@ | |||
package cn.com.taiji.core.model.comm.protocol.ats.weiXin; | |||
import cn.com.taiji.core.model.comm.protocol.ats.AbstractAtsRequest; | |||
import cn.com.taiji.core.model.comm.protocol.constraint.IntegerConstant; | |||
import cn.com.taiji.core.model.comm.protocol.valid.ErrorMsgBuilder; | |||
import lombok.Getter; | |||
import lombok.Setter; | |||
import javax.validation.constraints.NotBlank; | |||
import javax.validation.constraints.NotNull; | |||
/** | |||
* @Author:ChenChao | |||
* @Date:2025/5/20 19:57 | |||
* @Filename:AtsRefundRequest | |||
* @description: 退款申请 | |||
*/ | |||
@Setter | |||
@Getter | |||
public class AtsRefundV3Request extends AbstractAtsRequest<AtsRefundV3Response> { | |||
public AtsRefundV3Request() { | |||
super(WeiXinServiceCmd.REFUNDV3); | |||
} | |||
/** | |||
* 1单独下单url-普通商户下单 | |||
* 2服务商下单 | |||
* 3合并下单url | |||
* 目前只提供单独下单和服务商下单 | |||
*/ | |||
@IntegerConstant(values = "1,2") | |||
@NotNull | |||
private Integer createType; | |||
@NotBlank | |||
private String outTradeNo;//商户订单号 | |||
@NotBlank | |||
private String outRefundNo;//商户退款单号(由自己系统生成) | |||
private String reason;//退款原因 | |||
@NotNull | |||
private Long refund;//退款金额 | |||
@NotNull | |||
private Long total;//订单金额 | |||
private String currency = "CNY";//默认CNY | |||
@NotBlank | |||
protected String mchid;//直连商户号 spMchid;服务商户号 | |||
@NotBlank | |||
private String apiV3Key; // API V3密钥 | |||
@NotBlank | |||
private String privateKey;// 你的商户私钥 | |||
@NotBlank | |||
private String serialNo;// 商户证书序列号 | |||
private String subMchid;//子商户号 | |||
@Override | |||
protected void validate(ErrorMsgBuilder builder) { | |||
if (createType == 2){ | |||
builder.validFieldNotBlank("subMchid", subMchid); | |||
} | |||
} | |||
} |
@@ -0,0 +1,56 @@ | |||
package cn.com.taiji.core.model.comm.protocol.ats.weiXin; | |||
import cn.com.taiji.core.entity.dict.pay.RefundAccount; | |||
import cn.com.taiji.core.entity.dict.pay.RefundChannel; | |||
import cn.com.taiji.core.entity.dict.pay.RefundStatus; | |||
import cn.com.taiji.core.model.comm.protocol.ats.AbstractAtsResponse; | |||
import lombok.Getter; | |||
import lombok.Setter; | |||
/** | |||
* @Author:ChenChao | |||
* @Date:2025/5/20 19:57 | |||
* @Filename:AtsRefundResponse | |||
* @description: 退款申请响应 | |||
*/ | |||
@Getter | |||
@Setter | |||
public class AtsRefundV3Response extends AbstractAtsResponse { | |||
private String refundId;//微信支付退款单号 是 | |||
private String outRefundNo;//商户退款单号 -商户申请退款时传的商户系统内部退款单号。 是 | |||
private String transactionId;//微信支付订单号 是 | |||
private String outTradeNo;//商户订单号 是 | |||
/** | |||
* 1)退回银行卡:{银行名称}{卡类型}{卡尾号} | |||
* 2)退回支付用户零钱:支付用户零钱 | |||
* 3)退还商户:商户基本账户商户结算银行账户 | |||
* 4)退回支付用户零钱通:支付用户零钱通 | |||
* 5)退回支付用户银行电子账户:支付用户银行电子账户 | |||
* 6)退回支付用户零花钱:支付用户零花钱 | |||
* 7)退回用户经营账户:用户经营账户 | |||
* 8)退回支付用户来华零钱包:支付用户来华零钱包 | |||
* 9)退回企业支付商户:企业支付商户 | |||
*/ | |||
private String userReceivedAccount;//退款入账账户 是 | |||
private String successTime; //退款成功时间 是 | |||
private String createTime; // 退款创建时间 是 | |||
private Long total;//订单金额 是 | |||
private Long refund;//退款金额 是 | |||
private Long payerTotal;//用户实际支付金额 是 | |||
private Long payerRefund;//用户退款金额 - 指用户实际收到的现金退款金额 是 | |||
private Long settlementRefund; //应结退款金额 是 | |||
private Long settlementTotal; //应结订单金额 是 | |||
private Long discountRefund;//优惠退款金额 是 | |||
private String currency;//退款币种 是 | |||
private RefundChannel refundChannel;//退款渠道 是 | |||
private RefundAccount refundAccount;//资金账户 退款所使用资金对应的资金账户类型 是 | |||
private RefundStatus refundStatus;//退款状态 是 | |||
} |
@@ -10,6 +10,8 @@ public enum WeiXinServiceCmd implements SignServiceCommand { | |||
QUERYPAYRESULTV2("查询V2支付结果","queryPayResultV2", AtsQueryPayResultV2Request.class), | |||
CREATEPAYORDERV3("V3统一下单","creatOrderV3", AtsCreatPayOrderV3Request.class), | |||
QUERYPAYRESULTV3("查询V3支付结果","queryPayResultV3", AtsQueryPayResultV3Request.class), | |||
REFUNDV3("微信V3退款申请","refundV3", AtsRefundV3Request.class), | |||
REFUNDV2("微信V2退款申请","refundV2", AtsRefundV2Request.class), | |||
VEHICLEUSERSTATE("微信车主状态查询及获取签约参数","vehicleUserState", AtsVehicleUserStateRequest.class), | |||
MPSENDMESSAGE("微信公众号发送订阅信息","mpSendMessage", WxMpSendMessageRequest.class), | |||
MPISSUBSCRIBED("微信公众号是否订阅","isSubscribed", WxMpIsSubscribedRequest.class), |
@@ -50,12 +50,15 @@ public class WxPayAddReqDTO extends AbstractBizRequestDTO { | |||
@ApiModelProperty(value = "渠道编号") | |||
@NotBlank | |||
private String agencyId;//渠道编号 | |||
@ApiModelProperty(value = "微信商户下单类型,1普通商户,2服务商") | |||
private Integer wxServiceType;//微信商户下单类型,1普通商户,2服务商 | |||
@Override | |||
protected void validate(ViolationValidator validator) { | |||
super.validate(validator); | |||
if (PayChannelType.WEIXINPAY.equals(payChannelType)){ | |||
// validator.validField("wxPayVersion",wxPayVersion==null,"微信支付版本不能为空"); | |||
validator.validFieldNotNull("wxServiceType",wxServiceType); | |||
validator.validFieldNotNull("wxPayVersion",wxPayVersion); | |||
validator.validFieldNotBlank("wxAppId",wxAppId); | |||
validator.validFieldNotBlank("wxMchId",wxMchId); |
@@ -53,12 +53,15 @@ public class WxPayUpdateReqDTO extends AbstractBizRequestDTO { | |||
@ApiModelProperty(value = "渠道编号") | |||
@NotBlank | |||
private String agencyId;//渠道编号 | |||
@ApiModelProperty(value = "微信商户下单类型,1普通商户,2服务商") | |||
private Integer wxServiceType;//微信商户下单类型,1普通商户,2服务商 | |||
@Override | |||
protected void validate(ViolationValidator validator) { | |||
super.validate(validator); | |||
if (PayChannelType.WEIXINPAY.equals(payChannelType)){ | |||
// validator.validField("wxPayVersion",wxPayVersion==null,"微信支付版本不能为空"); | |||
validator.validFieldNotNull("wxServiceType",wxServiceType); | |||
validator.validFieldNotNull("wxPayVersion",wxPayVersion); | |||
validator.validFieldNotBlank("wxAppId",wxAppId); | |||
validator.validFieldNotBlank("wxMchId",wxMchId); |
@@ -38,6 +38,10 @@ public class WinXinServiceHandler extends AbstractAtsServiceHandler<WeiXinServic | |||
@Autowired | |||
private QueryPayResultV3Manager queryPayResultV3Manager; | |||
@Autowired | |||
private RefundV2Manager refundV2Manager; | |||
@Autowired | |||
private RefundV3Manager refundV3Manager; | |||
@Autowired | |||
private WxMessageManager wxMessageManager; | |||
@Autowired | |||
private VehicleUserStateManager vehicleUserStateManager; | |||
@@ -55,6 +59,10 @@ public class WinXinServiceHandler extends AbstractAtsServiceHandler<WeiXinServic | |||
return createPayOrderV3Manager.serviceHandle((AtsCreatPayOrderV3Request) request); | |||
case QUERYPAYRESULTV3: | |||
return queryPayResultV3Manager.serviceHandle((AtsQueryPayResultV3Request) request); | |||
case REFUNDV2: | |||
return refundV2Manager.serviceHandle((AtsRefundV2Request) request); | |||
case REFUNDV3: | |||
return refundV3Manager.serviceHandle((AtsRefundV3Request) request); | |||
case MPISSUBSCRIBED: | |||
return wxMessageManager.isSubscribed((WxMpIsSubscribedRequest) request); | |||
case MPSENDMESSAGE: |
@@ -32,6 +32,7 @@ public class QueryPayResultV2Manager extends AbstractManager { | |||
public AtsQueryPayResultV2Response serviceHandle(AtsQueryPayResultV2Request req) throws ServiceHandleException { | |||
logger.info("微信支付V2查询支付结果请求参数{}",req.toJson()); | |||
req.validate(); | |||
WxPayConfig wxPayConfig = wechatConfig.initV2Config(req.getAppId(), req.getMchId(), req.getMchKey(), req.getSubAppId(), req.getSubMchId()); | |||
wxPayService.setConfig(wxPayConfig); | |||
WxPayOrderQueryRequest request = copyProperties(req, new WxPayOrderQueryRequest()); |
@@ -0,0 +1,49 @@ | |||
package cn.com.taiji.ats.manager.weixin; | |||
import cn.com.taiji.ats.config.WechatConfig; | |||
import cn.com.taiji.common.manager.AbstractManager; | |||
import cn.com.taiji.common.manager.net.http.ServiceHandleException; | |||
import cn.com.taiji.core.model.comm.protocol.AbstractSignTypeResponse; | |||
import cn.com.taiji.core.model.comm.protocol.ats.weiXin.AtsRefundV2Request; | |||
import cn.com.taiji.core.model.comm.protocol.ats.weiXin.AtsRefundV2Response; | |||
import cn.com.taiji.core.model.comm.protocol.valid.GlyServiceError; | |||
import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest; | |||
import com.github.binarywang.wxpay.bean.result.WxPayRefundResult; | |||
import com.github.binarywang.wxpay.config.WxPayConfig; | |||
import com.github.binarywang.wxpay.exception.WxPayException; | |||
import com.github.binarywang.wxpay.service.WxPayService; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Service; | |||
/** | |||
* @Author:ChenChao | |||
* @Date:2025/5/20 21:35 | |||
* @Filename:RefundV2Manager | |||
* @description: | |||
*/ | |||
@Service | |||
public class RefundV2Manager extends AbstractManager { | |||
@Autowired | |||
private WechatConfig wechatConfig; | |||
@Autowired | |||
private WxPayService wxPayService; | |||
public AtsRefundV2Response serviceHandle(AtsRefundV2Request req) throws ServiceHandleException { | |||
logger.info("微信支付V2退款申请请求参数{}",req.toJson()); | |||
req.validate(); | |||
//初始化配置信息 | |||
WxPayConfig wxPayConfig = wechatConfig.initV2Config(req.getAppId(), req.getMchId(), req.getMchKey(), | |||
req.getSubAppId(), req.getSubMchId()); | |||
wxPayService.setConfig(wxPayConfig); | |||
WxPayRefundRequest request = copyProperties(req, new WxPayRefundRequest()); | |||
AtsRefundV2Response response = new AtsRefundV2Response(); | |||
try { | |||
WxPayRefundResult refund = wxPayService.refund(request); | |||
response = copyProperties(refund,response); | |||
} catch (WxPayException e) { | |||
logger.error("微信V2退款失败:{}", e.getMessage()); | |||
throw GlyServiceError.BUSINESS_VALIDATE_ERR.toHandleException(e.getMessage()); | |||
} | |||
return response; | |||
} | |||
} |
@@ -0,0 +1,46 @@ | |||
package cn.com.taiji.ats.manager.weixin; | |||
import cn.com.taiji.ats.config.WechatConfig; | |||
import cn.com.taiji.common.manager.AbstractManager; | |||
import cn.com.taiji.common.manager.net.http.ServiceHandleException; | |||
import cn.com.taiji.core.entity.dict.pay.RefundAccount; | |||
import cn.com.taiji.core.entity.dict.pay.RefundChannel; | |||
import cn.com.taiji.core.entity.dict.pay.RefundStatus; | |||
import cn.com.taiji.core.model.comm.protocol.ats.weiXin.AtsRefundV3Request; | |||
import cn.com.taiji.core.model.comm.protocol.ats.weiXin.AtsRefundV3Response; | |||
import com.wechat.pay.java.core.RSAAutoCertificateConfig; | |||
import com.wechat.pay.java.service.refund.RefundService; | |||
import com.wechat.pay.java.service.refund.model.CreateRequest; | |||
import com.wechat.pay.java.service.refund.model.Refund; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.stereotype.Service; | |||
/** | |||
* @Author:ChenChao | |||
* @Date:2025/5/20 20:03 | |||
* @Filename:RefundManager | |||
* @description: | |||
*/ | |||
@Service | |||
public class RefundV3Manager extends AbstractManager { | |||
@Autowired | |||
private WechatConfig wechatConfig; | |||
public AtsRefundV3Response serviceHandle(AtsRefundV3Request req) throws ServiceHandleException { | |||
logger.info("微信支付V3退款申请请求参数{}",req.toJson()); | |||
req.validate(); | |||
//配置支付信息 | |||
RSAAutoCertificateConfig config = | |||
wechatConfig.initV3Config(req.getMchid(), req.getApiV3Key(), req.getPrivateKey(), req.getSerialNo()); | |||
// 初始化服务 | |||
RefundService service = new RefundService.Builder().config(config).build(); | |||
CreateRequest createRequest = copyProperties(req, new CreateRequest()); | |||
Refund refund = service.create(createRequest); | |||
AtsRefundV3Response response = copyProperties(refund, new AtsRefundV3Response()); | |||
response.setRefundChannel(RefundChannel.valueOf(refund.getChannel().name())); | |||
response.setRefundAccount(RefundAccount.valueOf(refund.getFundsAccount().name())); | |||
response.setRefundStatus(RefundStatus.valueOf(refund.getStatus().name())); | |||
return response; | |||
} | |||
} |