feat(wechat): 实现微信分账 V2 和 V3 接口, 修复微信支付 v2 接口设置分账不生效问题

This commit is contained in:
bootx
2024-12-16 22:48:32 +08:00
parent d0bab54db5
commit f85eca4296
10 changed files with 239 additions and 32 deletions

View File

@@ -2,10 +2,7 @@ package cn.bootx.platform.core.util;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.hutool.json.*;
import lombok.experimental.UtilityClass;
/**

View File

@@ -143,6 +143,7 @@ public class AliPayAllocationService {
if (Objects.nonNull(detail)) {
detail.setResult(this.getDetailResultEnum(receiver.getState()).getCode());
detail.setErrorMsg(receiver.getErrorDesc());
detail.setOutDetailId(receiver.getDetailId());
// 如果是完成, 更新时间
if (AllocDetailResultEnum.SUCCESS.getCode().equals(detail.getResult())){
LocalDateTime finishTime = LocalDateTimeUtil.of(receiver.getExecuteDt());

View File

@@ -0,0 +1,41 @@
package org.dromara.daxpay.channel.wechat.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.dromara.daxpay.core.enums.AllocDetailResultEnum;
import org.dromara.daxpay.core.exception.ConfigNotExistException;
import java.util.Arrays;
import java.util.Objects;
/**
*
* @author xxm
* @since 2024/12/16
*/
@Getter
@AllArgsConstructor
public enum WechatAllocStatusEnum {
//处理中
PENDING("PENDING", AllocDetailResultEnum.PENDING),
//分账完成
SUCCESS("SUCCESS", AllocDetailResultEnum.SUCCESS),
//分账完成
CLOSED("CLOSED", AllocDetailResultEnum.FAIL),
;
private final String code;
private final AllocDetailResultEnum result;
/**
* 根据编码获取枚举
*/
public static WechatAllocStatusEnum findByCode(String code){
return Arrays.stream(values())
.filter(o -> Objects.equals(o.getCode(), code))
.findFirst()
.orElseThrow(() -> new ConfigNotExistException("该微信分账状态不存在"));
}
}

View File

