@Column(name = "UPDATE_TIME") | @Column(name = "UPDATE_TIME") | ||||
private LocalDateTime updateTime = LocalDateTime.now();//更新时间 | private LocalDateTime updateTime = LocalDateTime.now();//更新时间 | ||||
@Column(name = "WX_SERVICE_TYPE") | |||||
private Integer wxServiceType;//微信商户下单类型,1普通商户,2服务商 | |||||
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; | |||||
} |
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; | |||||
} |
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; | |||||
} |
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); | |||||
} | |||||
} | |||||
} |
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;//现金退款金额 否 | |||||
} |
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); | |||||
} | |||||
} | |||||
} |
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;//退款状态 是 | |||||
} |
QUERYPAYRESULTV2("查询V2支付结果","queryPayResultV2", AtsQueryPayResultV2Request.class), | QUERYPAYRESULTV2("查询V2支付结果","queryPayResultV2", AtsQueryPayResultV2Request.class), | ||||
CREATEPAYORDERV3("V3统一下单","creatOrderV3", AtsCreatPayOrderV3Request.class), | CREATEPAYORDERV3("V3统一下单","creatOrderV3", AtsCreatPayOrderV3Request.class), | ||||
QUERYPAYRESULTV3("查询V3支付结果","queryPayResultV3", AtsQueryPayResultV3Request.class), | QUERYPAYRESULTV3("查询V3支付结果","queryPayResultV3", AtsQueryPayResultV3Request.class), | ||||
REFUNDV3("微信V3退款申请","refundV3", AtsRefundV3Request.class), | |||||
REFUNDV2("微信V2退款申请","refundV2", AtsRefundV2Request.class), | |||||
VEHICLEUSERSTATE("微信车主状态查询及获取签约参数","vehicleUserState", AtsVehicleUserStateRequest.class), | VEHICLEUSERSTATE("微信车主状态查询及获取签约参数","vehicleUserState", AtsVehicleUserStateRequest.class), | ||||
MPSENDMESSAGE("微信公众号发送订阅信息","mpSendMessage", WxMpSendMessageRequest.class), | MPSENDMESSAGE("微信公众号发送订阅信息","mpSendMessage", WxMpSendMessageRequest.class), | ||||
MPISSUBSCRIBED("微信公众号是否订阅","isSubscribed", WxMpIsSubscribedRequest.class), | MPISSUBSCRIBED("微信公众号是否订阅","isSubscribed", WxMpIsSubscribedRequest.class), |
@ApiModelProperty(value = "渠道编号") | @ApiModelProperty(value = "渠道编号") | ||||
@NotBlank | @NotBlank | ||||
private String agencyId;//渠道编号 | private String agencyId;//渠道编号 | ||||
@ApiModelProperty(value = "微信商户下单类型,1普通商户,2服务商") | |||||
private Integer wxServiceType;//微信商户下单类型,1普通商户,2服务商 | |||||
@Override | @Override | ||||
protected void validate(ViolationValidator validator) { | protected void validate(ViolationValidator validator) { | ||||
super.validate(validator); | super.validate(validator); | ||||
if (PayChannelType.WEIXINPAY.equals(payChannelType)){ | if (PayChannelType.WEIXINPAY.equals(payChannelType)){ | ||||
// validator.validField("wxPayVersion",wxPayVersion==null,"微信支付版本不能为空"); | // validator.validField("wxPayVersion",wxPayVersion==null,"微信支付版本不能为空"); | ||||
validator.validFieldNotNull("wxServiceType",wxServiceType); | |||||
validator.validFieldNotNull("wxPayVersion",wxPayVersion); | validator.validFieldNotNull("wxPayVersion",wxPayVersion); | ||||
validator.validFieldNotBlank("wxAppId",wxAppId); | validator.validFieldNotBlank("wxAppId",wxAppId); | ||||
validator.validFieldNotBlank("wxMchId",wxMchId); | validator.validFieldNotBlank("wxMchId",wxMchId); |
@ApiModelProperty(value = "渠道编号") | @ApiModelProperty(value = "渠道编号") | ||||
@NotBlank | @NotBlank | ||||
private String agencyId;//渠道编号 | private String agencyId;//渠道编号 | ||||
@ApiModelProperty(value = "微信商户下单类型,1普通商户,2服务商") | |||||
private Integer wxServiceType;//微信商户下单类型,1普通商户,2服务商 | |||||
@Override | @Override | ||||
protected void validate(ViolationValidator validator) { | protected void validate(ViolationValidator validator) { | ||||
super.validate(validator); | super.validate(validator); | ||||
if (PayChannelType.WEIXINPAY.equals(payChannelType)){ | if (PayChannelType.WEIXINPAY.equals(payChannelType)){ | ||||
// validator.validField("wxPayVersion",wxPayVersion==null,"微信支付版本不能为空"); | // validator.validField("wxPayVersion",wxPayVersion==null,"微信支付版本不能为空"); | ||||
validator.validFieldNotNull("wxServiceType",wxServiceType); | |||||
validator.validFieldNotNull("wxPayVersion",wxPayVersion); | validator.validFieldNotNull("wxPayVersion",wxPayVersion); | ||||
validator.validFieldNotBlank("wxAppId",wxAppId); | validator.validFieldNotBlank("wxAppId",wxAppId); | ||||
validator.validFieldNotBlank("wxMchId",wxMchId); | validator.validFieldNotBlank("wxMchId",wxMchId); |
@Autowired | @Autowired | ||||
private QueryPayResultV3Manager queryPayResultV3Manager; | private QueryPayResultV3Manager queryPayResultV3Manager; | ||||
@Autowired | @Autowired | ||||
private RefundV2Manager refundV2Manager; | |||||
@Autowired | |||||
private RefundV3Manager refundV3Manager; | |||||
@Autowired | |||||
private WxMessageManager wxMessageManager; | private WxMessageManager wxMessageManager; | ||||
@Autowired | @Autowired | ||||
private VehicleUserStateManager vehicleUserStateManager; | private VehicleUserStateManager vehicleUserStateManager; | ||||
return createPayOrderV3Manager.serviceHandle((AtsCreatPayOrderV3Request) request); | return createPayOrderV3Manager.serviceHandle((AtsCreatPayOrderV3Request) request); | ||||
case QUERYPAYRESULTV3: | case QUERYPAYRESULTV3: | ||||
return queryPayResultV3Manager.serviceHandle((AtsQueryPayResultV3Request) request); | return queryPayResultV3Manager.serviceHandle((AtsQueryPayResultV3Request) request); | ||||
case REFUNDV2: | |||||
return refundV2Manager.serviceHandle((AtsRefundV2Request) request); | |||||
case REFUNDV3: | |||||
return refundV3Manager.serviceHandle((AtsRefundV3Request) request); | |||||
case MPISSUBSCRIBED: | case MPISSUBSCRIBED: | ||||
return wxMessageManager.isSubscribed((WxMpIsSubscribedRequest) request); | return wxMessageManager.isSubscribed((WxMpIsSubscribedRequest) request); | ||||
case MPSENDMESSAGE: | case MPSENDMESSAGE: |
public AtsQueryPayResultV2Response serviceHandle(AtsQueryPayResultV2Request req) throws ServiceHandleException { | public AtsQueryPayResultV2Response serviceHandle(AtsQueryPayResultV2Request req) throws ServiceHandleException { | ||||
logger.info("微信支付V2查询支付结果请求参数{}",req.toJson()); | logger.info("微信支付V2查询支付结果请求参数{}",req.toJson()); | ||||
req.validate(); | |||||
WxPayConfig wxPayConfig = wechatConfig.initV2Config(req.getAppId(), req.getMchId(), req.getMchKey(), req.getSubAppId(), req.getSubMchId()); | WxPayConfig wxPayConfig = wechatConfig.initV2Config(req.getAppId(), req.getMchId(), req.getMchKey(), req.getSubAppId(), req.getSubMchId()); | ||||
wxPayService.setConfig(wxPayConfig); | wxPayService.setConfig(wxPayConfig); | ||||
WxPayOrderQueryRequest request = copyProperties(req, new WxPayOrderQueryRequest()); | WxPayOrderQueryRequest request = copyProperties(req, new WxPayOrderQueryRequest()); |
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; | |||||
} | |||||
} |
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; | |||||
} | |||||
} |