mirror of
https://gitee.com/dromara/dax-pay
synced 2026-08-12 15:35:38 +08:00
feat(channel-alipay): 支付宝转账申请配套(转账应用/场景配置/资金流水号回写/时间解析) + 错误码与校验词条(10语)
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
package cn.daxpay.open.channel.alipay.client.req;
|
||||
|
||||
import cn.daxpay.open.channel.alipay.client.credential.AlipaySdkCredential;
|
||||
import cn.daxpay.open.payment.trade.transfer.param.TransferReportInfo;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/// # 支付宝通道转账请求(发起/同步共用)
|
||||
///
|
||||
/// 与子应用 dax-pay-channel-one 的 `AlipayTransferReq` 镜像, 字段对齐。
|
||||
@@ -33,6 +36,12 @@ public class AlipayTransferReq {
|
||||
/// 异步通知地址(支付宝→平台)
|
||||
private String notifyUrl;
|
||||
|
||||
/// 转账场景名称(2026新商户必填,由通道商户转账场景配置注入)
|
||||
private String transferSceneName;
|
||||
|
||||
/// 转账场景上报信息列表(对应支付宝 transfer_scene_report_infos,多条)
|
||||
private List<TransferReportInfo> reportInfos;
|
||||
|
||||
/// 通道调用凭证
|
||||
private AlipaySdkCredential credential;
|
||||
}
|
||||
|
||||
@@ -19,4 +19,13 @@ public class AlipayTransferResp {
|
||||
|
||||
/// 转账完成时间(支付宝 gmt_finish)
|
||||
private String finishTime;
|
||||
|
||||
/// 支付宝资金流水号(pay_fund_order_id,财务对账用)
|
||||
private String payFundOrderId;
|
||||
|
||||
/// 订单支付时间(发起响应 trans_date; 同步响应 pay_date)
|
||||
private String transDate;
|
||||
|
||||
/// 错误码(同步查询 FAIL/REFUND 时返回,用于精准报错)
|
||||
private String errorCode;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package cn.daxpay.open.channel.alipay.controller.direct;
|
||||
|
||||
import cn.daxpay.open.channel.alipay.param.direct.AlipayTransferConfigParam;
|
||||
import cn.daxpay.open.channel.alipay.result.direct.AlipayTransferConfigResult;
|
||||
import cn.daxpay.open.channel.alipay.service.direct.AlipayTransferConfigService;
|
||||
import cn.daxpay.open.platform.core.annotation.PermCode;
|
||||
import cn.daxpay.open.platform.core.code.PermCodes;
|
||||
import cn.daxpay.open.platform.core.rest.Res;
|
||||
import cn.daxpay.open.platform.core.rest.result.Result;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/// # 支付宝转账配置管理(运营端)
|
||||
///
|
||||
/// 管理通道商户的转账配置(转账转出应用), 挂在通道商户菜单下。
|
||||
///
|
||||
@PermCode(menuCode = PermCodes.Channel.Merchant.MENU)
|
||||
@Validated
|
||||
@Tag(name = "支付宝转账配置管理")
|
||||
@RestController
|
||||
@RequestMapping("/admin/alipay/transfer-config")
|
||||
@RequiredArgsConstructor
|
||||
public class AlipayTransferConfigController {
|
||||
|
||||
private final AlipayTransferConfigService alipayTransferConfigService;
|
||||
|
||||
@PermCode(code = PermCodes.Action.VIEW)
|
||||
@Operation(summary = "查询通道商户的转账配置")
|
||||
@GetMapping("/find-by-channel-mch-no")
|
||||
public Result<AlipayTransferConfigResult> findByChannelMchNo(
|
||||
@NotBlank(message = "{validation.field.mchNo.notBlank}") String mchNo,
|
||||
@NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") String channelMchNo) {
|
||||
return Res.ok(alipayTransferConfigService.findByChannelMchNo(mchNo, channelMchNo));
|
||||
}
|
||||
|
||||
@PermCode(code = PermCodes.Action.MANAGE)
|
||||
@Operation(summary = "保存或更新转账配置(一对一)")
|
||||
@PostMapping("/save")
|
||||
public Result<Void> save(@RequestBody @Validated AlipayTransferConfigParam param) {
|
||||
alipayTransferConfigService.saveOrUpdate(param);
|
||||
return Res.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package cn.daxpay.open.channel.alipay.controller.direct;
|
||||
|
||||
import cn.daxpay.open.channel.alipay.result.direct.AlipayTransferSceneConfigResult;
|
||||
import cn.daxpay.open.channel.alipay.result.direct.AlipayTransferSceneOptionResult;
|
||||
import cn.daxpay.open.channel.alipay.service.direct.AlipayTransferSceneConfigService;
|
||||
import cn.daxpay.open.platform.core.annotation.PermCode;
|
||||
import cn.daxpay.open.platform.core.code.PermCodes;
|
||||
import cn.daxpay.open.platform.core.rest.Res;
|
||||
import cn.daxpay.open.platform.core.rest.result.Result;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/// # 支付宝转账场景配置管理(运营端)
|
||||
///
|
||||
/// 按通道商户维度管理转账场景(2026 新商户转账必配),挂在通道商户菜单下。
|
||||
///
|
||||
@PermCode(menuCode = PermCodes.Channel.Merchant.MENU)
|
||||
@Validated
|
||||
@Tag(name = "支付宝转账场景配置管理")
|
||||
@RestController
|
||||
@RequestMapping("/admin/alipay/transfer-scene")
|
||||
@RequiredArgsConstructor
|
||||
public class AlipayTransferSceneConfigController {
|
||||
|
||||
private final AlipayTransferSceneConfigService alipayTransferSceneConfigService;
|
||||
|
||||
@PermCode(code = PermCodes.Action.VIEW)
|
||||
@Operation(summary = "查询通道商户的转账场景列表")
|
||||
@GetMapping("/list")
|
||||
public Result<List<AlipayTransferSceneConfigResult>> list(
|
||||
@NotBlank(message = "{validation.field.mchNo.notBlank}") String mchNo,
|
||||
@NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") String channelMchNo) {
|
||||
return Res.ok(alipayTransferSceneConfigService.list(mchNo, channelMchNo));
|
||||
}
|
||||
|
||||
@PermCode(code = PermCodes.Action.VIEW)
|
||||
@Operation(summary = "查询支付宝转账场景选项列表(主数据枚举投影)")
|
||||
@GetMapping("/scene-options")
|
||||
public Result<List<AlipayTransferSceneOptionResult>> sceneOptions() {
|
||||
return Res.ok(alipayTransferSceneConfigService.findSceneOptions());
|
||||
}
|
||||
|
||||
@PermCode(code = PermCodes.Action.MANAGE)
|
||||
@Operation(summary = "设为默认转账场景(自动启用, 按场景名称按需创建)")
|
||||
@PostMapping("/set-default")
|
||||
public Result<Void> setDefault(
|
||||
@NotBlank(message = "{validation.field.mchNo.notBlank}") String mchNo,
|
||||
@NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") String channelMchNo,
|
||||
@NotBlank(message = "{validation.field.transferSceneName.notBlank}") String sceneName) {
|
||||
alipayTransferSceneConfigService.setDefault(mchNo, channelMchNo, sceneName);
|
||||
return Res.ok();
|
||||
}
|
||||
|
||||
@PermCode(code = PermCodes.Action.MANAGE)
|
||||
@Operation(summary = "切换转账场景启用状态(最多启用3个, 按场景名称按需创建)")
|
||||
@PostMapping("/set-enabled")
|
||||
public Result<Void> setEnabled(
|
||||
@NotBlank(message = "{validation.field.mchNo.notBlank}") String mchNo,
|
||||
@NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") String channelMchNo,
|
||||
@NotBlank(message = "{validation.field.transferSceneName.notBlank}") String sceneName,
|
||||
@NotNull Boolean enabled) {
|
||||
alipayTransferSceneConfigService.setEnabled(mchNo, channelMchNo, sceneName, enabled);
|
||||
return Res.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package cn.daxpay.open.channel.alipay.controller.direct;
|
||||
|
||||
import cn.daxpay.open.channel.alipay.param.direct.AlipayTransferConfigParam;
|
||||
import cn.daxpay.open.channel.alipay.result.direct.AlipayTransferConfigResult;
|
||||
import cn.daxpay.open.channel.alipay.service.direct.AlipayTransferConfigService;
|
||||
import cn.daxpay.open.payment.common.context.PaymentContext;
|
||||
import cn.daxpay.open.platform.core.code.CommonCode;
|
||||
import cn.daxpay.open.platform.core.exception.BizInfoException;
|
||||
import cn.daxpay.open.platform.core.rest.Res;
|
||||
import cn.daxpay.open.platform.core.rest.result.Result;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/// # 支付宝转账配置管理(商户端)
|
||||
///
|
||||
/// 对照运营端 [AlipayTransferConfigController],路径前缀 `/mch/alipay/transfer-config`。
|
||||
/// 商户号一律取自 [PaymentContext],忽略请求中的 mchNo,防越权。
|
||||
///
|
||||
@Validated
|
||||
@Tag(name = "支付宝转账配置管理(商户端)")
|
||||
@RestController
|
||||
@RequestMapping("/mch/alipay/transfer-config")
|
||||
@RequiredArgsConstructor
|
||||
public class MchAlipayTransferConfigController {
|
||||
|
||||
private final AlipayTransferConfigService alipayTransferConfigService;
|
||||
private final PaymentContext paymentContext;
|
||||
|
||||
/// 当前登录商户号(上下文必有;缺则视为会话异常)
|
||||
private String requireMchNo() {
|
||||
String mchNo = paymentContext.getMchNo();
|
||||
if (mchNo == null || mchNo.isBlank()) {
|
||||
// 商户上下文缺失
|
||||
throw new BizInfoException(CommonCode.FAIL_CODE, "pay.error.assist.mchContextMissing");
|
||||
}
|
||||
return mchNo;
|
||||
}
|
||||
|
||||
@Operation(summary = "查询通道商户的转账配置")
|
||||
@GetMapping("/find-by-channel-mch-no")
|
||||
public Result<AlipayTransferConfigResult> findByChannelMchNo(
|
||||
@NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") String channelMchNo) {
|
||||
return Res.ok(alipayTransferConfigService.findByChannelMchNo(requireMchNo(), channelMchNo));
|
||||
}
|
||||
|
||||
@Operation(summary = "保存或更新转账配置(一对一)")
|
||||
@PostMapping("/save")
|
||||
public Result<Void> save(@RequestBody @Validated AlipayTransferConfigParam param) {
|
||||
// 商户号强制取自上下文, 防越权
|
||||
param.setMchNo(requireMchNo());
|
||||
alipayTransferConfigService.saveOrUpdate(param);
|
||||
return Res.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package cn.daxpay.open.channel.alipay.controller.direct;
|
||||
|
||||
import cn.daxpay.open.channel.alipay.result.direct.AlipayTransferSceneConfigResult;
|
||||
import cn.daxpay.open.channel.alipay.result.direct.AlipayTransferSceneOptionResult;
|
||||
import cn.daxpay.open.channel.alipay.service.direct.AlipayTransferSceneConfigService;
|
||||
import cn.daxpay.open.payment.common.context.PaymentContext;
|
||||
import cn.daxpay.open.platform.core.code.CommonCode;
|
||||
import cn.daxpay.open.platform.core.exception.BizInfoException;
|
||||
import cn.daxpay.open.platform.core.rest.Res;
|
||||
import cn.daxpay.open.platform.core.rest.result.Result;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/// # 支付宝转账场景配置管理(商户端)
|
||||
///
|
||||
/// 对照运营端 [AlipayTransferSceneConfigController],路径前缀 `/mch/alipay/transfer-scene`。
|
||||
/// 商户号一律取自 [PaymentContext],忽略请求中的 mchNo,防越权。
|
||||
///
|
||||
@Validated
|
||||
@Tag(name = "支付宝转账场景配置管理(商户端)")
|
||||
@RestController
|
||||
@RequestMapping("/mch/alipay/transfer-scene")
|
||||
@RequiredArgsConstructor
|
||||
public class MchAlipayTransferSceneConfigController {
|
||||
|
||||
private final AlipayTransferSceneConfigService alipayTransferSceneConfigService;
|
||||
private final PaymentContext paymentContext;
|
||||
|
||||
/// 当前登录商户号(上下文必有;缺则视为会话异常)
|
||||
private String requireMchNo() {
|
||||
String mchNo = paymentContext.getMchNo();
|
||||
if (mchNo == null || mchNo.isBlank()) {
|
||||
// 商户上下文缺失
|
||||
throw new BizInfoException(CommonCode.FAIL_CODE, "pay.error.assist.mchContextMissing");
|
||||
}
|
||||
return mchNo;
|
||||
}
|
||||
|
||||
@Operation(summary = "查询通道商户的转账场景列表")
|
||||
@GetMapping("/list")
|
||||
public Result<List<AlipayTransferSceneConfigResult>> list(
|
||||
@NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") String channelMchNo) {
|
||||
return Res.ok(alipayTransferSceneConfigService.list(requireMchNo(), channelMchNo));
|
||||
}
|
||||
|
||||
@Operation(summary = "查询支付宝转账场景选项列表(主数据枚举投影)")
|
||||
@GetMapping("/scene-options")
|
||||
public Result<List<AlipayTransferSceneOptionResult>> sceneOptions() {
|
||||
return Res.ok(alipayTransferSceneConfigService.findSceneOptions());
|
||||
}
|
||||
|
||||
@Operation(summary = "设为默认转账场景(自动启用, 按场景名称按需创建)")
|
||||
@PostMapping("/set-default")
|
||||
public Result<Void> setDefault(
|
||||
@NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") String channelMchNo,
|
||||
@NotBlank(message = "{validation.field.transferSceneName.notBlank}") String sceneName) {
|
||||
alipayTransferSceneConfigService.setDefault(requireMchNo(), channelMchNo, sceneName);
|
||||
return Res.ok();
|
||||
}
|
||||
|
||||
@Operation(summary = "切换转账场景启用状态(最多启用3个, 按场景名称按需创建)")
|
||||
@PostMapping("/set-enabled")
|
||||
public Result<Void> setEnabled(
|
||||
@NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") String channelMchNo,
|
||||
@NotBlank(message = "{validation.field.transferSceneName.notBlank}") String sceneName,
|
||||
@NotNull Boolean enabled) {
|
||||
alipayTransferSceneConfigService.setEnabled(requireMchNo(), channelMchNo, sceneName, enabled);
|
||||
return Res.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package cn.daxpay.open.channel.alipay.convert.direct;
|
||||
|
||||
import cn.daxpay.open.channel.alipay.entity.direct.AlipayTransferConfig;
|
||||
import cn.daxpay.open.channel.alipay.param.direct.AlipayTransferConfigParam;
|
||||
import cn.daxpay.open.channel.alipay.result.direct.AlipayTransferConfigResult;
|
||||
import org.mapstruct.BeanMapping;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.NullValuePropertyMappingStrategy;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/// # 支付宝转账配置转换
|
||||
///
|
||||
/// MapStruct 转换器, 负责转账配置在实体、参数和返回结果之间的转换, 更新时空值不覆盖。
|
||||
/// 冗余展示字段(转出应用名/aliAppId/应用类型)由 Service 填充, 不经 Convert。
|
||||
///
|
||||
@Mapper
|
||||
public interface AlipayTransferConfigConvert {
|
||||
|
||||
AlipayTransferConfigConvert CONVERT = Mappers.getMapper(AlipayTransferConfigConvert.class);
|
||||
|
||||
/// 转换为返回对象
|
||||
AlipayTransferConfigResult toResult(AlipayTransferConfig entity);
|
||||
|
||||
/// 转换为实体
|
||||
AlipayTransferConfig toEntity(AlipayTransferConfigParam param);
|
||||
|
||||
/// 更新源数据到实体(空值不覆盖)
|
||||
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
|
||||
void copy(AlipayTransferConfigParam param, @MappingTarget AlipayTransferConfig entity);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package cn.daxpay.open.channel.alipay.convert.direct;
|
||||
|
||||
import cn.daxpay.open.channel.alipay.entity.direct.AlipayTransferSceneConfig;
|
||||
import cn.daxpay.open.channel.alipay.result.direct.AlipayTransferSceneConfigResult;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/// # 支付宝转账场景配置转换
|
||||
///
|
||||
/// MapStruct 转换器,负责转账场景配置在实体与返回结果之间的转换。
|
||||
///
|
||||
@Mapper
|
||||
public interface AlipayTransferSceneConfigConvert {
|
||||
|
||||
AlipayTransferSceneConfigConvert CONVERT = Mappers.getMapper(AlipayTransferSceneConfigConvert.class);
|
||||
|
||||
/// 转换为返回对象
|
||||
AlipayTransferSceneConfigResult toResult(AlipayTransferSceneConfig entity);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package cn.daxpay.open.channel.alipay.dao.direct;
|
||||
|
||||
import cn.daxpay.open.channel.alipay.entity.direct.AlipayTransferConfig;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.impl.BaseManager;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/// # 支付宝转账配置
|
||||
///
|
||||
/// 一个通道商户一条转账配置(一对一), 提供按通道商户号查询/删除。
|
||||
///
|
||||
@Repository
|
||||
public class AlipayTransferConfigManager extends BaseManager<AlipayTransferConfigMapper, AlipayTransferConfig> {
|
||||
|
||||
/// 按通道商户号查询转账配置(一对一)
|
||||
public Optional<AlipayTransferConfig> findByChannelMchNo(String channelMchNo) {
|
||||
return lambdaQuery()
|
||||
.eq(AlipayTransferConfig::getChannelMchNo, channelMchNo)
|
||||
.oneOpt();
|
||||
}
|
||||
|
||||
/// 按通道商户号删除转账配置(逻辑删除, 通道商户删除时级联清理)
|
||||
public void deleteByChannelMchNo(String channelMchNo) {
|
||||
lambdaUpdate()
|
||||
.eq(AlipayTransferConfig::getChannelMchNo, channelMchNo)
|
||||
.remove();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cn.daxpay.open.channel.alipay.dao.direct;
|
||||
|
||||
import cn.daxpay.open.channel.alipay.entity.direct.AlipayTransferConfig;
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/// # 支付宝转账配置
|
||||
///
|
||||
/// 支付宝转账配置 MyBatis-Plus Mapper, 继承 MPJBaseMapper 支持多表联查。
|
||||
///
|
||||
@Mapper
|
||||
public interface AlipayTransferConfigMapper extends MPJBaseMapper<AlipayTransferConfig> {
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package cn.daxpay.open.channel.alipay.dao.direct;
|
||||
|
||||
import cn.daxpay.open.channel.alipay.entity.direct.AlipayTransferSceneConfig;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.impl.BaseManager;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/// # 支付宝转账场景配置
|
||||
///
|
||||
/// 转账场景配置数据访问管理器,提供按通道商户号查询列表、查询默认场景、清空默认等方法。
|
||||
///
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AlipayTransferSceneConfigManager extends BaseManager<AlipayTransferSceneConfigMapper, AlipayTransferSceneConfig> {
|
||||
|
||||
/// 按通道商户号查询全部场景配置(默认项排前, 启用排前)
|
||||
public List<AlipayTransferSceneConfig> listByChannelMchNo(String channelMchNo) {
|
||||
return lambdaQuery()
|
||||
.eq(AlipayTransferSceneConfig::getChannelMchNo, channelMchNo)
|
||||
.orderByDesc(AlipayTransferSceneConfig::getIsDefault)
|
||||
.orderByDesc(AlipayTransferSceneConfig::getEnabled)
|
||||
.orderByDesc(AlipayTransferSceneConfig::getCreateTime)
|
||||
.list();
|
||||
}
|
||||
|
||||
/// 查询通道商户的默认场景(默认场景必须启用)
|
||||
public Optional<AlipayTransferSceneConfig> findDefault(String channelMchNo) {
|
||||
return lambdaQuery()
|
||||
.eq(AlipayTransferSceneConfig::getChannelMchNo, channelMchNo)
|
||||
.eq(AlipayTransferSceneConfig::getIsDefault, true)
|
||||
.eq(AlipayTransferSceneConfig::getEnabled, true)
|
||||
.oneOpt();
|
||||
}
|
||||
|
||||
/// 统计通道商户已启用的场景数量
|
||||
public long countEnabled(String channelMchNo) {
|
||||
return lambdaQuery()
|
||||
.eq(AlipayTransferSceneConfig::getChannelMchNo, channelMchNo)
|
||||
.eq(AlipayTransferSceneConfig::getEnabled, true)
|
||||
.count();
|
||||
}
|
||||
|
||||
/// 按主键查询
|
||||
public Optional<AlipayTransferSceneConfig> findById(Long id) {
|
||||
if (id == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return lambdaQuery()
|
||||
.eq(AlipayTransferSceneConfig::getId, id)
|
||||
.oneOpt();
|
||||
}
|
||||
|
||||
/// 按通道商户号与场景名称查询(场景行不存在时返回空, 由调用方按需创建)
|
||||
public Optional<AlipayTransferSceneConfig> findByChannelMchNoAndSceneName(String channelMchNo, String sceneName) {
|
||||
return lambdaQuery()
|
||||
.eq(AlipayTransferSceneConfig::getChannelMchNo, channelMchNo)
|
||||
.eq(AlipayTransferSceneConfig::getSceneName, sceneName)
|
||||
.oneOpt();
|
||||
}
|
||||
|
||||
/// 按主键删除(逻辑删除, 由 deleted 字段标记)
|
||||
public void deleteById(Long id) {
|
||||
lambdaUpdate()
|
||||
.eq(AlipayTransferSceneConfig::getId, id)
|
||||
.remove();
|
||||
}
|
||||
|
||||
/// 清空指定通道商户的默认标记(设新默认前调用)
|
||||
public void clearDefault(String channelMchNo) {
|
||||
lambdaUpdate()
|
||||
.eq(AlipayTransferSceneConfig::getChannelMchNo, channelMchNo)
|
||||
.eq(AlipayTransferSceneConfig::getIsDefault, true)
|
||||
.set(AlipayTransferSceneConfig::getIsDefault, false)
|
||||
.update();
|
||||
}
|
||||
|
||||
/// 清空指定通道商户的默认标记(排除指定 id,用于更新自身为默认)
|
||||
public void clearDefaultExclude(String channelMchNo, Long excludeId) {
|
||||
lambdaUpdate()
|
||||
.eq(AlipayTransferSceneConfig::getChannelMchNo, channelMchNo)
|
||||
.eq(AlipayTransferSceneConfig::getIsDefault, true)
|
||||
.ne(AlipayTransferSceneConfig::getId, excludeId)
|
||||
.set(AlipayTransferSceneConfig::getIsDefault, false)
|
||||
.update();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cn.daxpay.open.channel.alipay.dao.direct;
|
||||
|
||||
import cn.daxpay.open.channel.alipay.entity.direct.AlipayTransferSceneConfig;
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/// # 支付宝转账场景配置
|
||||
///
|
||||
/// 支付宝转账场景配置 MyBatis-Plus Mapper,继承 MPJBaseMapper 支持多表联查。
|
||||
///
|
||||
@Mapper
|
||||
public interface AlipayTransferSceneConfigMapper extends MPJBaseMapper<AlipayTransferSceneConfig> {
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package cn.daxpay.open.channel.alipay.entity.direct;
|
||||
|
||||
import cn.daxpay.open.channel.alipay.convert.direct.AlipayTransferConfigConvert;
|
||||
import cn.daxpay.open.channel.alipay.result.direct.AlipayTransferConfigResult;
|
||||
import cn.daxpay.open.payment.common.entity.MchBaseEntity;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.function.ToResult;
|
||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 支付宝转账配置
|
||||
///
|
||||
/// 一个通道商户一条转账配置(一对一), 绑定「转账转出应用」。
|
||||
/// 发起转账时由 [cn.daxpay.open.channel.alipay.strategy.direct.transfer.AlipayTransferStrategy]
|
||||
/// 读取本配置按 [transferAppRefId] 解析转出应用(支付宝直连应用)的 aliAppId 与密钥。
|
||||
///
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@TableName("alipay_transfer_config")
|
||||
public class AlipayTransferConfig extends MchBaseEntity implements ToResult<AlipayTransferConfigResult> {
|
||||
|
||||
/// 通道商户号
|
||||
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||
private String channelMchNo;
|
||||
|
||||
/// 转账转出应用引用(指向 alipay_direct_app 主键, 决定转账使用的 aliAppId 与密钥)
|
||||
private Long transferAppRefId;
|
||||
|
||||
/// 转换
|
||||
@Override
|
||||
public AlipayTransferConfigResult toResult() {
|
||||
return AlipayTransferConfigConvert.CONVERT.toResult(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package cn.daxpay.open.channel.alipay.entity.direct;
|
||||
|
||||
import cn.daxpay.open.channel.alipay.convert.direct.AlipayTransferSceneConfigConvert;
|
||||
import cn.daxpay.open.channel.alipay.result.direct.AlipayTransferSceneConfigResult;
|
||||
import cn.daxpay.open.payment.common.entity.MchBaseEntity;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.function.ToResult;
|
||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 支付宝转账场景配置
|
||||
///
|
||||
/// 按通道商户维度管理转账场景(一对多),承载 2026 年起支付宝对新接入商户强制要求的
|
||||
/// `transfer_scene_name` 与 `transfer_scene_report_infos`。发起转账时按本表配置注入请求。
|
||||
///
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@TableName("alipay_transfer_scene_config")
|
||||
public class AlipayTransferSceneConfig extends MchBaseEntity implements ToResult<AlipayTransferSceneConfigResult> {
|
||||
|
||||
/// 通道商户号
|
||||
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||
private String channelMchNo;
|
||||
|
||||
/// 转账场景名称(8 枚举之一:现金营销/企业退款/佣金报酬/业务结算/二手回收/公益补助/行政补贴和退款/保险理赔)
|
||||
private String sceneName;
|
||||
|
||||
/// 是否启用(一个通道商户最多启用3个, 发起转账时仅可选择启用场景)
|
||||
private Boolean enabled;
|
||||
|
||||
/// 是否默认场景(一个通道商户最多一个默认, 默认场景必须启用, 由部分唯一索引约束)
|
||||
private Boolean isDefault;
|
||||
|
||||
/// 转换
|
||||
@Override
|
||||
public AlipayTransferSceneConfigResult toResult() {
|
||||
return AlipayTransferSceneConfigConvert.CONVERT.toResult(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package cn.daxpay.open.channel.alipay.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/// # 支付宝转账场景枚举
|
||||
///
|
||||
/// 对应支付宝资金转账(alipay.fund.trans.uni.transfer)的转账场景, 场景名称(transfer_scene_name)
|
||||
/// 为支付宝协议固定的中文取值, 直传支付宝。每个场景要求不同的报备字段([reportInfoTypes]),
|
||||
/// 字段值为支付宝协议固定的中文 [infoType], 不可更改。
|
||||
///
|
||||
/// 报备字段内容(infoContent)由商户在发起转账时填写。
|
||||
/// [reportInfoDescriptions] 与 [reportInfoTypes] 一一平行, 描述每个字段含义和支付宝文档示例。
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum AlipayTransferSceneEnum {
|
||||
|
||||
/// 现金营销
|
||||
CASH_MARKETING("现金营销",
|
||||
List.of("活动名称", "奖励说明"),
|
||||
List.of("请描述收款方参与活动的名称", "请描述收款方因什么奖励获取这笔资金")),
|
||||
|
||||
/// 企业退款
|
||||
ENTERPRISE_REFUND("企业退款",
|
||||
List.of("退款原因"),
|
||||
List.of("请描述退款原因,如商品质量问题退款")),
|
||||
|
||||
/// 佣金报酬
|
||||
COMMISSION_REWARD("佣金报酬",
|
||||
List.of("佣金报酬说明"),
|
||||
List.of("请描述接收款项原因,如8月家政服务报酬")),
|
||||
|
||||
/// 业务结算
|
||||
BUSINESS_SETTLEMENT("业务结算",
|
||||
List.of("结算款项名称"),
|
||||
List.of("请描述款项名称,如材料货款")),
|
||||
|
||||
/// 二手回收
|
||||
SECOND_HAND_RECYCLING("二手回收",
|
||||
List.of("回收商品名称"),
|
||||
List.of("请描述回收商品名称,如衣服")),
|
||||
|
||||
/// 公益补助
|
||||
PUBLIC_WELFARE_SUBSIDY("公益补助",
|
||||
List.of("公益活动名称"),
|
||||
List.of("请描述公益活动在民政部的备案名称")),
|
||||
|
||||
/// 行政补贴和退款
|
||||
ADMINISTRATIVE_SUBSIDY("行政补贴和退款",
|
||||
List.of("补贴/退款类型"),
|
||||
List.of("请描述补贴/退款类型,如某地人才补贴")),
|
||||
|
||||
/// 保险理赔
|
||||
INSURANCE_CLAIM("保险理赔",
|
||||
List.of("业务类型", "保险险种", "业务交易订单号"),
|
||||
List.of("请描述业务类型,如理赔、退保、其他",
|
||||
"请描述保险险种及产品名称,如医疗险-某百万医疗保险",
|
||||
"请描述这笔转账的业务内部交易订单号"));
|
||||
|
||||
/// 转账场景名称(支付宝协议固定中文取值, 直传 transfer_scene_name)
|
||||
private final String sceneName;
|
||||
|
||||
/// 报备字段定义(支付宝协议固定中文 infoType, 顺序即报备明细下标)
|
||||
private final List<String> reportInfoTypes;
|
||||
|
||||
/// 报备字段说明(与 reportInfoTypes 平行, 描述字段含义和支付宝文档示例)
|
||||
private final List<String> reportInfoDescriptions;
|
||||
|
||||
/// 根据场景名称查找枚举
|
||||
public static AlipayTransferSceneEnum findBySceneName(String sceneName) {
|
||||
for (AlipayTransferSceneEnum scene : values()) {
|
||||
if (scene.sceneName.equals(sceneName)) {
|
||||
return scene;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.daxpay.open.channel.alipay.param.direct;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 支付宝转账配置保存参数
|
||||
///
|
||||
/// 一对一 upsert: 存在则更新, 不存在则新增。`transferAppRefId` 必填,
|
||||
/// 未绑定时发起转账将报错提示先绑定转出应用。
|
||||
///
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "支付宝转账配置保存参数")
|
||||
public class AlipayTransferConfigParam {
|
||||
|
||||
@NotBlank(message = "{validation.field.mchNo.notBlank}")
|
||||
@Schema(description = "商户号")
|
||||
private String mchNo;
|
||||
|
||||
@NotBlank(message = "{validation.field.channelMerchantNo.notBlank}")
|
||||
@Schema(description = "通道商户号")
|
||||
private String channelMchNo;
|
||||
|
||||
@NotNull(message = "{validation.field.transferAppRefId.notNull}")
|
||||
@Schema(description = "转账转出应用引用(指向 alipay_direct_app 主键)")
|
||||
private Long transferAppRefId;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package cn.daxpay.open.channel.alipay.result.direct;
|
||||
|
||||
import cn.daxpay.open.platform.core.result.BaseResult;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 支付宝转账配置
|
||||
///
|
||||
/// 转账配置返回结果对象。冗余展示字段(转出应用名/aliAppId/应用类型)由
|
||||
/// [cn.daxpay.open.channel.alipay.service.direct.AlipayTransferConfigService] 填充,
|
||||
/// 不经 MapStruct 自动映射。
|
||||
///
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "支付宝转账配置")
|
||||
public class AlipayTransferConfigResult extends BaseResult {
|
||||
|
||||
@Schema(description = "商户号")
|
||||
private String mchNo;
|
||||
|
||||
@Schema(description = "通道商户号")
|
||||
private String channelMchNo;
|
||||
|
||||
@Schema(description = "转账转出应用引用")
|
||||
private Long transferAppRefId;
|
||||
|
||||
// ===== 冗余展示(由 Service 填充) =====
|
||||
|
||||
@Schema(description = "转出应用名称")
|
||||
private String transferAppName;
|
||||
|
||||
@Schema(description = "转出应用支付宝AppId")
|
||||
private String aliAppId;
|
||||
|
||||
@Schema(description = "转出应用类型(mini_program-小程序/mobile_app-移动应用/web_app-网站应用)")
|
||||
private String appType;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package cn.daxpay.open.channel.alipay.result.direct;
|
||||
|
||||
import cn.daxpay.open.platform.core.result.BaseResult;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/// # 支付宝转账场景配置
|
||||
///
|
||||
/// 转账场景配置返回结果对象。报备字段元数据([reportInfoTypes]/[reportInfoDescriptions])
|
||||
/// 由枚举 [cn.daxpay.open.channel.alipay.enums.AlipayTransferSceneEnum] 推导, 供前端动态渲染报备输入框。
|
||||
///
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "支付宝转账场景配置")
|
||||
public class AlipayTransferSceneConfigResult extends BaseResult {
|
||||
|
||||
@Schema(description = "商户号")
|
||||
private String mchNo;
|
||||
|
||||
@Schema(description = "通道商户号")
|
||||
private String channelMchNo;
|
||||
|
||||
@Schema(description = "转账场景名称")
|
||||
private String sceneName;
|
||||
|
||||
@Schema(description = "是否启用")
|
||||
private Boolean enabled;
|
||||
|
||||
@Schema(description = "是否默认场景")
|
||||
private Boolean isDefault;
|
||||
|
||||
@Schema(description = "报备字段定义(支付宝协议固定中文 infoType, 由枚举推导)")
|
||||
private List<String> reportInfoTypes;
|
||||
|
||||
@Schema(description = "报备字段说明(与 reportInfoTypes 平行, 含支付宝文档示例)")
|
||||
private List<String> reportInfoDescriptions;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package cn.daxpay.open.channel.alipay.result.direct;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/// # 支付宝转账场景选项结果
|
||||
///
|
||||
/// 供前端场景卡片渲染与报备字段动态展示。报备字段 [reportInfoTypes] 为支付宝协议固定中文
|
||||
/// [infoType], 不可更改; 顺序即发起转账时 [infoContent] 的填写下标。
|
||||
///
|
||||
/// [reportInfoDescriptions] 与 [reportInfoTypes] 平行, 描述每个字段含义和支付宝文档示例。
|
||||
/// 由枚举 [cn.daxpay.open.channel.alipay.enums.AlipayTransferSceneEnum] 投影, 不查库。
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "支付宝转账场景选项")
|
||||
public class AlipayTransferSceneOptionResult {
|
||||
|
||||
@Schema(description = "转账场景名称(支付宝协议固定中文取值)")
|
||||
private String sceneName;
|
||||
|
||||
@Schema(description = "报备字段定义(支付宝协议固定中文 infoType)")
|
||||
private List<String> reportInfoTypes;
|
||||
|
||||
@Schema(description = "报备字段说明(与 reportInfoTypes 平行, 含支付宝文档示例)")
|
||||
private List<String> reportInfoDescriptions;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.daxpay.open.channel.alipay.service.direct;
|
||||
|
||||
import cn.daxpay.open.channel.alipay.dao.direct.AlipayDirectAppManager;
|
||||
import cn.daxpay.open.channel.alipay.dao.direct.AlipayDirectChannelMerchantManager;
|
||||
import cn.daxpay.open.channel.alipay.client.credential.AlipaySdkCredential;
|
||||
import cn.daxpay.open.channel.alipay.entity.direct.AlipayDirectApp;
|
||||
@@ -12,6 +13,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/// # 支付宝直连通道凭证组装器
|
||||
@@ -22,7 +24,9 @@ import java.util.Optional;
|
||||
/// 沙箱标识直接读通道商户固化的 [AlipayDirectChannelMerchant#isSandbox]
|
||||
/// (创建时按当时产品 activeEnv 写入, 不随产品切换改变), 据此选择对应环境的密钥与网关地址。
|
||||
///
|
||||
/// 应用解析优先级:能力关联(显式配置 > appType 唯一推导) > 未命中报错(拒绝首个兜底)。
|
||||
/// 支付应用解析优先级:能力关联(显式配置 > appType 唯一推导) > 未命中报错(拒绝首个兜底);
|
||||
/// 转账不走能力关联, 由转账配置([cn.daxpay.open.channel.alipay.entity.direct.AlipayTransferConfig])
|
||||
/// 显式指定转出应用, 见 [#buildTransferConfig]。
|
||||
///
|
||||
/// 供支付策略([cn.daxpay.open.channel.alipay.strategy.direct.AlipayDirectPayStrategy])组装通道调用凭证。
|
||||
@Slf4j
|
||||
@@ -33,6 +37,7 @@ public class AlipayDirectConfigAssembler {
|
||||
private final AlipayDirectChannelMerchantManager alipayDirectChannelMerchantManager;
|
||||
private final AlipayDirectAppKeyConfigService alipayDirectAppKeyConfigService;
|
||||
private final AlipayDirectAppCapabilityService alipayDirectAppCapabilityService;
|
||||
private final AlipayDirectAppManager alipayDirectAppManager;
|
||||
|
||||
/// 组装直连商户的通道调用凭证(下发给子应用)
|
||||
///
|
||||
@@ -42,6 +47,29 @@ public class AlipayDirectConfigAssembler {
|
||||
/// @return 支付宝 SDK 凭证, 字段对齐子应用 AlipaySdkCredential
|
||||
public AlipaySdkCredential buildConfig(String mchNo, String channelMchNo, String capability) {
|
||||
AlipayDirectApp app = resolveApp(mchNo, channelMchNo, capability);
|
||||
return assembleCredential(channelMchNo, app);
|
||||
}
|
||||
|
||||
/// 组装转账通道调用凭证(转账无能力维度, 按转账配置显式指定的应用)
|
||||
///
|
||||
/// @param mchNo 商户号(归属校验)
|
||||
/// @param channelMchNo 通道商户号
|
||||
/// @param appRefId 转账转出应用引用(alipay_direct_app 主键, 由转账配置绑定)
|
||||
/// @return 支付宝 SDK 凭证
|
||||
public AlipaySdkCredential buildTransferConfig(String mchNo, String channelMchNo, Long appRefId) {
|
||||
AlipayDirectApp app = alipayDirectAppManager.lambdaQuery()
|
||||
.eq(AlipayDirectApp::getId, appRefId)
|
||||
.oneOpt()
|
||||
.orElseThrow(() -> new DataNotExistException("error.channel.alipay.transferAppNotExist"));
|
||||
if (!Objects.equals(app.getMchNo(), mchNo)) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.alipay.transferAppNotBelong");
|
||||
}
|
||||
return assembleCredential(channelMchNo, app);
|
||||
}
|
||||
|
||||
/// 按通道商户与应用组装凭证(通道商户沙箱快照 + 应用级密钥)
|
||||
private AlipaySdkCredential assembleCredential(String channelMchNo, AlipayDirectApp app) {
|
||||
AlipayDirectChannelMerchant channelMerchant = alipayDirectChannelMerchantManager.lambdaQuery()
|
||||
.eq(AlipayDirectChannelMerchant::getChannelMchNo, channelMchNo)
|
||||
.oneOpt()
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
package cn.daxpay.open.channel.alipay.service.direct;
|
||||
|
||||
import cn.daxpay.open.channel.alipay.convert.direct.AlipayTransferConfigConvert;
|
||||
import cn.daxpay.open.channel.alipay.dao.direct.AlipayDirectAppManager;
|
||||
import cn.daxpay.open.channel.alipay.dao.direct.AlipayDirectChannelMerchantManager;
|
||||
import cn.daxpay.open.channel.alipay.dao.direct.AlipayTransferConfigManager;
|
||||
import cn.daxpay.open.channel.alipay.entity.direct.AlipayDirectApp;
|
||||
import cn.daxpay.open.channel.alipay.entity.direct.AlipayDirectChannelMerchant;
|
||||
import cn.daxpay.open.channel.alipay.entity.direct.AlipayTransferConfig;
|
||||
import cn.daxpay.open.channel.alipay.param.direct.AlipayTransferConfigParam;
|
||||
import cn.daxpay.open.channel.alipay.result.direct.AlipayTransferConfigResult;
|
||||
import cn.daxpay.open.platform.core.code.CommonErrorCode;
|
||||
import cn.daxpay.open.platform.core.exception.BizInfoException;
|
||||
import cn.daxpay.open.platform.core.exception.DataNotExistException;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/// # 支付宝转账配置
|
||||
///
|
||||
/// 管理通道商户的转账配置(一对一: 转账转出应用)。
|
||||
/// 发起转账时由转账策略读取本配置按 [AlipayTransferConfig#getTransferAppRefId]
|
||||
/// 解析转出应用(支付宝直连应用)的 aliAppId 与密钥。
|
||||
///
|
||||
/// 运营端写 [AlipayTransferConfig](MchBaseEntity) 显式 setMchNo, 避免上下文缺失。
|
||||
///
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AlipayTransferConfigService {
|
||||
|
||||
private final AlipayTransferConfigManager alipayTransferConfigManager;
|
||||
private final AlipayDirectChannelMerchantManager alipayDirectChannelMerchantManager;
|
||||
private final AlipayDirectAppManager alipayDirectAppManager;
|
||||
|
||||
/// 查询通道商户的转账配置(一对一, 未配置返回 null)
|
||||
///
|
||||
/// @param mchNo 商户号(归属校验)
|
||||
/// @param channelMchNo 通道商户号
|
||||
/// @return 转账配置(含冗余展示), 不存在返回 null
|
||||
public AlipayTransferConfigResult findByChannelMchNo(String mchNo, String channelMchNo) {
|
||||
assertChannelMerchant(mchNo, channelMchNo);
|
||||
return alipayTransferConfigManager.findByChannelMchNo(channelMchNo)
|
||||
.map(this::toResultWithMeta)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/// 保存或更新转账配置(一对一 upsert, 转账应用必填)
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void saveOrUpdate(AlipayTransferConfigParam param) {
|
||||
// 校验通道商户存在与归属
|
||||
assertChannelMerchant(param.getMchNo(), param.getChannelMchNo());
|
||||
// 校验转出应用: 存在 + 归属
|
||||
AlipayDirectApp app = alipayDirectAppManager.lambdaQuery()
|
||||
.eq(AlipayDirectApp::getId, param.getTransferAppRefId())
|
||||
.oneOpt()
|
||||
.orElseThrow(() -> new DataNotExistException("error.channel.alipay.transferAppNotExist"));
|
||||
if (!Objects.equals(app.getMchNo(), param.getMchNo())) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.alipay.transferAppNotBelong");
|
||||
}
|
||||
// upsert: 存在则全量覆盖, 不存在则新增
|
||||
Optional<AlipayTransferConfig> existing = alipayTransferConfigManager
|
||||
.findByChannelMchNo(param.getChannelMchNo());
|
||||
if (existing.isPresent()) {
|
||||
AlipayTransferConfig entity = existing.get();
|
||||
entity.setTransferAppRefId(param.getTransferAppRefId());
|
||||
alipayTransferConfigManager.updateById(entity);
|
||||
} else {
|
||||
AlipayTransferConfig entity = AlipayTransferConfigConvert.CONVERT.toEntity(param);
|
||||
// 运营端写 MchBaseEntity 必须显式 setMchNo(父类 setter 返回类型不匹配, 单独赋值)
|
||||
entity.setMchNo(param.getMchNo());
|
||||
alipayTransferConfigManager.save(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/// 删除通道商户的转账配置(通道商户删除时级联清理)
|
||||
public void deleteByChannelMchNo(String channelMchNo) {
|
||||
alipayTransferConfigManager.deleteByChannelMchNo(channelMchNo);
|
||||
}
|
||||
|
||||
/// 校验通道商户存在且归属匹配
|
||||
private void assertChannelMerchant(String mchNo, String channelMchNo) {
|
||||
AlipayDirectChannelMerchant channelMerchant = alipayDirectChannelMerchantManager.lambdaQuery()
|
||||
.eq(AlipayDirectChannelMerchant::getChannelMchNo, channelMchNo)
|
||||
.oneOpt()
|
||||
.orElseThrow(() -> new DataNotExistException("error.payment.channel.channelMerchantNotExist"));
|
||||
if (!Objects.equals(channelMerchant.getMchNo(), mchNo)) {
|
||||
throw new BizInfoException(CommonErrorCode.UN_SUPPORTED_OPERATE,
|
||||
"error.channel.alipay.channelMerchantMismatch");
|
||||
}
|
||||
}
|
||||
|
||||
/// 转Result并填充冗余展示(转出应用信息)
|
||||
private AlipayTransferConfigResult toResultWithMeta(AlipayTransferConfig entity) {
|
||||
AlipayTransferConfigResult result = entity.toResult();
|
||||
// 转出应用展示信息
|
||||
if (entity.getTransferAppRefId() != null) {
|
||||
alipayDirectAppManager.lambdaQuery()
|
||||
.eq(AlipayDirectApp::getId, entity.getTransferAppRefId())
|
||||
.oneOpt()
|
||||
.ifPresent(app -> {
|
||||
result.setTransferAppName(app.getAppName());
|
||||
result.setAliAppId(app.getAliAppId());
|
||||
result.setAppType(app.getAppType());
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
package cn.daxpay.open.channel.alipay.service.direct;
|
||||
|
||||
import cn.daxpay.open.channel.alipay.dao.direct.AlipayTransferSceneConfigManager;
|
||||
import cn.daxpay.open.channel.alipay.entity.direct.AlipayTransferSceneConfig;
|
||||
import cn.daxpay.open.channel.alipay.enums.AlipayTransferSceneEnum;
|
||||
import cn.daxpay.open.channel.alipay.result.direct.AlipayTransferSceneConfigResult;
|
||||
import cn.daxpay.open.channel.alipay.result.direct.AlipayTransferSceneOptionResult;
|
||||
import cn.daxpay.open.platform.core.code.CommonErrorCode;
|
||||
import cn.daxpay.open.platform.core.exception.BizInfoException;
|
||||
import cn.daxpay.open.platform.core.exception.DataNotExistException;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/// # 支付宝转账场景配置
|
||||
///
|
||||
/// 管理转账场景配置的启用/默认状态, 采用主数据枚举驱动模式:
|
||||
/// - 场景元数据(8个场景 + 报备字段)由 [AlipayTransferSceneEnum] 投影, 不查库、不预置, 见 [AlipayTransferSceneOptionResult]
|
||||
/// - 配置表仅存"被操作过的"场景行(启用/默认状态), 启用或设默认时按需创建, 无行即为未启用
|
||||
/// - 最多启用3个, 默认1个(必须启用), 发起转账时通过 [findEffective] 解析生效配置
|
||||
///
|
||||
/// 2026 年起支付宝对新接入商户强制要求 `transfer_scene_name` 与 `transfer_scene_report_infos`,
|
||||
/// 未配置场景的商户发起转账将被支付宝拒单。
|
||||
///
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AlipayTransferSceneConfigService {
|
||||
|
||||
/// 最大启用场景数
|
||||
private static final int MAX_ENABLED = 3;
|
||||
|
||||
private final AlipayTransferSceneConfigManager alipayTransferSceneConfigManager;
|
||||
|
||||
/// 查询支付宝转账场景选项列表(主数据枚举投影, 不查库, 供前端卡片渲染与报备字段动态展示)
|
||||
public List<AlipayTransferSceneOptionResult> findSceneOptions() {
|
||||
return Arrays.stream(AlipayTransferSceneEnum.values())
|
||||
.map(scene -> new AlipayTransferSceneOptionResult()
|
||||
.setSceneName(scene.getSceneName())
|
||||
.setReportInfoTypes(scene.getReportInfoTypes())
|
||||
.setReportInfoDescriptions(scene.getReportInfoDescriptions()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// 查询通道商户下的场景配置行(仅已操作过的行, 按枚举固定顺序排序, 填充报备元数据)
|
||||
///
|
||||
/// 未操作过的场景无行, 前端以场景选项为基准渲染, 本方法返回的状态映射到对应卡片。
|
||||
public List<AlipayTransferSceneConfigResult> list(String mchNo, String channelMchNo) {
|
||||
List<AlipayTransferSceneConfig> entities = alipayTransferSceneConfigManager.listByChannelMchNo(channelMchNo);
|
||||
// 按枚举固定顺序排序
|
||||
Map<String, Integer> orderMap = new HashMap<>();
|
||||
AlipayTransferSceneEnum[] enums = AlipayTransferSceneEnum.values();
|
||||
for (int i = 0; i < enums.length; i++) {
|
||||
orderMap.put(enums[i].getSceneName(), i);
|
||||
}
|
||||
return entities.stream()
|
||||
.filter(e -> mchNo == null || mchNo.equals(e.getMchNo()))
|
||||
.sorted(Comparator.comparingInt(e -> orderMap.getOrDefault(e.getSceneName(), Integer.MAX_VALUE)))
|
||||
.map(this::toResultWithMeta)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// 将实体转为Result并填充报备字段元数据(由枚举推导)
|
||||
private AlipayTransferSceneConfigResult toResultWithMeta(AlipayTransferSceneConfig entity) {
|
||||
AlipayTransferSceneConfigResult result = entity.toResult();
|
||||
AlipayTransferSceneEnum scene = AlipayTransferSceneEnum.findBySceneName(entity.getSceneName());
|
||||
if (scene != null) {
|
||||
result.setReportInfoTypes(scene.getReportInfoTypes());
|
||||
result.setReportInfoDescriptions(scene.getReportInfoDescriptions());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// 设置场景启用状态(按场景名称操作, 场景行不存在时自动创建)
|
||||
///
|
||||
/// @param mchNo 商户号(归属校验, 新行写入用)
|
||||
/// @param channelMchNo 通道商户号
|
||||
/// @param sceneName 场景名称(支付宝协议固定中文取值)
|
||||
/// @param enabled 是否启用; 启用时校验上限(最多3个), 禁用时校验非默认
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void setEnabled(String mchNo, String channelMchNo, String sceneName, boolean enabled) {
|
||||
// 校验场景名称合法性
|
||||
AlipayTransferSceneEnum scene = AlipayTransferSceneEnum.findBySceneName(sceneName);
|
||||
if (scene == null) {
|
||||
// 支付宝: 不支持的转账场景: {0}
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.alipay.transferSceneNameInvalid", sceneName);
|
||||
}
|
||||
AlipayTransferSceneConfig entity = alipayTransferSceneConfigManager
|
||||
.findByChannelMchNoAndSceneName(channelMchNo, sceneName)
|
||||
// 主数据模式下无行即为未操作过, 按需创建(启用/设默认才落行)
|
||||
.orElseGet(() -> {
|
||||
var newEntity = new AlipayTransferSceneConfig();
|
||||
// 运营端写 MchBaseEntity 必须显式 setMchNo(父类 setter 返回类型不匹配, 单独赋值)
|
||||
newEntity.setMchNo(mchNo);
|
||||
newEntity.setChannelMchNo(channelMchNo);
|
||||
newEntity.setSceneName(sceneName);
|
||||
newEntity.setEnabled(false);
|
||||
newEntity.setIsDefault(false);
|
||||
return newEntity;
|
||||
});
|
||||
if (!mchNo.equals(entity.getMchNo())) {
|
||||
// 支付宝: 转账场景配置不属于当前商户
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.alipay.transferSceneNotBelong");
|
||||
}
|
||||
if (enabled) {
|
||||
// 已启用的幂等返回, 未启用的校验上限
|
||||
if (!Boolean.TRUE.equals(entity.getEnabled())) {
|
||||
long count = alipayTransferSceneConfigManager.countEnabled(channelMchNo);
|
||||
if (count >= MAX_ENABLED) {
|
||||
// 支付宝: 启用场景不能超过{0}个
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.alipay.transferSceneEnabledLimit", MAX_ENABLED);
|
||||
}
|
||||
}
|
||||
entity.setEnabled(true);
|
||||
} else {
|
||||
// 默认场景不允许禁用
|
||||
if (Boolean.TRUE.equals(entity.getIsDefault())) {
|
||||
// 支付宝: 默认场景不能禁用, 请先取消默认或切换默认到其他场景
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.alipay.transferSceneCannotDisableDefault");
|
||||
}
|
||||
entity.setEnabled(false);
|
||||
}
|
||||
if (entity.getId() == null) {
|
||||
// 新行先落库(自动填充主键)
|
||||
alipayTransferSceneConfigManager.save(entity);
|
||||
} else {
|
||||
alipayTransferSceneConfigManager.updateById(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/// 设为默认场景(按场景名称操作, 场景行不存在时自动创建; 自动启用, 含上限校验, 事务内清旧默认)
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void setDefault(String mchNo, String channelMchNo, String sceneName) {
|
||||
// 校验场景名称合法性
|
||||
AlipayTransferSceneEnum scene = AlipayTransferSceneEnum.findBySceneName(sceneName);
|
||||
if (scene == null) {
|
||||
// 支付宝: 不支持的转账场景: {0}
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.alipay.transferSceneNameInvalid", sceneName);
|
||||
}
|
||||
AlipayTransferSceneConfig entity = alipayTransferSceneConfigManager
|
||||
.findByChannelMchNoAndSceneName(channelMchNo, sceneName)
|
||||
.orElseGet(() -> {
|
||||
var newEntity = new AlipayTransferSceneConfig();
|
||||
// 运营端写 MchBaseEntity 必须显式 setMchNo(父类 setter 返回类型不匹配, 单独赋值)
|
||||
newEntity.setMchNo(mchNo);
|
||||
newEntity.setChannelMchNo(channelMchNo);
|
||||
newEntity.setSceneName(sceneName);
|
||||
newEntity.setEnabled(false);
|
||||
newEntity.setIsDefault(false);
|
||||
return newEntity;
|
||||
});
|
||||
if (!mchNo.equals(entity.getMchNo())) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.alipay.transferSceneNotBelong");
|
||||
}
|
||||
// 设默认时自动启用(含上限校验)
|
||||
if (!Boolean.TRUE.equals(entity.getEnabled())) {
|
||||
long count = alipayTransferSceneConfigManager.countEnabled(channelMchNo);
|
||||
if (count >= MAX_ENABLED) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.alipay.transferSceneEnabledLimit", MAX_ENABLED);
|
||||
}
|
||||
entity.setEnabled(true);
|
||||
}
|
||||
// 清旧默认
|
||||
alipayTransferSceneConfigManager.clearDefault(channelMchNo);
|
||||
entity.setIsDefault(true);
|
||||
if (entity.getId() == null) {
|
||||
// 新行先落库(自动填充主键)
|
||||
alipayTransferSceneConfigManager.save(entity);
|
||||
} else {
|
||||
alipayTransferSceneConfigManager.updateById(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/// 解析发起转账时生效的场景配置
|
||||
///
|
||||
/// @param channelMchNo 通道商户号
|
||||
/// @param configIdStr 订单指定的配置 id(字符串,可空); 非空则优先用指定,空则用默认
|
||||
/// @return 生效的场景配置, 无可用配置抛 transferSceneNotConfigured
|
||||
public AlipayTransferSceneConfig findEffective(String channelMchNo, String configIdStr) {
|
||||
if (StrUtil.isNotBlank(configIdStr)) {
|
||||
Long configId;
|
||||
try {
|
||||
configId = Long.parseLong(configIdStr);
|
||||
} catch (NumberFormatException e) {
|
||||
// 支付宝: 转账场景配置ID格式错误
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.alipay.transferSceneIdInvalid", configIdStr);
|
||||
}
|
||||
var config = alipayTransferSceneConfigManager.findById(configId)
|
||||
.orElseThrow(() -> new DataNotExistException("error.channel.alipay.transferSceneNotFound"));
|
||||
if (!channelMchNo.equals(config.getChannelMchNo())) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.alipay.transferSceneNotBelong");
|
||||
}
|
||||
// 禁用的场景不可用
|
||||
if (!Boolean.TRUE.equals(config.getEnabled())) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.alipay.transferSceneNotConfigured");
|
||||
}
|
||||
return config;
|
||||
}
|
||||
return alipayTransferSceneConfigManager.findDefault(channelMchNo)
|
||||
.orElseThrow(() -> new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.alipay.transferSceneNotConfigured"));
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,14 @@ import cn.daxpay.open.channel.alipay.client.AlipayChannelClient;
|
||||
import cn.daxpay.open.channel.alipay.client.credential.AlipaySdkCredential;
|
||||
import cn.daxpay.open.channel.alipay.client.req.AlipayTransferReq;
|
||||
import cn.daxpay.open.channel.alipay.client.resp.AlipayTransferResp;
|
||||
import cn.daxpay.open.channel.alipay.entity.direct.AlipayTransferSceneConfig;
|
||||
import cn.daxpay.open.channel.alipay.service.direct.AlipayTransferSceneConfigService;
|
||||
import cn.daxpay.open.payment.common.result.DaxResult;
|
||||
import cn.daxpay.open.payment.strategy.transfer.TransferStrategyContext;
|
||||
import cn.daxpay.open.payment.trade.enums.PayFundStatusEnum;
|
||||
import cn.daxpay.open.payment.trade.transfer.bo.TransferResultBo;
|
||||
import cn.daxpay.open.payment.trade.transfer.dao.AlipayTransferOrderManager;
|
||||
import cn.daxpay.open.payment.trade.transfer.entity.AlipayTransferOrder;
|
||||
import cn.daxpay.open.platform.core.code.DaxPayErrorCode;
|
||||
import cn.daxpay.open.platform.core.exception.BizInfoException;
|
||||
import cn.daxpay.open.platform.system.service.config.infra.PlatformUrlConfigService;
|
||||
@@ -16,26 +20,45 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Objects;
|
||||
|
||||
/// # 支付宝转账执行业务服务
|
||||
///
|
||||
/// 通过 [AlipayChannelClient] 调用子应用 dax-pay-channel-one 完成支付宝单笔转账
|
||||
/// (alipay.fund.trans.uni.transfer)。请求构建、响应解析与状态映射在本类中完成。
|
||||
///
|
||||
/// 转账场景(transfer_scene_name): 2026 年起新接入商户必填, 由 [AlipayTransferSceneConfigService]
|
||||
/// 按通道商户配置注入(显式 configId 优先, 否则用默认场景)。
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AlipayTransferService {
|
||||
|
||||
/// 支付宝时间格式(东八区本地时间字面量)
|
||||
private static final DateTimeFormatter ALIPAY_DATE_FMT =
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
private final AlipayChannelClient alipayChannelClient;
|
||||
private final PlatformUrlConfigService platformUrlConfigService;
|
||||
// 转账场景配置(2026 新商户必填)
|
||||
private final AlipayTransferSceneConfigService alipayTransferSceneConfigService;
|
||||
// 支付宝容器(回写资金流水号等通道特有字段)
|
||||
private final AlipayTransferOrderManager alipayTransferOrderManager;
|
||||
|
||||
/// 执行支付宝转账
|
||||
///
|
||||
/// @param context 转账策略上下文(通道特有字段: payeeType/payeeAccount/payeeName)
|
||||
/// @param context 转账策略上下文(通道特有字段: payeeType/payeeAccount/payeeName/transferSceneConfigId)
|
||||
/// @param credential 通道调用凭证
|
||||
/// @return 转账结果
|
||||
public TransferResultBo transfer(TransferStrategyContext context, AlipaySdkCredential credential) {
|
||||
// 转账场景配置(2026 新商户必填): 显式 configId 优先, 否则用通道商户默认
|
||||
AlipayTransferSceneConfig scene = alipayTransferSceneConfigService.findEffective(
|
||||
context.getChannelMchNo(), context.getTransferSceneConfigId());
|
||||
|
||||
AlipayTransferReq req = new AlipayTransferReq();
|
||||
req.setOutBizNo(context.getTransferNo());
|
||||
req.setAmount(context.getAmount());
|
||||
@@ -45,6 +68,9 @@ public class AlipayTransferService {
|
||||
req.setPayeeAccount(context.getPayeeAccount());
|
||||
req.setPayeeName(context.getPayeeName());
|
||||
req.setNotifyUrl(this.buildNotifyUrl(context));
|
||||
req.setTransferSceneName(scene.getSceneName());
|
||||
// 转账场景报备信息由发起方手动填写,从上下文透传(不再从场景配置取)
|
||||
req.setReportInfos(context.getReportInfos());
|
||||
req.setCredential(credential);
|
||||
|
||||
DaxResult<AlipayTransferResp> result = alipayChannelClient.transfer(req);
|
||||
@@ -53,13 +79,17 @@ public class AlipayTransferService {
|
||||
}
|
||||
AlipayTransferResp resp = result.getData();
|
||||
TransferResultBo bo = new TransferResultBo()
|
||||
.setOutTransferNo(resp.getOrderId());
|
||||
.setOutTransferNo(resp.getOrderId())
|
||||
.setPayFundOrderId(resp.getPayFundOrderId())
|
||||
.setFinishTime(this.parseAlipayDate(resp.getTransDate()));
|
||||
// 同步返回 SUCCESS 直接成功, 其余(DEALING等)视为处理中, 交同步/回调确认
|
||||
if (Objects.equals(resp.getStatus(), "SUCCESS")) {
|
||||
bo.setStatus(PayFundStatusEnum.SUCCESS);
|
||||
} else {
|
||||
bo.setStatus(PayFundStatusEnum.PROCESSING);
|
||||
}
|
||||
// 回写支付宝特有字段(资金流水号)到容器
|
||||
this.writeBackPayFundOrderId(context, resp.getPayFundOrderId());
|
||||
return bo;
|
||||
}
|
||||
|
||||
@@ -80,28 +110,60 @@ public class AlipayTransferService {
|
||||
.setSyncErrorMsg(result.getMsg())
|
||||
.setStatus(PayFundStatusEnum.PROCESSING);
|
||||
}
|
||||
return mapSyncResult(result.getData());
|
||||
AlipayTransferResp resp = result.getData();
|
||||
// 回写资金流水号(同步查询可能补获)
|
||||
this.writeBackPayFundOrderId(context, resp.getPayFundOrderId());
|
||||
return mapSyncResult(resp);
|
||||
}
|
||||
|
||||
/// 映射同步结果为平台资金态
|
||||
///
|
||||
/// 支付宝状态: SUCCESS(成功) / FAIL(失败) / REFUND(退回) / DEALING(处理中) / CLOSED(关闭)
|
||||
/// 支付宝状态(uni.transfer): SUCCESS(成功) / FAIL(失败) / DEALING(处理中) / REFUND(退票)
|
||||
/// - FAIL → fail(终态失败, 允许复用原单号重试, 与支付宝"相同 out_biz_no 重发"指引一致)
|
||||
/// - REFUND → close(退票资金退回, 等同关闭)
|
||||
private TransferResultBo mapSyncResult(AlipayTransferResp resp) {
|
||||
String status = resp.getStatus();
|
||||
TransferResultBo bo = new TransferResultBo()
|
||||
.setOutTransferNo(resp.getOrderId())
|
||||
.setPayFundOrderId(resp.getPayFundOrderId())
|
||||
.setFinishTime(this.parseAlipayDate(resp.getFinishTime()))
|
||||
.setSyncErrorCode(resp.getErrorCode())
|
||||
.setSyncErrorMsg(resp.getFailReason());
|
||||
if (Objects.equals(status, "SUCCESS")) {
|
||||
bo.setStatus(PayFundStatusEnum.SUCCESS);
|
||||
} else if (Objects.equals(status, "FAIL") || Objects.equals(status, "CLOSED")) {
|
||||
} else if (Objects.equals(status, "FAIL")) {
|
||||
bo.setStatus(PayFundStatusEnum.FAIL);
|
||||
} else if (Objects.equals(status, "CLOSED") || Objects.equals(status, "REFUND")) {
|
||||
bo.setStatus(PayFundStatusEnum.CLOSE);
|
||||
} else {
|
||||
// DEALING/REFUND/未知: 保持处理中, 由后续同步轮询确认
|
||||
// DEALING/未知: 保持处理中, 由后续同步轮询确认
|
||||
bo.setStatus(PayFundStatusEnum.PROCESSING);
|
||||
}
|
||||
return bo;
|
||||
}
|
||||
|
||||
/// 回写支付宝资金流水号到容器(支付宝特有, 非状态流转, 直接更新)
|
||||
private void writeBackPayFundOrderId(TransferStrategyContext context, String payFundOrderId) {
|
||||
if (StrUtil.isBlank(payFundOrderId) || context.getTrade() == null) {
|
||||
return;
|
||||
}
|
||||
Long containerId = context.getTrade().getContainerId();
|
||||
alipayTransferOrderManager.lambdaUpdate()
|
||||
.eq(AlipayTransferOrder::getId, containerId)
|
||||
.set(AlipayTransferOrder::getPayFundOrderId, payFundOrderId)
|
||||
.update();
|
||||
}
|
||||
|
||||
/// 解析支付宝时间字面量(东八区 yyyy-MM-dd HH:mm:ss)为 OffsetDateTime
|
||||
///
|
||||
/// 支付宝返回的时间字段无时区后缀, 按通道时间解析规范先用 LocalDateTime 接住再附加东八区偏移。
|
||||
private OffsetDateTime parseAlipayDate(String dateStr) {
|
||||
if (StrUtil.isBlank(dateStr)) {
|
||||
return null;
|
||||
}
|
||||
return LocalDateTime.parse(dateStr, ALIPAY_DATE_FMT).atOffset(ZoneOffset.ofHours(8));
|
||||
}
|
||||
|
||||
/// 生成支付宝转账异步通知地址(支付宝→平台)
|
||||
///
|
||||
/// 路径约定: `{backendBaseUrl}/unipay/callback/{mchNo}/{channelMchNo}/alipay`
|
||||
|
||||
@@ -5,6 +5,7 @@ import cn.daxpay.open.channel.alipay.dao.direct.AlipayDirectAppCapabilityManager
|
||||
import cn.daxpay.open.channel.alipay.dao.direct.AlipayDirectAppKeyConfigManager;
|
||||
import cn.daxpay.open.channel.alipay.dao.direct.AlipayDirectAppManager;
|
||||
import cn.daxpay.open.channel.alipay.dao.direct.AlipayDirectChannelMerchantManager;
|
||||
import cn.daxpay.open.channel.alipay.dao.direct.AlipayTransferConfigManager;
|
||||
import cn.daxpay.open.channel.alipay.entity.direct.AlipayDirectApp;
|
||||
import cn.daxpay.open.channel.alipay.entity.direct.AlipayDirectAppAuthConfig;
|
||||
import cn.daxpay.open.channel.alipay.entity.direct.AlipayDirectAppCapability;
|
||||
@@ -34,6 +35,7 @@ public class AlipayDirectChannelMerchantCleanupStrategy implements ChannelMercha
|
||||
private final AlipayDirectAppKeyConfigManager alipayDirectAppKeyConfigManager;
|
||||
private final AlipayDirectAppCapabilityManager alipayDirectAppCapabilityManager;
|
||||
private final AlipayDirectAppAuthConfigManager alipayDirectAppAuthConfigManager;
|
||||
private final AlipayTransferConfigManager alipayTransferConfigManager;
|
||||
|
||||
/// 对应产品: 支付宝直连
|
||||
@Override
|
||||
@@ -50,5 +52,7 @@ public class AlipayDirectChannelMerchantCleanupStrategy implements ChannelMercha
|
||||
alipayDirectAppKeyConfigManager.deleteByField(AlipayDirectAppKeyConfig::getChannelMchNo, channelMchNo);
|
||||
alipayDirectAppCapabilityManager.deleteByField(AlipayDirectAppCapability::getChannelMchNo, channelMchNo);
|
||||
alipayDirectAppAuthConfigManager.deleteByField(AlipayDirectAppAuthConfig::getChannelMchNo, channelMchNo);
|
||||
// 转账配置(转账应用绑定)
|
||||
alipayTransferConfigManager.deleteByChannelMchNo(channelMchNo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package cn.daxpay.open.channel.alipay.strategy.direct.transfer;
|
||||
|
||||
import cn.daxpay.open.channel.alipay.client.credential.AlipaySdkCredential;
|
||||
import cn.daxpay.open.channel.alipay.dao.direct.AlipayTransferConfigManager;
|
||||
import cn.daxpay.open.channel.alipay.entity.direct.AlipayTransferConfig;
|
||||
import cn.daxpay.open.channel.alipay.service.direct.AlipayDirectConfigAssembler;
|
||||
import cn.daxpay.open.channel.alipay.service.payment.transfer.AlipayTransferService;
|
||||
import cn.daxpay.open.payment.strategy.transfer.AbsTransferStrategy;
|
||||
@@ -36,6 +38,7 @@ public class AlipayTransferStrategy extends AbsTransferStrategy {
|
||||
|
||||
private final AlipayTransferService alipayTransferService;
|
||||
private final AlipayDirectConfigAssembler alipayDirectConfigAssembler;
|
||||
private final AlipayTransferConfigManager alipayTransferConfigManager;
|
||||
|
||||
@Override
|
||||
public String getChannel() {
|
||||
@@ -55,6 +58,11 @@ public class AlipayTransferStrategy extends AbsTransferStrategy {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.alipay.transferPayeeAccountRequired");
|
||||
}
|
||||
if (StrUtil.isBlank(param.getTitle())) {
|
||||
// 支付宝: 转账标题必填(order_title 支付宝要求必选)
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.alipay.transferTitleRequired");
|
||||
}
|
||||
}
|
||||
|
||||
/// 发起转账
|
||||
@@ -71,9 +79,18 @@ public class AlipayTransferStrategy extends AbsTransferStrategy {
|
||||
return alipayTransferService.sync(context, credential);
|
||||
}
|
||||
|
||||
/// 组装通道调用凭证(转账无能力维度, 走直连密钥配置)
|
||||
/// 组装通道调用凭证(转账无能力维度, 按转账配置显式绑定应用解析)
|
||||
private AlipaySdkCredential buildCredential(TransferStrategyContext context) {
|
||||
return alipayDirectConfigAssembler.buildConfig(
|
||||
context.getMchNo(), context.getChannelMchNo(), null);
|
||||
// 读取转账配置(一对一绑定转出应用, 未绑定不允许发起)
|
||||
AlipayTransferConfig transferConfig = alipayTransferConfigManager
|
||||
.findByChannelMchNo(context.getChannelMchNo())
|
||||
.orElseThrow(() -> new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.alipay.transferAppNotConfigured"));
|
||||
if (transferConfig.getTransferAppRefId() == null) {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.alipay.transferAppNotConfigured");
|
||||
}
|
||||
return alipayDirectConfigAssembler.buildTransferConfig(
|
||||
context.getMchNo(), context.getChannelMchNo(), transferConfig.getTransferAppRefId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,5 +50,18 @@
|
||||
"transferFailed": "Alipay transfer failed: {0}",
|
||||
"transferQueryFailed": "Alipay transfer query failed: {0}",
|
||||
"transferPayeeTypeInvalid": "Alipay: unsupported payee account type: {0}",
|
||||
"transferPayeeAccountRequired": "Alipay: payee account required"
|
||||
"transferPayeeAccountRequired": "Alipay: payee account required",
|
||||
"transferTitleRequired": "Alipay: transfer title required",
|
||||
"transferSceneNotFound": "Alipay: transfer scene config not found",
|
||||
"transferSceneNameInvalid": "Alipay: unsupported transfer scene: {0}",
|
||||
"transferSceneNotConfigured": "Alipay: no transfer scene configured for this channel merchant, please configure one in 'Transfer Scene' tab of channel merchant detail",
|
||||
"transferSceneNotBelong": "Alipay: transfer scene config does not belong to the current merchant",
|
||||
"transferSceneIdRequired": "Alipay: transfer scene config id required",
|
||||
"transferSceneIdInvalid": "Alipay: invalid transfer scene config id: {0}",
|
||||
"transferSceneEnabledLimit": "Alipay: Cannot enable more than {0} transfer scenes",
|
||||
"transferSceneCannotDisableDefault": "Alipay: Default scene cannot be disabled, please switch default to another scene first",
|
||||
"transferAppNotConfigured": "Alipay: no transfer app bound for this channel merchant, please bind one in 'Transfer App' of channel merchant detail",
|
||||
"transferAppNotExist": "Alipay: transfer app not found",
|
||||
"transferAppNotBelong": "Alipay: transfer app does not belong to the current merchant",
|
||||
"channelMerchantMismatch": "Alipay: channel merchant does not belong to the current merchant"
|
||||
}
|
||||
|
||||
@@ -835,6 +835,20 @@
|
||||
},
|
||||
"runtime": {
|
||||
"size": "Runtime code cannot exceed 16 characters"
|
||||
},
|
||||
"transferSceneName": {
|
||||
"notBlank": "Transfer scene name cannot be empty",
|
||||
"size": "Transfer scene name cannot exceed 64 characters"
|
||||
},
|
||||
"transferSceneReportInfoType": {
|
||||
"size": "Transfer scene report info type cannot exceed 64 characters"
|
||||
},
|
||||
"transferSceneReportInfoContent": {
|
||||
"size": "Transfer scene report info content cannot exceed 200 characters"
|
||||
},
|
||||
"transferSceneId": {
|
||||
"notBlank": "Transfer scene ID cannot be empty",
|
||||
"size": "Transfer scene ID cannot exceed 16 characters"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,5 +50,7 @@
|
||||
"transferFailed": "Transfer Alipay gagal: {0}",
|
||||
"transferQueryFailed": "Kueri transfer Alipay gagal: {0}",
|
||||
"transferPayeeTypeInvalid": "Alipay: jenis akun penerima tidak didukung: {0}",
|
||||
"transferPayeeAccountRequired": "Alipay: akun penerima wajib"
|
||||
"transferPayeeAccountRequired": "Alipay: akun penerima wajib",
|
||||
"transferSceneEnabledLimit": "Alipay: Tidak dapat mengaktifkan lebih dari {0} adegan transfer",
|
||||
"transferSceneCannotDisableDefault": "Alipay: Adegan default tidak dapat dinonaktifkan, alihkan default ke adegan lain terlebih dahulu"
|
||||
}
|
||||
|
||||
@@ -831,6 +831,10 @@
|
||||
},
|
||||
"runtime": {
|
||||
"size": "Kode runtime tidak boleh lebih dari 16 karakter"
|
||||
},
|
||||
"transferSceneId": {
|
||||
"notBlank": "ID skenario transfer tidak boleh kosong",
|
||||
"size": "ID skenario transfer tidak boleh melebihi 16 karakter"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,5 +50,18 @@
|
||||
"transferFailed": "Alipay送金エラー: {0}",
|
||||
"transferQueryFailed": "Alipay送金照会エラー: {0}",
|
||||
"transferPayeeTypeInvalid": "Alipay: 未対応の受取人アカウントタイプ: {0}",
|
||||
"transferPayeeAccountRequired": "Alipay: 受取人アカウントは必須です"
|
||||
"transferPayeeAccountRequired": "Alipay: 受取人アカウントは必須です",
|
||||
"transferTitleRequired": "Alipay: 振込タイトルは必須です",
|
||||
"transferSceneNotFound": "Alipay: 振込シーン設定が存在しません",
|
||||
"transferSceneNameInvalid": "Alipay: サポートされていない振込シーン: {0}",
|
||||
"transferSceneNotConfigured": "Alipay: 当該チャネル加盟店に振込シーンが設定されていません。チャネル加盟店詳細の「振込シーン」で設定してください",
|
||||
"transferSceneNotBelong": "Alipay: 振込シーン設定は現在の加盟店に属していません",
|
||||
"transferSceneIdRequired": "Alipay: 振込シーン設定IDは必須です",
|
||||
"transferSceneIdInvalid": "Alipay: 振込シーン設定IDの形式エラー: {0}",
|
||||
"transferSceneEnabledLimit": "Alipay: 有効化する送金シーンは{0}個までです",
|
||||
"transferSceneCannotDisableDefault": "Alipay: デフォルトシーンは無効化できません, 先にデフォルトを他のシーンに切り替えてください",
|
||||
"transferAppNotConfigured": "Alipay: 当該チャネル加盟店に振込アプリがバインドされていません。チャネル加盟店詳細の「振込アプリ」で設定してください",
|
||||
"transferAppNotExist": "Alipay: 振込アプリが存在しません",
|
||||
"transferAppNotBelong": "Alipay: 振込アプリは現在の加盟店に属していません",
|
||||
"channelMerchantMismatch": "Alipay: チャネル加盟店は現在の加盟店に属していません"
|
||||
}
|
||||
|
||||
@@ -831,6 +831,20 @@
|
||||
},
|
||||
"runtime": {
|
||||
"size": "実行形態コードは16文字以内です"
|
||||
},
|
||||
"transferSceneName": {
|
||||
"notBlank": "振込シーン名は空にできません",
|
||||
"size": "振込シーン名は64文字を超えることはできません"
|
||||
},
|
||||
"transferSceneReportInfoType": {
|
||||
"size": "振込シーン報告情報タイプは64文字を超えることはできません"
|
||||
},
|
||||
"transferSceneReportInfoContent": {
|
||||
"size": "振込シーン報告情報内容は200文字を超えることはできません"
|
||||
},
|
||||
"transferSceneId": {
|
||||
"notBlank": "送金シーンIDは必須です",
|
||||
"size": "送金シーンIDは16文字以内で入力してください"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,5 +50,18 @@
|
||||
"transferFailed": "알리페이 송금 실패: {0}",
|
||||
"transferQueryFailed": "알리페이 송금 조회 실패: {0}",
|
||||
"transferPayeeTypeInvalid": "알리페이: 지원하지 않는 수취인 계정 유형: {0}",
|
||||
"transferPayeeAccountRequired": "알리페이: 수취인 계정 필수"
|
||||
"transferPayeeAccountRequired": "알리페이: 수취인 계정 필수",
|
||||
"transferTitleRequired": "Alipay: 이체 제목은 필수입니다",
|
||||
"transferSceneNotFound": "Alipay: 이체 시나리오 설정이 존재하지 않습니다",
|
||||
"transferSceneNameInvalid": "Alipay: 지원되지 않는 이체 시나리오: {0}",
|
||||
"transferSceneNotConfigured": "Alipay: 이 채널 가맹점에 이체 시나리오가 설정되지 않았습니다. 채널 가맹점 상세의 '이체 시나리오'에서 설정해 주세요",
|
||||
"transferSceneNotBelong": "Alipay: 이체 시나리오 설정이 현재 가맹점에 속하지 않습니다",
|
||||
"transferSceneIdRequired": "Alipay: 이체 시나리오 설정 ID는 필수입니다",
|
||||
"transferSceneIdInvalid": "Alipay: 이체 시나리오 설정 ID 형식 오류: {0}",
|
||||
"transferSceneEnabledLimit": "Alipay: 활성화된 이체 장면은 {0}개를 초과할 수 없습니다",
|
||||
"transferSceneCannotDisableDefault": "Alipay: 기본 장면은 비활성화할 수 없습니다, 먼저 기본을 다른 장면으로 전환하세요",
|
||||
"transferAppNotConfigured": "Alipay: 이 채널 가맹점에 이체 앱이 바인딩되지 않았습니다. 채널 가맹점 상세의 '이체 앱'에서 설정해 주세요",
|
||||
"transferAppNotExist": "Alipay: 이체 앱이 존재하지 않습니다",
|
||||
"transferAppNotBelong": "Alipay: 이체 앱이 현재 가맹점에 속하지 않습니다",
|
||||
"channelMerchantMismatch": "Alipay: 채널 가맹점이 현재 가맹점에 속하지 않습니다"
|
||||
}
|
||||
|
||||
@@ -831,6 +831,20 @@
|
||||
},
|
||||
"runtime": {
|
||||
"size": "런타임 코드는 16자를 초과할 수 없습니다"
|
||||
},
|
||||
"transferSceneName": {
|
||||
"notBlank": "이체 시나리오 이름은 비워둘 수 없습니다",
|
||||
"size": "이체 시나리오 이름은 64자를 초과할 수 없습니다"
|
||||
},
|
||||
"transferSceneReportInfoType": {
|
||||
"size": "이체 시나리오 보고 정보 유형은 64자를 초과할 수 없습니다"
|
||||
},
|
||||
"transferSceneReportInfoContent": {
|
||||
"size": "이체 시나리오 보고 정보 내용은 200자를 초과할 수 없습니다"
|
||||
},
|
||||
"transferSceneId": {
|
||||
"notBlank": "송금 시나리오 ID는 필수입니다",
|
||||
"size": "송금 시나리오 ID는 16자 이내여야 합니다"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,5 +50,7 @@
|
||||
"transferFailed": "Pindahan Alipay gagal: {0}",
|
||||
"transferQueryFailed": "Pertanyaan pindahan Alipay gagal: {0}",
|
||||
"transferPayeeTypeInvalid": "Alipay: jenis akaun penerima tidak disokong: {0}",
|
||||
"transferPayeeAccountRequired": "Alipay: akaun penerima wajib"
|
||||
"transferPayeeAccountRequired": "Alipay: akaun penerima wajib",
|
||||
"transferSceneEnabledLimit": "Alipay: Tidak boleh mengaktifkan lebih daripada {0} babak pemindahan",
|
||||
"transferSceneCannotDisableDefault": "Alipay: Babak lalai tidak boleh dilumpuhkan, sila tukar lalai ke babak lain dahulu"
|
||||
}
|
||||
|
||||
@@ -831,6 +831,10 @@
|
||||
},
|
||||
"runtime": {
|
||||
"size": "Kod runtime tidak boleh melebihi 16 aksara"
|
||||
},
|
||||
"transferSceneId": {
|
||||
"notBlank": "ID senario pindahan tidak boleh kosong",
|
||||
"size": "ID senario pindahan tidak boleh melebihi 16 aksara"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,5 +50,7 @@
|
||||
"transferFailed": "การโอนเงิน Alipay ล้มเหลว: {0}",
|
||||
"transferQueryFailed": "การสอบถามการโอนเงิน Alipay ล้มเหลว: {0}",
|
||||
"transferPayeeTypeInvalid": "Alipay: ไม่รองรับประเภทบัญชีผู้รับ: {0}",
|
||||
"transferPayeeAccountRequired": "Alipay: ต้องระบุบัญชีผู้รับ"
|
||||
"transferPayeeAccountRequired": "Alipay: ต้องระบุบัญชีผู้รับ",
|
||||
"transferSceneEnabledLimit": "Alipay: ไม่สามารถเปิดใช้งานเกิน {0} สถานการณ์การโอน",
|
||||
"transferSceneCannotDisableDefault": "Alipay: ไม่สามารถปิดใช้งานสถานการณ์เริ่มต้น โปรดสลับเริ่มต้นไปยังสถานการณ์อื่นก่อน"
|
||||
}
|
||||
|
||||
@@ -831,6 +831,10 @@
|
||||
},
|
||||
"runtime": {
|
||||
"size": "รหัส runtime ต้องไม่เกิน 16 ตัวอักษร"
|
||||
},
|
||||
"transferSceneId": {
|
||||
"notBlank": "รหัสสถานการณ์การโอนต้องไม่ว่าง",
|
||||
"size": "รหัสสถานการณ์การโอนต้องไม่เกิน 16 ตัวอักษร"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,5 +50,7 @@
|
||||
"transferFailed": "Chuyển khoản Alipay thất bại: {0}",
|
||||
"transferQueryFailed": "Truy vấn chuyển khoản Alipay thất bại: {0}",
|
||||
"transferPayeeTypeInvalid": "Alipay: loại tài khoản người nhận không được hỗ trợ: {0}",
|
||||
"transferPayeeAccountRequired": "Alipay: bắt buộc nhập tài khoản người nhận"
|
||||
"transferPayeeAccountRequired": "Alipay: bắt buộc nhập tài khoản người nhận",
|
||||
"transferSceneEnabledLimit": "Alipay: Không thể kích hoạt quá {0} cảnh chuyển khoản",
|
||||
"transferSceneCannotDisableDefault": "Alipay: Cảnh mặc định không thể vô hiệu hóa, vui lòng chuyển mặc định sang cảnh khác trước"
|
||||
}
|
||||
|
||||
@@ -831,6 +831,10 @@
|
||||
},
|
||||
"runtime": {
|
||||
"size": "Mã runtime không được vượt quá 16 ký tự"
|
||||
},
|
||||
"transferSceneId": {
|
||||
"notBlank": "ID kịch bản chuyển khoản không được để trống",
|
||||
"size": "ID kịch bản chuyển khoản không được vượt quá 16 ký tự"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,5 +50,18 @@
|
||||
"transferFailed": "支付宝转账失败: {0}",
|
||||
"transferQueryFailed": "支付宝转账查询失败: {0}",
|
||||
"transferPayeeTypeInvalid": "支付宝: 不支持的收款人账号类型: {0}",
|
||||
"transferPayeeAccountRequired": "支付宝: 收款人账号必填"
|
||||
"transferPayeeAccountRequired": "支付宝: 收款人账号必填",
|
||||
"transferTitleRequired": "支付宝: 转账标题必填",
|
||||
"transferSceneNotFound": "支付宝: 转账场景配置不存在",
|
||||
"transferSceneNameInvalid": "支付宝: 不支持的转账场景: {0}",
|
||||
"transferSceneNotConfigured": "支付宝: 该通道商户尚未配置转账场景, 请先在通道商户详情的「转账场景」中配置",
|
||||
"transferSceneNotBelong": "支付宝: 转账场景配置不属于当前商户",
|
||||
"transferSceneIdRequired": "支付宝: 转账场景配置主键必填",
|
||||
"transferSceneIdInvalid": "支付宝: 转账场景配置ID格式错误: {0}",
|
||||
"transferSceneEnabledLimit": "支付宝: 启用的转账场景不能超过{0}个",
|
||||
"transferSceneCannotDisableDefault": "支付宝: 默认场景不能禁用, 请先切换默认到其他场景",
|
||||
"transferAppNotConfigured": "支付宝: 该通道商户尚未绑定转账应用, 请先在通道商户详情的「转账应用」中配置",
|
||||
"transferAppNotExist": "支付宝: 转账应用不存在",
|
||||
"transferAppNotBelong": "支付宝: 转账应用不属于当前商户",
|
||||
"channelMerchantMismatch": "支付宝: 通道商户不属于当前商户"
|
||||
}
|
||||
|
||||
@@ -835,6 +835,20 @@
|
||||
},
|
||||
"runtime": {
|
||||
"size": "运行形态编码不可超过16位"
|
||||
},
|
||||
"transferSceneName": {
|
||||
"notBlank": "转账场景名称不可为空",
|
||||
"size": "转账场景名称不可超过64位"
|
||||
},
|
||||
"transferSceneReportInfoType": {
|
||||
"size": "转账场景上报信息类型不可超过64位"
|
||||
},
|
||||
"transferSceneReportInfoContent": {
|
||||
"size": "转账场景上报信息内容不可超过200位"
|
||||
},
|
||||
"transferSceneId": {
|
||||
"notBlank": "转账场景ID不可为空",
|
||||
"size": "转账场景ID不可超过16位"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,5 +50,18 @@
|
||||
"transferFailed": "支付寶轉賬失敗: {0}",
|
||||
"transferQueryFailed": "支付寶轉賬查詢失敗: {0}",
|
||||
"transferPayeeTypeInvalid": "支付寶: 不支持的收款人賬號類型: {0}",
|
||||
"transferPayeeAccountRequired": "支付寶: 收款人賬號必填"
|
||||
"transferPayeeAccountRequired": "支付寶: 收款人賬號必填",
|
||||
"transferTitleRequired": "支付寶: 轉賬標題必填",
|
||||
"transferSceneNotFound": "支付寶: 轉賬場景設定不存在",
|
||||
"transferSceneNameInvalid": "支付寶: 不支援的轉賬場景: {0}",
|
||||
"transferSceneNotConfigured": "支付寶: 該通道商戶尚未設定轉賬場景, 請先在通道商戶詳情的「轉賬場景」中設定",
|
||||
"transferSceneNotBelong": "支付寶: 轉賬場景設定不屬於目前商戶",
|
||||
"transferSceneIdRequired": "支付寶: 轉賬場景設定主鍵必填",
|
||||
"transferSceneIdInvalid": "支付寶: 轉賬場景設定ID格式錯誤: {0}",
|
||||
"transferSceneEnabledLimit": "支付寶: 啟用的轉賬場景不能超過{0}個",
|
||||
"transferSceneCannotDisableDefault": "支付寶: 預設場景不能停用, 請先切換預設到其他場景",
|
||||
"transferAppNotConfigured": "支付寶: 該通道商戶尚未綁定轉賬應用, 請先在通道商戶詳情的「轉賬應用」中設定",
|
||||
"transferAppNotExist": "支付寶: 轉賬應用不存在",
|
||||
"transferAppNotBelong": "支付寶: 轉賬應用不屬於目前商戶",
|
||||
"channelMerchantMismatch": "支付寶: 通道商戶不屬於目前商戶"
|
||||
}
|
||||
|
||||
@@ -831,6 +831,20 @@
|
||||
},
|
||||
"runtime": {
|
||||
"size": "運行形態編碼不可超過16位"
|
||||
},
|
||||
"transferSceneName": {
|
||||
"notBlank": "轉賬場景名稱不可為空",
|
||||
"size": "轉賬場景名稱不可超過64位"
|
||||
},
|
||||
"transferSceneReportInfoType": {
|
||||
"size": "轉賬場景上報資訊類型不可超過64位"
|
||||
},
|
||||
"transferSceneReportInfoContent": {
|
||||
"size": "轉賬場景上報資訊內容不可超過200位"
|
||||
},
|
||||
"transferSceneId": {
|
||||
"notBlank": "轉賬場景ID不可為空",
|
||||
"size": "轉賬場景ID不可超過16位"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,5 +50,18 @@
|
||||
"transferFailed": "支付寶轉帳失敗: {0}",
|
||||
"transferQueryFailed": "支付寶轉帳查詢失敗: {0}",
|
||||
"transferPayeeTypeInvalid": "支付寶: 不支持的收款人賬號類型: {0}",
|
||||
"transferPayeeAccountRequired": "支付寶: 收款人賬號必填"
|
||||
"transferPayeeAccountRequired": "支付寶: 收款人賬號必填",
|
||||
"transferTitleRequired": "支付寶: 轉帳標題必填",
|
||||
"transferSceneNotFound": "支付寶: 轉帳場景設定不存在",
|
||||
"transferSceneNameInvalid": "支付寶: 不支援的轉帳場景: {0}",
|
||||
"transferSceneNotConfigured": "支付寶: 該通道商戶尚未設定轉帳場景, 請先在通道商戶詳情的「轉帳場景」中設定",
|
||||
"transferSceneNotBelong": "支付寶: 轉帳場景設定不屬於當前商戶",
|
||||
"transferSceneIdRequired": "支付寶: 轉帳場景設定主鍵必填",
|
||||
"transferSceneIdInvalid": "支付寶: 轉帳場景設定ID格式錯誤: {0}",
|
||||
"transferSceneEnabledLimit": "支付寶: 啟用的轉賬場景不能超過{0}個",
|
||||
"transferSceneCannotDisableDefault": "支付寶: 預設場景不能停用, 請先切換預設到其他場景",
|
||||
"transferAppNotConfigured": "支付寶: 該通道商戶尚未綁定轉帳應用, 請先在通道商戶詳情的「轉帳應用」中設定",
|
||||
"transferAppNotExist": "支付寶: 轉帳應用不存在",
|
||||
"transferAppNotBelong": "支付寶: 轉帳應用不屬於當前商戶",
|
||||
"channelMerchantMismatch": "支付寶: 通道商戶不屬於當前商戶"
|
||||
}
|
||||
|
||||
@@ -831,6 +831,20 @@
|
||||
},
|
||||
"runtime": {
|
||||
"size": "執行形態編碼不可超過16位"
|
||||
},
|
||||
"transferSceneName": {
|
||||
"notBlank": "轉帳場景名稱不可為空",
|
||||
"size": "轉帳場景名稱不可超過64位"
|
||||
},
|
||||
"transferSceneReportInfoType": {
|
||||
"size": "轉帳場景上報資訊類型不可超過64位"
|
||||
},
|
||||
"transferSceneReportInfoContent": {
|
||||
"size": "轉帳場景上報資訊內容不可超過200位"
|
||||
},
|
||||
"transferSceneId": {
|
||||
"notBlank": "轉帳場景ID不可為空",
|
||||
"size": "轉帳場景ID不可超過16位"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user