@@ -1,14 +1,41 @@
package org.dromara.daxpay.channel.wechat.service.allocation;
import cn.bootx.platform.common.mybatisplus.function.CollectorsFunction;
import cn.hutool.json.JSONUtil;
import com.github.binarywang.wxpay.bean.profitsharing.request.ProfitSharingQueryRequest;
import com.github.binarywang.wxpay.bean.profitsharing.request.ProfitSharingRequest;
import com.github.binarywang.wxpay.bean.profitsharing.request.ProfitSharingUnfreezeRequest;
import com.github.binarywang.wxpay.bean.profitsharing.request.ProfitSharingV3Request;
import com.github.binarywang.wxpay.bean.profitsharing.result.ProfitSharingQueryResult;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.ProfitSharingService;
import com.github.binarywang.wxpay.service.WxPayService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.daxpay.channel.wechat.entity.config.WechatPayConfig;
import org.dromara.daxpay.channel.wechat.enums.WechatAllocStatusEnum;
import org.dromara.daxpay.channel.wechat.service.config.WechatPayConfigService;
import org.dromara.daxpay.channel.wechat.util.WechatPayUtil;
import org.dromara.daxpay.core.enums.AllocDetailResultEnum;
import org.dromara.daxpay.core.enums.AllocReceiverTypeEnum;
import org.dromara.daxpay.core.enums.AllocationStatusEnum;
import org.dromara.daxpay.core.exception.ConfigErrorException;
import org.dromara.daxpay.core.exception.OperationFailException;
import org.dromara.daxpay.core.util.PayUtil;
import org.dromara.daxpay.service.bo.allocation.AllocStartResultBo;
import org.dromara.daxpay.service.bo.allocation.AllocSyncResultBo;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocDetail;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocOrder;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import static org.dromara.daxpay.core.enums.AllocReceiverTypeEnum.MERCHANT_NO;
import static org.dromara.daxpay.core.enums.AllocReceiverTypeEnum.OPEN_ID;
/**
* 微信分账V2版本接口
@@ -20,24 +47,120 @@ import java.util.List;
@RequiredArgsConstructor
public class WeChatPayAllocationV2Service {
private final WechatPayConfigService wechatPayConfigService;
/**
* 分账
*/
public AllocStartResultBo start(AllocOrder order, List<AllocDetail> details, WechatPayConfig config) {
return null;
public AllocStartResultBo start(AllocOrder allocOrder, List<AllocDetail> details, WechatPayConfig config) {
WxPayService wxPayService = wechatPayConfigService.wxJavaSdk(config);
ProfitSharingService sharingService = wxPayService.getProfitSharingService();
ProfitSharingRequest request = new ProfitSharingRequest();
request.setAppid(config.getWxAppId());
request.setOutOrderNo(allocOrder.getAllocNo());
request.setTransactionId(allocOrder.getOutOrderNo());
// 分账接收方信息
var receivers = details.stream()
.map(o -> {
AllocReceiverTypeEnum receiverTypeEnum = AllocReceiverTypeEnum.findByCode(o.getReceiverType());
String receiverType = this.getReceiverType(receiverTypeEnum);
var receiver = new ProfitSharingV3Request.Receiver();
receiver.setType(receiverType);
receiver.setAmount(PayUtil.convertCentAmount(o.getAmount()));
receiver.setAccount(o.getReceiverAccount());
receiver.setName(o.getReceiverName());
receiver.setDescription("订单分账");
return receiver;
})
.toList();
request.setReceivers(JSONUtil.toJsonStr(receivers));
try {
var result = sharingService.multiProfitSharing(request);
return new AllocStartResultBo().setOutAllocNo(result.getTransactionId());
} catch (WxPayException e) {
throw new OperationFailException("微信分账V2失败: "+e.getMessage());
}
}
/**
* 完结
*/
public void finish(AllocOrder order, List<AllocDetail> details, WechatPayConfig config) {
public void finish(AllocOrder allocOrder, WechatPayConfig config) {
WxPayService wxPayService = wechatPayConfigService.wxJavaSdk(config);
ProfitSharingService sharingService = wxPayService.getProfitSharingService();
var request = new ProfitSharingUnfreezeRequest();
request.setOutOrderNo(String.valueOf(allocOrder.getId()));
request.setTransactionId(allocOrder.getOutOrderNo());
request.setDescription("分账完结");
try {
sharingService.profitSharingFinish(request);
} catch (WxPayException e) {
throw new OperationFailException("微信分账完结V2失败: "+e.getMessage());
}
}
/**
* 同步
*/
public void sync(AllocOrder order, List<AllocDetail> details, WechatPayConfig config) {
public AllocSyncResultBo sync(AllocOrder allocOrder, List<AllocDetail> details, WechatPayConfig config) {
WxPayService wxPayService = wechatPayConfigService.wxJavaSdk(config);
ProfitSharingService sharingService = wxPayService.getProfitSharingService();
ProfitSharingQueryRequest request = new ProfitSharingQueryRequest();
// 根据订单状态判断 使用ID还是分账号
request.setTransactionId(allocOrder.getOutOrderNo());
if (Objects.equals(AllocationStatusEnum.PROCESSING.getCode(), allocOrder.getStatus())){
request.setOutOrderNo(allocOrder.getAllocNo());
} else {
request.setOutOrderNo(String.valueOf(allocOrder.getId()));
}
ProfitSharingQueryResult result;
try {
result = sharingService.profitSharingQuery(request);
} catch (WxPayException e) {
throw new OperationFailException("微信分账订单查询V2失败: "+e.getMessage());
}
var detailMap = details.stream()
.collect(Collectors.toMap(AllocDetail::getReceiverAccount, Function.identity(), CollectorsFunction::retainLatest));
var royaltyDetailList = result.getReceivers();
for (var receiver : royaltyDetailList) {
var detail = detailMap.get(receiver.getAccount());
if (Objects.nonNull(detail)) {
detail.setResult(this.getDetailResultEnum(receiver.getResult()).getCode());
detail.setErrorMsg(receiver.getFailReason());
detail.setOutDetailId(receiver.getDetailId());
// 如果是完成, 更新时间
if (AllocDetailResultEnum.SUCCESS.getCode().equals(detail.getResult())){
LocalDateTime finishTime = WechatPayUtil.parseV2(receiver.getFinishTime());
detail.setFinishTime(finishTime)
.setErrorMsg(null)
.setErrorCode(null);
}
}
}
return new AllocSyncResultBo().setSyncInfo(JSONUtil.toJsonStr(result));
}
/**
* 获取分账接收方类型编码
*/
private String getReceiverType(AllocReceiverTypeEnum receiverTypeEnum){
if (receiverTypeEnum == OPEN_ID){
return "PERSONAL_OPENID";
}
if (receiverTypeEnum == MERCHANT_NO){
return "MERCHANT_ID";
}
throw new ConfigErrorException("分账接收方类型错误");
}
/**
* 转换支付宝分账类型到系统中统一的状态
*/
private AllocDetailResultEnum getDetailResultEnum (String result){
return WechatAllocStatusEnum.findByCode(result).getResult();
}
}

View File

@@ -1,6 +1,7 @@
package org.dromara.daxpay.channel.wechat.service.allocation;
import com.github.binarywang.wxpay.bean.profitsharing.request.ProfitSharingQueryV3Request;
import cn.bootx.platform.common.mybatisplus.function.CollectorsFunction;
import cn.hutool.json.JSONUtil;
import com.github.binarywang.wxpay.bean.profitsharing.request.ProfitSharingUnfreezeV3Request;
import com.github.binarywang.wxpay.bean.profitsharing.request.ProfitSharingV3Request;
import com.github.binarywang.wxpay.bean.profitsharing.result.ProfitSharingV3Result;
@@ -10,17 +11,26 @@ import com.github.binarywang.wxpay.service.WxPayService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.daxpay.channel.wechat.entity.config.WechatPayConfig;
import org.dromara.daxpay.channel.wechat.enums.WechatAllocStatusEnum;
import org.dromara.daxpay.channel.wechat.service.config.WechatPayConfigService;
import org.dromara.daxpay.channel.wechat.util.WechatPayUtil;
import org.dromara.daxpay.core.enums.AllocDetailResultEnum;
import org.dromara.daxpay.core.enums.AllocReceiverTypeEnum;
import org.dromara.daxpay.core.enums.AllocationStatusEnum;
import org.dromara.daxpay.core.exception.ConfigErrorException;
import org.dromara.daxpay.core.exception.OperationFailException;
import org.dromara.daxpay.core.util.PayUtil;
import org.dromara.daxpay.service.bo.allocation.AllocStartResultBo;
import org.dromara.daxpay.service.bo.allocation.AllocSyncResultBo;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocDetail;
import org.dromara.daxpay.service.entity.allocation.transaction.AllocOrder;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
import static org.dromara.daxpay.core.enums.AllocReceiverTypeEnum.MERCHANT_NO;
import static org.dromara.daxpay.core.enums.AllocReceiverTypeEnum.OPEN_ID;
@@ -37,10 +47,9 @@ public class WeChatPayAllocationV3Service {
private final WechatPayConfigService wechatPayConfigService;
/**
* 发起分账 使用分账号作为请求号4
* 发起分账 使用分账号作为请求号
*/
public AllocStartResultBo start(AllocOrder allocOrder, List<AllocDetail> orderDetails, WechatPayConfig config){
WxPayService wxPayService = wechatPayConfigService.wxJavaSdk(config);
ProfitSharingService sharingService = wxPayService.getProfitSharingService();
@@ -69,7 +78,7 @@ public class WeChatPayAllocationV3Service {
request.setReceivers(receivers);
try {
ProfitSharingV3Result result = sharingService.profitSharingV3(request);
return new AllocStartResultBo().setOutAllocNo(result.getOrderId());
return new AllocStartResultBo().setOutAllocNo(result.getTransactionId());
} catch (WxPayException e) {
throw new OperationFailException("微信分账V3失败: "+e.getMessage());
}
@@ -78,7 +87,7 @@ public class WeChatPayAllocationV3Service {
/**
* 分账完结 使用ID作为请求号
*/
public void finish(AllocOrder allocOrder, List<AllocDetail> details, WechatPayConfig config){
public void finish(AllocOrder allocOrder, WechatPayConfig config){
WxPayService wxPayService = wechatPayConfigService.wxJavaSdk(config);
ProfitSharingService sharingService = wxPayService.getProfitSharingService();
var request = new ProfitSharingUnfreezeV3Request();
@@ -86,28 +95,51 @@ public class WeChatPayAllocationV3Service {
request.setTransactionId(allocOrder.getOutOrderNo());
request.setDescription("分账完结");
try {
var result = sharingService.profitSharingUnfreeze(request);
sharingService.profitSharingUnfreeze(request);
} catch (WxPayException e) {
throw new OperationFailException("微信分账V3失败: "+e.getMessage());
throw new OperationFailException("微信分账完结V3失败: "+e.getMessage());
}
}
/**
* 同步
*/
public void sync(AllocOrder allocOrder, List<AllocDetail> details, WechatPayConfig config){
public AllocSyncResultBo sync(AllocOrder allocOrder, List<AllocDetail> details, WechatPayConfig config){
WxPayService wxPayService = wechatPayConfigService.wxJavaSdk(config);
ProfitSharingService sharingService = wxPayService.getProfitSharingService();
ProfitSharingQueryV3Request request = new ProfitSharingQueryV3Request();
request.setOutOrderNo(allocOrder.getAllocNo());
// 根据订单状态判断
// 根据订单状态判断 使用ID还是分账号
String outOrderNo;
if (Objects.equals(AllocationStatusEnum.PROCESSING.getCode(), allocOrder.getStatus())){
outOrderNo = allocOrder.getAllocNo();
} else {
outOrderNo = String.valueOf(allocOrder.getId());
}
request.setTransactionId(allocOrder.getOutOrderNo());
ProfitSharingV3Result result;
try {
var result = sharingService.profitSharingQueryV3(request);
result = sharingService.profitSharingQueryV3(outOrderNo,allocOrder.getOutOrderNo());
} catch (WxPayException e) {
throw new OperationFailException("微信分账订单查询V3失败: "+e.getMessage());
}
var detailMap = details.stream()
.collect(Collectors.toMap(AllocDetail::getReceiverAccount, Function.identity(), CollectorsFunction::retainLatest));
var royaltyDetailList = result.getReceivers();
for (var receiver : royaltyDetailList) {
var detail = detailMap.get(receiver.getAccount());
if (Objects.nonNull(detail)) {
detail.setResult(this.getDetailResultEnum(receiver.getResult()).getCode());
detail.setErrorMsg(receiver.getFailReason());
detail.setOutDetailId(receiver.getDetailId());
// 如果是完成, 更新时间
if (AllocDetailResultEnum.SUCCESS.getCode().equals(detail.getResult())){
LocalDateTime finishTime = WechatPayUtil.parseV3(receiver.getFinishTime());
detail.setFinishTime(finishTime)
.setErrorMsg(null)
.setErrorCode(null);
}
}
}
return new AllocSyncResultBo().setSyncInfo(JSONUtil.toJsonStr(result));
}
/**
@@ -123,4 +155,12 @@ public class WeChatPayAllocationV3Service {
throw new ConfigErrorException("分账接收方类型错误");
}
/**
* 转换支付宝分账类型到系统中统一的状态
*/
private AllocDetailResultEnum getDetailResultEnum (String result){
return WechatAllocStatusEnum.findByCode(result).getResult();
}
}

View File

@@ -165,7 +165,7 @@ public class WechatPayV2Service {
request.setSpbillCreateIp(payOrder.getClientIp());
request.setDetail(payOrder.getDescription());
request.setBody(payOrder.getTitle());
request.setProfitSharing(payOrder.getAutoAllocation()?"Y":"N");
request.setProfitSharing(payOrder.getAllocation()?"Y":"N");
try {
WxPayMicropayResult result = wxPayService.micropay(request);
// 支付成功处理, 如果不成功会走异常流
@@ -232,6 +232,7 @@ public class WechatPayV2Service {
request.setTotalFee(PayUtil.convertCentAmount(payOrder.getAmount()));
request.setNotifyUrl(wechatPayConfigService.getPayNotifyUrl());
request.setSpbillCreateIp(payOrder.getClientIp());
request.setProfitSharing(payOrder.getAllocation()?"Y":"N");
return request;
}

View File

@@ -65,9 +65,9 @@ public class WechatAllocationStrategy extends AbsAllocationStrategy {
@Override
public void finish() {
if (Objects.equals(config.getApiVersion(), WechatPayCode.API_V3)){
weChatPayAllocationV3Service.finish(getOrder(), this.getDetails(), this.config);
weChatPayAllocationV3Service.finish(getOrder(), this.config);
} else {
weChatPayAllocationV2Service.finish(getOrder(), this.getDetails(), this.config);
weChatPayAllocationV2Service.finish(getOrder(), this.config);
}
}
@@ -77,13 +77,10 @@ public class WechatAllocationStrategy extends AbsAllocationStrategy {
@Override
public AllocSyncResultBo doSync() {
if (Objects.equals(config.getApiVersion(), WechatPayCode.API_V3)){
weChatPayAllocationV3Service.finish(getOrder(), this.getDetails(), this.config);
weChatPayAllocationV3Service.sync(getOrder(), this.getDetails(), this.config);
return weChatPayAllocationV3Service.sync(getOrder(), this.getDetails(), this.config);
} else {
weChatPayAllocationV2Service.sync(getOrder(), this.getDetails(), this.config);
return weChatPayAllocationV2Service.sync(getOrder(), this.getDetails(), this.config);
}
return null;
}
}

View File

@@ -119,14 +119,14 @@ public class WechatPayUtil {
}
/**
* v3接口时间序列
* v2接口时间序列
*/
public LocalDateTime parseV2(String date) {
return LocalDateTimeUtil.parse(date, DatePattern.PURE_DATETIME_PATTERN);
}
/**
* v3接口时间序列
* v3接口时间序列
*/
public LocalDateTime parseV3(String dateStr) {
return LocalDateTimeUtil.parse(dateStr, "yyyy-MM-dd'T'HH:mm:ss+08:00");

View File

@@ -28,6 +28,9 @@ public class AllocDetail extends MchAppBaseEntity implements ToResult<AllocDetai
/** 分账订单ID */
private Long allocationId;
/** 外部明细ID */
private String outDetailId;
/** 接收者ID */
private Long receiverId;

View File

@@ -32,6 +32,10 @@ public class AllocDetailVo extends MchAppResult implements TransPojo {
@Schema(description = "分账订单ID")
private Long allocationId;
/** 外部明细ID */
@Schema(description = "外部明细ID")
private String outDetailId;
@Schema(description = "分账接收方编号")
private String receiverNo;