@@ -11,7 +11,7 @@ import lombok.Getter; | |||
*/ | |||
@AllArgsConstructor | |||
@Getter | |||
public enum RefundAccount { | |||
public enum RefundV3Account { | |||
UNSETTLED("未结算资金"), | |||
AVAILABLE("可用余额"), | |||
UNAVAILABLE("不可用余额"), |
@@ -11,7 +11,7 @@ import lombok.Getter; | |||
*/ | |||
@AllArgsConstructor | |||
@Getter | |||
public enum RefundStatus { | |||
public enum RefundV3Status { | |||
SUCCESS("退款成功"), | |||
@@ -0,0 +1,54 @@ | |||
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/21 9:17 | |||
* @Filename:AtsRefundV2QueryRequest | |||
* @description: 微信v2退款查询 | |||
*/ | |||
@Getter | |||
@Setter | |||
public class AtsQueryRefundV2Request extends AbstractAtsRequest<AtsQueryRefundV2Response> { | |||
public AtsQueryRefundV2Request() { | |||
super(WeiXinServiceCmd.REFUNDQUERYV2); | |||
} | |||
/** | |||
* 1单独下单url-普通商户下单 | |||
* 2服务商下单 | |||
* 3合并下单url | |||
* 目前只提供单独下单和服务商下单 | |||
*/ | |||
@IntegerConstant(values = "1,2") | |||
@NotNull | |||
private Integer createType; | |||
@NotBlank | |||
private String outTradeNo;//商户订单号 | |||
@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,75 @@ | |||
package cn.com.taiji.core.model.comm.protocol.ats.weiXin; | |||
import cn.com.taiji.core.entity.dict.pay.RefundChannel; | |||
import cn.com.taiji.core.model.comm.protocol.ats.AbstractAtsResponse; | |||
import lombok.Getter; | |||
import lombok.Setter; | |||
import java.util.List; | |||
/** | |||
* @Author:ChenChao | |||
* @Date:2025/5/21 9:18 | |||
* @Filename:AtsRefundV2QueryResponse | |||
* @description:微信V2退款查询响应 | |||
*/ | |||
@Getter | |||
@Setter | |||
public class AtsQueryRefundV2Response extends AbstractAtsResponse { | |||
private String transactionId;//微信订单号 是 | |||
private String outTradeNo;//商户订单号 是 | |||
private Integer totalFee;//订单金额 是 | |||
private Integer refundFee;//退款总金额 是 | |||
private Integer cashRefundFee;//用户退款金额,不包含所有优惠券金额 是 | |||
private String feeType;//货币类型 否 | |||
private Integer refundCount; //退款笔数 是 | |||
private List<RefundRecord> refundRecords; //是 | |||
@Setter | |||
@Getter | |||
public static class RefundRecord { | |||
private String outRefundNo;// 商户退款单号 是 | |||
private String refundId;// 微信退款单号 否 | |||
private RefundChannel refundChannel;//退款渠道 否 | |||
private Integer refundFee;//申请退款金额 是 | |||
/** | |||
* 退款金额=申请退款金额-非充值代金券退款金额,退款金额<=申请退款金额 | |||
*/ | |||
private Integer settlementRefundFee;//否 | |||
/** | |||
* SUCCESS—退款成功 | |||
* REFUNDCLOSE—退款关闭,指商户发起退款失败的情况。 | |||
* PROCESSING—退款处理中 | |||
* CHANGE—退款异常, | |||
*/ | |||
private String refundStatus; //退款状态 是 | |||
/** | |||
* REFUND_SOURCE_RECHARGE_FUNDS---可用余额退款/基本账户 | |||
* REFUND_SOURCE_UNSETTLED_FUNDS---未结算资金退款 | |||
*/ | |||
private String refundAccount;//退款资金来源 否 | |||
private String refundSuccessTime;//退款成功时间 否 | |||
} | |||
} |
@@ -0,0 +1,56 @@ | |||
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/21 11:13 | |||
* @Filename:AtsQueryRefundV3Request | |||
* @description: 微信V3退款查询 | |||
*/ | |||
@Getter | |||
@Setter | |||
public class AtsQueryRefundV3Request extends AbstractAtsRequest<AtsQueryRefundV3Response> { | |||
public AtsQueryRefundV3Request() { | |||
super(WeiXinServiceCmd.REFUNDQUERYV3); | |||
} | |||
/** | |||
* 1单独下单url-普通商户下单 | |||
* 2服务商下单 | |||
* 3合并下单url | |||
* 目前只提供单独下单和服务商下单 | |||
*/ | |||
@IntegerConstant(values = "1,2") | |||
@NotNull | |||
private Integer createType; | |||
@NotBlank | |||
private String outRefundNo;//退款单号 | |||
@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,63 @@ | |||
package cn.com.taiji.core.model.comm.protocol.ats.weiXin; | |||
import cn.com.taiji.core.entity.dict.pay.RefundChannel; | |||
import cn.com.taiji.core.entity.dict.pay.RefundV3Account; | |||
import cn.com.taiji.core.entity.dict.pay.RefundV3Status; | |||
import cn.com.taiji.core.model.comm.protocol.ats.AbstractAtsResponse; | |||
import lombok.Getter; | |||
import lombok.Setter; | |||
/** | |||
* @Author:ChenChao | |||
* @Date:2025/5/21 11:13 | |||
* @Filename:AtsQueryRefundV3Response | |||
* @description:微信V3订单退款查询响应 | |||
* 响应参数意义参考微信文档:https://pay.weixin.qq.com/doc/v3/merchant/4012791863 | |||
*/ | |||
@Setter | |||
@Getter | |||
public class AtsQueryRefundV3Response 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 channel; //退款渠道 是 | |||
private RefundV3Account fundsAccount;//退款所使用资金对应的资金账户类型 是 | |||
private RefundV3Status status;//退款状态 是 | |||
} |
@@ -1,8 +1,5 @@ | |||
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; |
@@ -40,6 +40,7 @@ public class AtsRefundV3Request extends AbstractAtsRequest<AtsRefundV3Response> | |||
private String reason;//退款原因 | |||
//=============================amount========================= | |||
@NotNull | |||
private Long refund;//退款金额 | |||
@@ -47,6 +48,7 @@ public class AtsRefundV3Request extends AbstractAtsRequest<AtsRefundV3Response> | |||
private Long total;//订单金额 | |||
private String currency = "CNY";//默认CNY | |||
//=============================amount========================= | |||
@NotBlank | |||
protected String mchid;//直连商户号 spMchid;服务商户号 |
@@ -1,8 +1,8 @@ | |||
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.RefundV3Account; | |||
import cn.com.taiji.core.entity.dict.pay.RefundChannel; | |||
import cn.com.taiji.core.entity.dict.pay.RefundStatus; | |||
import cn.com.taiji.core.entity.dict.pay.RefundV3Status; | |||
import cn.com.taiji.core.model.comm.protocol.ats.AbstractAtsResponse; | |||
import lombok.Getter; | |||
import lombok.Setter; | |||
@@ -12,6 +12,7 @@ import lombok.Setter; | |||
* @Date:2025/5/20 19:57 | |||
* @Filename:AtsRefundResponse | |||
* @description: 退款申请响应 | |||
* 响应参数意义参考微信支付文档https://pay.weixin.qq.com/doc/v3/merchant/4012791862 | |||
*/ | |||
@Getter | |||
@Setter | |||
@@ -49,8 +50,10 @@ public class AtsRefundV3Response extends AbstractAtsResponse { | |||
private Long discountRefund;//优惠退款金额 是 | |||
private String currency;//退款币种 是 | |||
private RefundChannel refundChannel;//退款渠道 是 | |||
private RefundAccount refundAccount;//资金账户 退款所使用资金对应的资金账户类型 是 | |||
private RefundStatus refundStatus;//退款状态 是 | |||
private RefundChannel channel; //退款渠道 是 | |||
private RefundV3Account fundsAccount;//退款所使用资金对应的资金账户类型 是 | |||
private RefundV3Status status;//退款状态 是 | |||
} |
@@ -11,7 +11,9 @@ public enum WeiXinServiceCmd implements SignServiceCommand { | |||
CREATEPAYORDERV3("V3统一下单","creatOrderV3", AtsCreatPayOrderV3Request.class), | |||
QUERYPAYRESULTV3("查询V3支付结果","queryPayResultV3", AtsQueryPayResultV3Request.class), | |||
REFUNDV3("微信V3退款申请","refundV3", AtsRefundV3Request.class), | |||
REFUNDQUERYV3("微信V3退款查询","refundV3Query", AtsQueryRefundV3Request.class), | |||
REFUNDV2("微信V2退款申请","refundV2", AtsRefundV2Request.class), | |||
REFUNDQUERYV2("微信V2退款查询","refundV2Query", AtsQueryRefundV2Request.class), | |||
VEHICLEUSERSTATE("微信车主状态查询及获取签约参数","vehicleUserState", AtsVehicleUserStateRequest.class), | |||
MPSENDMESSAGE("微信公众号发送订阅信息","mpSendMessage", WxMpSendMessageRequest.class), | |||
MPISSUBSCRIBED("微信公众号是否订阅","isSubscribed", WxMpIsSubscribedRequest.class), |
@@ -40,8 +40,12 @@ public class WinXinServiceHandler extends AbstractAtsServiceHandler<WeiXinServic | |||
@Autowired | |||
private RefundV2Manager refundV2Manager; | |||
@Autowired | |||
private QueryRefundV2Manager queryRefundV2Manager; | |||
@Autowired | |||
private RefundV3Manager refundV3Manager; | |||
@Autowired | |||
private QueryRefundV3Manager queryRefundV3Manager; | |||
@Autowired | |||
private WxMessageManager wxMessageManager; | |||
@Autowired | |||
private VehicleUserStateManager vehicleUserStateManager; | |||
@@ -61,8 +65,12 @@ public class WinXinServiceHandler extends AbstractAtsServiceHandler<WeiXinServic | |||
return queryPayResultV3Manager.serviceHandle((AtsQueryPayResultV3Request) request); | |||
case REFUNDV2: | |||
return refundV2Manager.serviceHandle((AtsRefundV2Request) request); | |||
case REFUNDQUERYV2: | |||
return queryRefundV2Manager.serviceHandle((AtsQueryRefundV2Request) request); | |||
case REFUNDV3: | |||
return refundV3Manager.serviceHandle((AtsRefundV3Request) request); | |||
case REFUNDQUERYV3: | |||
return queryRefundV3Manager.serviceHandle((AtsQueryRefundV3Request) request); | |||
case MPISSUBSCRIBED: | |||
return wxMessageManager.isSubscribed((WxMpIsSubscribedRequest) request); | |||
case MPSENDMESSAGE: |
@@ -0,0 +1,59 @@ | |||
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.RefundChannel; | |||
import cn.com.taiji.core.model.comm.protocol.ats.weiXin.AtsQueryRefundV2Request; | |||
import cn.com.taiji.core.model.comm.protocol.ats.weiXin.AtsQueryRefundV2Response; | |||
import com.github.binarywang.wxpay.bean.request.WxPayRefundQueryRequest; | |||
import com.github.binarywang.wxpay.bean.result.WxPayRefundQueryResult; | |||
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; | |||
import java.util.List; | |||
import java.util.stream.Collectors; | |||
/** | |||
* @Author:ChenChao | |||
* @Date:2025/5/21 9:25 | |||
* @Filename:QueryRefundV2Manager | |||
* @description: 微信退款查询 | |||
*/ | |||
@Service | |||
public class QueryRefundV2Manager extends AbstractManager { | |||
@Autowired | |||
private WechatConfig wechatConfig; | |||
@Autowired | |||
private WxPayService wxPayService; | |||
public AtsQueryRefundV2Response serviceHandle(AtsQueryRefundV2Request req) throws ServiceHandleException { | |||
//格式校验 | |||
req.validate(); | |||
//配置信息 | |||
WxPayConfig config = | |||
wechatConfig.initV2Config(req.getAppId(), req.getMchId(), req.getMchKey(), req.getSubAppId(), req.getSubMchId()); | |||
wxPayService.setConfig(config); | |||
WxPayRefundQueryRequest request = copyProperties(req, new WxPayRefundQueryRequest()); | |||
WxPayRefundQueryResult result = null; | |||
try { | |||
result = wxPayService.refundQuery(request); | |||
} catch (WxPayException e) { | |||
throw new RuntimeException(e); | |||
} | |||
//处理响应 | |||
AtsQueryRefundV2Response response = copyProperties(result,new AtsQueryRefundV2Response()); | |||
if (result.getRefundCount()>0) { | |||
List<AtsQueryRefundV2Response.RefundRecord> collect = result.getRefundRecords().stream().map(t -> { | |||
AtsQueryRefundV2Response.RefundRecord record = copyProperties(t, new AtsQueryRefundV2Response.RefundRecord()); | |||
record.setRefundChannel(RefundChannel.valueOf(t.getRefundChannel())); | |||
return record; | |||
}).collect(Collectors.toList()); | |||
response.setRefundRecords(collect); | |||
} | |||
return response; | |||
} | |||
} |
@@ -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.entity.dict.pay.RefundChannel; | |||
import cn.com.taiji.core.entity.dict.pay.RefundV3Account; | |||
import cn.com.taiji.core.entity.dict.pay.RefundV3Status; | |||
import cn.com.taiji.core.model.comm.protocol.ats.weiXin.AtsQueryRefundV3Request; | |||
import cn.com.taiji.core.model.comm.protocol.ats.weiXin.AtsQueryRefundV3Response; | |||
import com.wechat.pay.java.core.RSAAutoCertificateConfig; | |||
import com.wechat.pay.java.service.refund.RefundService; | |||
import com.wechat.pay.java.service.refund.model.Amount; | |||
import com.wechat.pay.java.service.refund.model.QueryByOutRefundNoRequest; | |||
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/21 11:15 | |||
* @Filename:QueryRefundV3Manager | |||
* @description: 微信V3 查询退款 | |||
*/ | |||
@Service | |||
public class QueryRefundV3Manager extends AbstractManager { | |||
@Autowired | |||
private WechatConfig wechatConfig; | |||
public AtsQueryRefundV3Response serviceHandle(AtsQueryRefundV3Request 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(); | |||
QueryByOutRefundNoRequest request = copyProperties(req, new QueryByOutRefundNoRequest()); | |||
Refund refund = service.queryByOutRefundNo(request); | |||
//处理响应 | |||
AtsQueryRefundV3Response response = copyProperties(refund, new AtsQueryRefundV3Response()); | |||
Amount amount = refund.getAmount(); | |||
copyProperties(amount,response); | |||
response.setChannel(RefundChannel.valueOf(refund.getChannel().name())); | |||
response.setFundsAccount(RefundV3Account.valueOf(refund.getFundsAccount().name())); | |||
response.setStatus(RefundV3Status.valueOf(refund.getStatus().name())); | |||
return response; | |||
} | |||
} |
@@ -39,6 +39,7 @@ public class RefundV2Manager extends AbstractManager { | |||
AtsRefundV2Response response = new AtsRefundV2Response(); | |||
try { | |||
WxPayRefundResult refund = wxPayService.refund(request); | |||
logger.info("微信平台V2退款申请返回数据{}",refund.toString()); | |||
response = copyProperties(refund,response); | |||
} catch (WxPayException e) { | |||
logger.error("微信V2退款失败:{}", e.getMessage()); |
@@ -3,13 +3,15 @@ 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.entity.dict.pay.RefundV3Account; | |||
import cn.com.taiji.core.entity.dict.pay.RefundV3Status; | |||
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.Amount; | |||
import com.wechat.pay.java.service.refund.model.AmountReq; | |||
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; | |||
@@ -35,12 +37,27 @@ public class RefundV3Manager extends AbstractManager { | |||
wechatConfig.initV3Config(req.getMchid(), req.getApiV3Key(), req.getPrivateKey(), req.getSerialNo()); | |||
// 初始化服务 | |||
RefundService service = new RefundService.Builder().config(config).build(); | |||
// 创建请求对象 | |||
CreateRequest createRequest = copyProperties(req, new CreateRequest()); | |||
createRequest.setAmount(copyProperties(req ,new AmountReq())); | |||
//请求服务 | |||
Refund refund = service.create(createRequest); | |||
logger.info("微信平台V3退款申请返回数据{}",refund.toString()); | |||
//处理响应 | |||
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())); | |||
Amount amount = refund.getAmount(); | |||
response.setTotal(amount.getTotal()); | |||
response.setRefund(amount.getRefund()); | |||
response.setPayerTotal(amount.getPayerTotal()); | |||
response.setPayerRefund(amount.getPayerRefund()); | |||
response.setSettlementTotal(amount.getSettlementTotal()); | |||
response.setSettlementRefund(amount.getSettlementRefund()); | |||
response.setDiscountRefund(amount.getDiscountRefund()); | |||
response.setCurrency(amount.getCurrency()); | |||
response.setChannel(RefundChannel.valueOf(refund.getChannel().name())); | |||
response.setFundsAccount(RefundV3Account.valueOf(refund.getFundsAccount().name())); | |||
response.setStatus(RefundV3Status.valueOf(refund.getStatus().name())); | |||
return response; | |||
} | |||
} |