mirror of
https://gitee.com/dromara/dax-pay
synced 2026-08-11 23:25:33 +08:00
feat(transfer): 抖音转账场景主数据枚举(1001-1007) + 转账发起应用绑定(仅网站应用), 策略按转账配置解析发起应用(10语)
This commit is contained in:
@@ -810,6 +810,46 @@ COMMENT ON COLUMN "public"."dy_platform_app_capability"."dy_platform_app_id" IS
|
||||
COMMENT ON COLUMN "public"."dy_platform_app_capability"."product" IS '产品编码';
|
||||
COMMENT ON TABLE "public"."dy_platform_app_capability" IS '平台抖音应用默认能力绑定(全局一能力一应用)';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for douyin_transfer_config
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS "public"."douyin_transfer_config";
|
||||
CREATE TABLE "public"."douyin_transfer_config" (
|
||||
"id" int8 NOT NULL,
|
||||
"creator" int8,
|
||||
"create_time" timestamptz(6),
|
||||
"last_modifier" int8,
|
||||
"last_modified_time" timestamptz(6),
|
||||
"version" int4 NOT NULL DEFAULT 0,
|
||||
"deleted" bool NOT NULL DEFAULT false,
|
||||
"mch_no" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
|
||||
"channel_mch_no" varchar(64) COLLATE "pg_catalog"."default" NOT NULL,
|
||||
"transfer_app_ref_id" int8
|
||||
)
|
||||
;
|
||||
COMMENT ON COLUMN "public"."douyin_transfer_config"."id" IS '主键';
|
||||
COMMENT ON COLUMN "public"."douyin_transfer_config"."creator" IS '创建者ID';
|
||||
COMMENT ON COLUMN "public"."douyin_transfer_config"."create_time" IS '创建时间';
|
||||
COMMENT ON COLUMN "public"."douyin_transfer_config"."last_modifier" IS '最后修改ID';
|
||||
COMMENT ON COLUMN "public"."douyin_transfer_config"."last_modified_time" IS '最后修改时间';
|
||||
COMMENT ON COLUMN "public"."douyin_transfer_config"."version" IS '版本号';
|
||||
COMMENT ON COLUMN "public"."douyin_transfer_config"."deleted" IS '删除标志';
|
||||
COMMENT ON COLUMN "public"."douyin_transfer_config"."mch_no" IS '商户号';
|
||||
COMMENT ON COLUMN "public"."douyin_transfer_config"."channel_mch_no" IS '通道商户号';
|
||||
COMMENT ON COLUMN "public"."douyin_transfer_config"."transfer_app_ref_id" IS '转账发起应用引用(dy_mch_app 主键, 须为网站应用 web_app, 支持手机H5获取OpenId)';
|
||||
COMMENT ON TABLE "public"."douyin_transfer_config" IS '抖音转账配置(一对一, 指定转账发起应用, 决定转出主体与收款人openId来源)';
|
||||
|
||||
-- 主键约束
|
||||
ALTER TABLE "public"."douyin_transfer_config" ADD CONSTRAINT "pk_douyin_transfer_config" PRIMARY KEY ("id");
|
||||
|
||||
-- 通道商户查询索引
|
||||
CREATE INDEX "idx_douyin_transfer_config_mch" ON "public"."douyin_transfer_config" USING btree ("channel_mch_no", "deleted");
|
||||
COMMENT ON INDEX "public"."idx_douyin_transfer_config_mch" IS '按通道商户号查询转账配置';
|
||||
|
||||
-- 通道商户唯一索引(一个通道商户一条转账配置)
|
||||
CREATE UNIQUE INDEX "uk_douyin_transfer_config_mch" ON "public"."douyin_transfer_config" ("channel_mch_no") WHERE deleted = false;
|
||||
COMMENT ON INDEX "public"."uk_douyin_transfer_config_mch" IS '同一通道商户仅一条转账配置(部分唯一索引)';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for fuyou_isv_channel_merchant
|
||||
-- ----------------------------
|
||||
|
||||
@@ -1,35 +1,43 @@
|
||||
-- ============================================================
|
||||
-- 支付宝转账场景预置数据(补跑段)
|
||||
-- 表结构与索引(先删后增)已执行成功, 本段仅执行预置数据初始化:
|
||||
-- 为现有直连通道商户预置8个转账场景行(enabled=false, is_default=false)
|
||||
-- 场景为支付宝协议固定中文名称, 重复执行时靠场景唯一索引(部分索引)幂等跳过
|
||||
-- 抖音转账配置表(转账发起应用绑定, 发起转账时决定转出主体与收款人openId来源)
|
||||
-- 对齐微信 wechat_transfer_config 范式; 转账场景为主数据枚举无需落库, 本表仅存发起应用
|
||||
-- ============================================================
|
||||
DROP TABLE IF EXISTS "public"."douyin_transfer_config";
|
||||
|
||||
-- ID 用时间戳毫秒*1000000 + 递增序号生成, 与应用运行时 Snowflake ID 范围相近且不冲突
|
||||
DO $$
|
||||
DECLARE
|
||||
base_id bigint;
|
||||
seq bigint := 0;
|
||||
mch_record RECORD;
|
||||
scene_name text;
|
||||
BEGIN
|
||||
base_id := (extract(epoch from now()) * 1000)::bigint * 1000000;
|
||||
FOR mch_record IN
|
||||
SELECT mch_no, channel_mch_no
|
||||
FROM "public"."alipay_direct_channel_merchant"
|
||||
WHERE deleted = false
|
||||
LOOP
|
||||
FOREACH scene_name IN ARRAY ARRAY['现金营销', '企业退款', '佣金报酬', '业务结算',
|
||||
'二手回收', '公益补助', '行政补贴和退款', '保险理赔']
|
||||
LOOP
|
||||
INSERT INTO "public"."alipay_transfer_scene_config"
|
||||
("id", "mch_no", "channel_mch_no", "scene_name", "enabled", "is_default",
|
||||
"create_time", "last_modified_time", "version", "deleted")
|
||||
VALUES
|
||||
(base_id + seq, mch_record.mch_no, mch_record.channel_mch_no, scene_name, false, false,
|
||||
now(), now(), 0, false)
|
||||
ON CONFLICT DO NOTHING;
|
||||
seq := seq + 1;
|
||||
END LOOP;
|
||||
END LOOP;
|
||||
END $$;
|
||||
CREATE TABLE "public"."douyin_transfer_config" (
|
||||
"id" int8 NOT NULL,
|
||||
"creator" int8,
|
||||
"create_time" timestamptz(6),
|
||||
"last_modifier" int8,
|
||||
"last_modified_time" timestamptz(6),
|
||||
"version" int4 NOT NULL DEFAULT 0,
|
||||
"deleted" bool NOT NULL DEFAULT false,
|
||||
"mch_no" varchar(32) COLLATE "pg_catalog"."default" NOT NULL,
|
||||
"channel_mch_no" varchar(64) COLLATE "pg_catalog"."default" NOT NULL,
|
||||
"transfer_app_ref_id" int8
|
||||
);
|
||||
COMMENT ON COLUMN "public"."douyin_transfer_config"."id" IS '主键';
|
||||
COMMENT ON COLUMN "public"."douyin_transfer_config"."creator" IS '创建者ID';
|
||||
COMMENT ON COLUMN "public"."douyin_transfer_config"."create_time" IS '创建时间';
|
||||
COMMENT ON COLUMN "public"."douyin_transfer_config"."last_modifier" IS '最后修改ID';
|
||||
COMMENT ON COLUMN "public"."douyin_transfer_config"."last_modified_time" IS '最后修改时间';
|
||||
COMMENT ON COLUMN "public"."douyin_transfer_config"."version" IS '版本号';
|
||||
COMMENT ON COLUMN "public"."douyin_transfer_config"."deleted" IS '删除标志';
|
||||
COMMENT ON COLUMN "public"."douyin_transfer_config"."mch_no" IS '商户号';
|
||||
COMMENT ON COLUMN "public"."douyin_transfer_config"."channel_mch_no" IS '通道商户号';
|
||||
COMMENT ON COLUMN "public"."douyin_transfer_config"."transfer_app_ref_id" IS '转账发起应用引用(dy_mch_app 主键, 须为网站应用 web_app, 支持手机H5获取OpenId)';
|
||||
COMMENT ON TABLE "public"."douyin_transfer_config" IS '抖音转账配置(一对一, 指定转账发起应用, 决定转出主体与收款人openId来源)';
|
||||
|
||||
-- 主键约束 先删后增
|
||||
ALTER TABLE "public"."douyin_transfer_config" DROP CONSTRAINT IF EXISTS "pk_douyin_transfer_config";
|
||||
ALTER TABLE "public"."douyin_transfer_config" ADD CONSTRAINT "pk_douyin_transfer_config" PRIMARY KEY ("id");
|
||||
|
||||
-- 通道商户查询索引 先删后增
|
||||
DROP INDEX IF EXISTS "public"."idx_douyin_transfer_config_mch";
|
||||
CREATE INDEX "idx_douyin_transfer_config_mch" ON "public"."douyin_transfer_config" USING btree ("channel_mch_no", "deleted");
|
||||
COMMENT ON INDEX "public"."idx_douyin_transfer_config_mch" IS '按通道商户号查询转账配置';
|
||||
|
||||
-- 通道商户唯一索引(一个通道商户一条转账配置) 先删后增
|
||||
DROP INDEX IF EXISTS "public"."uk_douyin_transfer_config_mch";
|
||||
CREATE UNIQUE INDEX "uk_douyin_transfer_config_mch" ON "public"."douyin_transfer_config" ("channel_mch_no") WHERE deleted = false;
|
||||
COMMENT ON INDEX "public"."uk_douyin_transfer_config_mch" IS '同一通道商户仅一条转账配置(部分唯一索引)';
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package cn.daxpay.open.channel.douyin.client.req;
|
||||
|
||||
import cn.daxpay.open.channel.douyin.client.credential.DouyinSdkCredential;
|
||||
import cn.daxpay.open.payment.trade.transfer.param.TransferReportInfo;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/// # 抖音通道转账请求(发起/同步共用)
|
||||
///
|
||||
/// 与子应用 dax-pay-channel-one 的 `DouyinTransferReq` 镜像, 字段对齐。
|
||||
@@ -21,7 +24,7 @@ public class DouyinTransferReq {
|
||||
/// 收款人 openid
|
||||
private String openid;
|
||||
|
||||
/// 转账场景ID(transfer_scene_id, 来自通道商户配置)
|
||||
/// 转账场景ID(transfer_scene_id, 主数据枚举1001-1007)
|
||||
private String scene;
|
||||
|
||||
/// 收款人姓名(金额>=2000元必填, 子应用加密上送)
|
||||
@@ -30,9 +33,12 @@ public class DouyinTransferReq {
|
||||
/// 转账备注(对应 transfer_remark)
|
||||
private String remark;
|
||||
|
||||
/// 收款感知文案(对应 user_recv_perception)
|
||||
/// 收款感知文案(对应 user_recv_perception, 按场景枚举选项)
|
||||
private String perception;
|
||||
|
||||
/// 转账场景报备信息(对应 transfer_scene_report_infos, 按场景要求填写)
|
||||
private List<TransferReportInfo> reportInfos;
|
||||
|
||||
/// 异步通知地址(抖音→平台)
|
||||
private String notifyUrl;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import cn.daxpay.open.channel.douyin.param.direct.DouyinDirectChannelMerchantCre
|
||||
import cn.daxpay.open.channel.douyin.param.direct.DouyinDirectKeyConfigParam;
|
||||
import cn.daxpay.open.channel.douyin.result.direct.DouyinDirectChannelMerchantResult;
|
||||
import cn.daxpay.open.channel.douyin.result.direct.DouyinDirectKeyConfigResult;
|
||||
import cn.daxpay.open.channel.douyin.result.direct.DouyinTransferSceneOptionResult;
|
||||
import cn.daxpay.open.channel.douyin.service.direct.DouyinDirectChannelMerchantService;
|
||||
import cn.daxpay.open.channel.douyin.service.direct.DouyinDirectKeyConfigService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
@@ -21,6 +22,8 @@ import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/// # 抖音直连通道商户管理
|
||||
///
|
||||
@PermCode(menuCode = PermCodes.Channel.Merchant.MENU)
|
||||
@@ -69,4 +72,11 @@ public class DouyinDirectChannelMerchantController {
|
||||
douyinDirectKeyConfigService.save(param);
|
||||
return Res.ok();
|
||||
}
|
||||
|
||||
@PermCode(code = PermCodes.Action.VIEW)
|
||||
@Operation(summary = "查询抖音转账场景选项列表(主数据枚举)")
|
||||
@GetMapping("/scene-options")
|
||||
public Result<List<DouyinTransferSceneOptionResult>> sceneOptions() {
|
||||
return Res.ok(douyinDirectChannelMerchantService.findSceneOptions());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package cn.daxpay.open.channel.douyin.controller.direct;
|
||||
|
||||
import cn.daxpay.open.channel.douyin.param.direct.DouyinTransferConfigParam;
|
||||
import cn.daxpay.open.channel.douyin.result.direct.DouyinTransferConfigResult;
|
||||
import cn.daxpay.open.channel.douyin.service.direct.DouyinTransferConfigService;
|
||||
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/douyin/transfer-config")
|
||||
@RequiredArgsConstructor
|
||||
public class DouyinTransferConfigController {
|
||||
|
||||
private final DouyinTransferConfigService douyinTransferConfigService;
|
||||
|
||||
@PermCode(code = PermCodes.Action.VIEW)
|
||||
@Operation(summary = "查询通道商户的转账配置")
|
||||
@GetMapping("/find-by-channel-mch-no")
|
||||
public Result<DouyinTransferConfigResult> findByChannelMchNo(
|
||||
@NotBlank(message = "{validation.field.mchNo.notBlank}") String mchNo,
|
||||
@NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") String channelMchNo) {
|
||||
return Res.ok(douyinTransferConfigService.findByChannelMchNo(mchNo, channelMchNo));
|
||||
}
|
||||
|
||||
@PermCode(code = PermCodes.Action.MANAGE)
|
||||
@Operation(summary = "保存或更新转账配置(一对一)")
|
||||
@PostMapping("/save")
|
||||
public Result<Void> save(@RequestBody @Validated DouyinTransferConfigParam param) {
|
||||
douyinTransferConfigService.saveOrUpdate(param);
|
||||
return Res.ok();
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package cn.daxpay.open.channel.douyin.controller.merchant;
|
||||
import cn.daxpay.open.channel.douyin.param.direct.DouyinDirectKeyConfigParam;
|
||||
import cn.daxpay.open.channel.douyin.result.direct.DouyinDirectChannelMerchantResult;
|
||||
import cn.daxpay.open.channel.douyin.result.direct.DouyinDirectKeyConfigResult;
|
||||
import cn.daxpay.open.channel.douyin.result.direct.DouyinTransferSceneOptionResult;
|
||||
import cn.daxpay.open.channel.douyin.service.direct.DouyinDirectChannelMerchantService;
|
||||
import cn.daxpay.open.channel.douyin.service.direct.DouyinDirectKeyConfigService;
|
||||
import cn.daxpay.open.payment.common.context.PaymentContext;
|
||||
@@ -27,6 +28,7 @@ import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/// # 抖音直连通道商户配置(商户端)
|
||||
@@ -100,4 +102,10 @@ public class MchDouyinDirectChannelMerchantController {
|
||||
douyinDirectKeyConfigService.save(param);
|
||||
return Res.ok();
|
||||
}
|
||||
|
||||
@Operation(summary = "查询抖音转账场景选项列表(主数据枚举)")
|
||||
@GetMapping("/scene-options")
|
||||
public Result<List<DouyinTransferSceneOptionResult>> sceneOptions() {
|
||||
return Res.ok(douyinDirectChannelMerchantService.findSceneOptions());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package cn.daxpay.open.channel.douyin.controller.merchant;
|
||||
|
||||
import cn.daxpay.open.channel.douyin.param.direct.DouyinTransferConfigParam;
|
||||
import cn.daxpay.open.channel.douyin.result.direct.DouyinTransferConfigResult;
|
||||
import cn.daxpay.open.channel.douyin.service.direct.DouyinTransferConfigService;
|
||||
import cn.daxpay.open.payment.common.context.PaymentContext;
|
||||
import cn.daxpay.open.payment.merchant.dao.channel.ChannelMerchantManager;
|
||||
import cn.daxpay.open.payment.merchant.entity.channel.ChannelMerchant;
|
||||
import cn.daxpay.open.platform.core.annotation.PermCode;
|
||||
import cn.daxpay.open.platform.core.code.CommonCode;
|
||||
import cn.daxpay.open.platform.core.code.PermCodes;
|
||||
import cn.daxpay.open.platform.core.exception.BizInfoException;
|
||||
import cn.daxpay.open.platform.core.exception.config.ConfigErrorException;
|
||||
import cn.daxpay.open.platform.core.rest.Res;
|
||||
import cn.daxpay.open.platform.core.rest.result.Result;
|
||||
import cn.daxpay.open.platform.core.util.ValidationUtil;
|
||||
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.RestController;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/// # 抖音转账配置管理(商户端)
|
||||
///
|
||||
/// 对照运营端 [cn.daxpay.open.channel.douyin.controller.direct.DouyinTransferConfigController],
|
||||
/// 路径前缀 `/mch/douyin/transfer-config`。
|
||||
/// 商户号一律取自 [PaymentContext],防越权。
|
||||
@PermCode(menuCode = PermCodes.Channel.Merchant.MENU)
|
||||
@Validated
|
||||
@Tag(name = "抖音转账配置管理(商户端)")
|
||||
@RestController
|
||||
@RequestMapping("/mch/douyin/transfer-config")
|
||||
@RequiredArgsConstructor
|
||||
public class MchDouyinTransferConfigController {
|
||||
|
||||
private final DouyinTransferConfigService douyinTransferConfigService;
|
||||
private final ChannelMerchantManager channelMerchantManager;
|
||||
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;
|
||||
}
|
||||
|
||||
/// 校验通道商户属于当前商户(防越权)
|
||||
private void assertChannelMchOwned(String channelMchNo) {
|
||||
ChannelMerchant channelMerchant = channelMerchantManager.findByChannelMchNo(channelMchNo)
|
||||
// 抖音: 通道商户不存在或商户号不匹配
|
||||
.orElseThrow(() -> new ConfigErrorException("error.payment.douyin.channelMerchantMismatch"));
|
||||
if (!Objects.equals(channelMerchant.getMchNo(), this.requireMchNo())) {
|
||||
// 抖音: 通道商户与商户号不匹配
|
||||
throw new ConfigErrorException("error.payment.douyin.channelMerchantMismatch");
|
||||
}
|
||||
}
|
||||
|
||||
@PermCode(code = PermCodes.Action.VIEW)
|
||||
@Operation(summary = "查询通道商户的转账配置")
|
||||
@GetMapping("/find-by-channel-mch-no")
|
||||
public Result<DouyinTransferConfigResult> findByChannelMchNo(
|
||||
@NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") String channelMchNo) {
|
||||
this.assertChannelMchOwned(channelMchNo);
|
||||
return Res.ok(douyinTransferConfigService.findByChannelMchNo(this.requireMchNo(), channelMchNo));
|
||||
}
|
||||
|
||||
@PermCode(code = PermCodes.Action.MANAGE)
|
||||
@Operation(summary = "保存或更新转账配置(一对一)")
|
||||
@PostMapping("/save")
|
||||
public Result<Void> save(@RequestBody DouyinTransferConfigParam param) {
|
||||
this.assertChannelMchOwned(param.getChannelMchNo());
|
||||
// 强制当前商户号,忽略客户端传入(防越权)
|
||||
param.setMchNo(this.requireMchNo());
|
||||
ValidationUtil.validateParam(param);
|
||||
douyinTransferConfigService.saveOrUpdate(param);
|
||||
return Res.ok();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package cn.daxpay.open.channel.douyin.convert.direct;
|
||||
|
||||
import cn.daxpay.open.channel.douyin.entity.direct.DouyinTransferConfig;
|
||||
import cn.daxpay.open.channel.douyin.param.direct.DouyinTransferConfigParam;
|
||||
import cn.daxpay.open.channel.douyin.result.direct.DouyinTransferConfigResult;
|
||||
import org.mapstruct.BeanMapping;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.NullValuePropertyMappingStrategy;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/// # 抖音转账配置转换
|
||||
///
|
||||
/// MapStruct 转换器, 负责转账配置在实体、参数和返回结果之间的转换, 更新时空值不覆盖。
|
||||
/// 冗余展示字段(发起应用名/douyinAppId/应用类型)由 Service 填充, 不经 Convert。
|
||||
///
|
||||
@Mapper
|
||||
public interface DouyinTransferConfigConvert {
|
||||
|
||||
DouyinTransferConfigConvert CONVERT = Mappers.getMapper(DouyinTransferConfigConvert.class);
|
||||
|
||||
/// 转换为返回对象
|
||||
DouyinTransferConfigResult toResult(DouyinTransferConfig entity);
|
||||
|
||||
/// 转换为实体
|
||||
DouyinTransferConfig toEntity(DouyinTransferConfigParam param);
|
||||
|
||||
/// 更新源数据到实体(空值不覆盖)
|
||||
@BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
|
||||
void copy(DouyinTransferConfigParam param, @MappingTarget DouyinTransferConfig entity);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package cn.daxpay.open.channel.douyin.dao.direct;
|
||||
|
||||
import cn.daxpay.open.channel.douyin.entity.direct.DouyinTransferConfig;
|
||||
import cn.daxpay.open.platform.common.mybatisplus.impl.BaseManager;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/// # 抖音转账配置
|
||||
///
|
||||
/// 一个通道商户一条转账配置(一对一), 提供按通道商户号查询/删除。
|
||||
///
|
||||
@Repository
|
||||
public class DouyinTransferConfigManager extends BaseManager<DouyinTransferConfigMapper, DouyinTransferConfig> {
|
||||
|
||||
/// 按通道商户号查询转账配置(一对一)
|
||||
public Optional<DouyinTransferConfig> findByChannelMchNo(String channelMchNo) {
|
||||
return lambdaQuery()
|
||||
.eq(DouyinTransferConfig::getChannelMchNo, channelMchNo)
|
||||
.oneOpt();
|
||||
}
|
||||
|
||||
/// 按通道商户号删除转账配置(逻辑删除, 通道商户删除时级联清理)
|
||||
public void deleteByChannelMchNo(String channelMchNo) {
|
||||
lambdaUpdate()
|
||||
.eq(DouyinTransferConfig::getChannelMchNo, channelMchNo)
|
||||
.remove();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package cn.daxpay.open.channel.douyin.dao.direct;
|
||||
|
||||
import cn.daxpay.open.channel.douyin.entity.direct.DouyinTransferConfig;
|
||||
import com.github.yulichang.base.MPJBaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/// # 抖音转账配置
|
||||
///
|
||||
/// 抖音转账配置 MyBatis-Plus Mapper, 继承 MPJBaseMapper 支持多表联查。
|
||||
///
|
||||
@Mapper
|
||||
public interface DouyinTransferConfigMapper extends MPJBaseMapper<DouyinTransferConfig> {
|
||||
}
|
||||
@@ -29,9 +29,6 @@ public class DouyinDirectChannelMerchant extends MchBaseEntity implements ToResu
|
||||
/// 抖音商户号(MCHID)
|
||||
private String dyMchId;
|
||||
|
||||
/// 转账场景ID(商家转账 transfer_scene_id, 未配置时发起转账报错)
|
||||
private String transferScene;
|
||||
|
||||
/// 转换
|
||||
@Override
|
||||
public DouyinDirectChannelMerchantResult toResult() {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package cn.daxpay.open.channel.douyin.entity.direct;
|
||||
|
||||
import cn.daxpay.open.channel.douyin.convert.direct.DouyinTransferConfigConvert;
|
||||
import cn.daxpay.open.channel.douyin.result.direct.DouyinTransferConfigResult;
|
||||
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.douyin.strategy.transfer.DouyinTransferStrategy]
|
||||
/// 读取本配置按 [transferAppRefId] 解析发起应用(网站应用)的 douyinAppId, 决定转出主体与
|
||||
/// 收款人 openId 的来源(H5 授权由网站应用承接)。
|
||||
///
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@TableName("douyin_transfer_config")
|
||||
public class DouyinTransferConfig extends MchBaseEntity implements ToResult<DouyinTransferConfigResult> {
|
||||
|
||||
/// 通道商户号
|
||||
@TableField(updateStrategy = FieldStrategy.NEVER)
|
||||
private String channelMchNo;
|
||||
|
||||
/// 转账发起应用引用(指向 dy_mch_app 主键, 须为网站应用 web_app, 决定转出 appid 与 openid 来源)
|
||||
private Long transferAppRefId;
|
||||
|
||||
/// 转换
|
||||
@Override
|
||||
public DouyinTransferConfigResult toResult() {
|
||||
return DouyinTransferConfigConvert.CONVERT.toResult(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package cn.daxpay.open.channel.douyin.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/// # 抖音转账场景枚举
|
||||
///
|
||||
/// 对应抖音商家转账(/v1/fund_trade/mch-transfer/transfer-bills)的转账场景,
|
||||
/// 场景ID 为固定枚举值(1001-1007),商户在抖音商户平台「产品中心-商家转账到抖音零钱」开通。
|
||||
/// 每个场景要求不同的报备字段([reportInfoTypes]), 字段值为抖音协议固定的中文 [infoType], 不可更改。
|
||||
///
|
||||
/// 报备字段内容(infoContent)由商户在发起转账时填写。
|
||||
/// [reportInfoDescriptions] 与 [reportInfoTypes] 一一平行, 描述每个字段的含义和抖音文档示例。
|
||||
/// [userRecvPerceptionOptions] 为收款人在抖音中看到的感知文案可选值, 不传时抖音按场景取默认(第一个)。
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum DouyinTransferSceneEnum {
|
||||
|
||||
/// 现金营销
|
||||
CASH_MARKETING("1001", "现金营销",
|
||||
List.of("活动名称", "奖励说明"),
|
||||
List.of("商户自定义内容,如「新会员有礼」", "商户自定义内容,如「注册会员抽奖一等奖」"),
|
||||
List.of("活动奖励", "现金奖励")),
|
||||
|
||||
/// 企业赔付
|
||||
ENTERPRISE_COMPENSATION("1002", "企业赔付",
|
||||
List.of("赔付原因"),
|
||||
List.of("商户自定义内容,如「商品质量问题退款」"),
|
||||
List.of("退款", "商家赔付")),
|
||||
|
||||
/// 佣金报酬
|
||||
COMMISSION_REWARD("1003", "佣金报酬",
|
||||
List.of("岗位类型", "报酬说明"),
|
||||
List.of("商户自定义内容,如「外卖员」", "商户自定义内容,如「7月份配送费」"),
|
||||
List.of("劳务报酬", "报销款", "企业补贴", "开工利是")),
|
||||
|
||||
/// 采购货款
|
||||
PURCHASE_PAYMENT("1004", "采购货款",
|
||||
List.of("采购商品名称"),
|
||||
List.of("商户自定义内容,如「戴尔笔记本电脑」"),
|
||||
List.of("货款")),
|
||||
|
||||
/// 二手回收
|
||||
SECOND_HAND_RECYCLING("1005", "二手回收",
|
||||
List.of("回收商品名称"),
|
||||
List.of("商户自定义内容,如「塑料瓶」"),
|
||||
List.of("二手回收货款")),
|
||||
|
||||
/// 公益补助
|
||||
PUBLIC_WELFARE_SUBSIDY("1006", "公益补助",
|
||||
List.of("公益活动名称", "公益活动备案编号"),
|
||||
List.of("请填写在民政部的备案名称", "请填写在民政部的备案编号"),
|
||||
List.of("公益补助金")),
|
||||
|
||||
/// 行政补贴
|
||||
ADMINISTRATIVE_SUBSIDY("1007", "行政补贴",
|
||||
List.of("补贴类型"),
|
||||
List.of("商户自定义内容,如「购车补贴」"),
|
||||
List.of("行政补贴", "行政奖励"));
|
||||
|
||||
/// 转账场景ID
|
||||
private final String code;
|
||||
|
||||
/// 场景名称
|
||||
private final String name;
|
||||
|
||||
/// 报备字段定义(抖音协议固定中文 infoType, 顺序即报备明细下标)
|
||||
private final List<String> reportInfoTypes;
|
||||
|
||||
/// 报备字段说明(与 reportInfoTypes 平行, 描述字段含义和抖音文档示例)
|
||||
private final List<String> reportInfoDescriptions;
|
||||
|
||||
/// 用户收款感知可选值(收款人在抖音中看到的文案, 不传时取第一个为默认)
|
||||
private final List<String> userRecvPerceptionOptions;
|
||||
|
||||
/// 根据场景ID 查找枚举
|
||||
public static DouyinTransferSceneEnum findByCode(String code) {
|
||||
for (DouyinTransferSceneEnum scene : values()) {
|
||||
if (scene.code.equals(code)) {
|
||||
return scene;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -31,9 +31,5 @@ public class DouyinDirectChannelMerchantCreateParam {
|
||||
@Schema(description = "抖音商户号(MCHID)")
|
||||
@NotBlank(message = "{validation.field.dyMchId.notBlank}")
|
||||
private String dyMchId;
|
||||
|
||||
/// 转账场景ID(商家转账, 抖音转账时必填)
|
||||
@Schema(description = "转账场景ID")
|
||||
private String transferScene;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.daxpay.open.channel.douyin.param.direct;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/// # 抖音转账配置保存参数
|
||||
///
|
||||
/// 一对一 upsert: 存在则更新, 不存在则新增。`transferAppRefId` 允许为空(支持清空),
|
||||
/// 但发起转账时必须已配置, 由转账策略校验。
|
||||
///
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "抖音转账配置保存参数")
|
||||
public class DouyinTransferConfigParam {
|
||||
|
||||
@NotBlank(message = "{validation.field.mchNo.notBlank}")
|
||||
@Schema(description = "商户号")
|
||||
private String mchNo;
|
||||
|
||||
@NotBlank(message = "{validation.field.channelMerchantNo.notBlank}")
|
||||
@Schema(description = "通道商户号")
|
||||
private String channelMchNo;
|
||||
|
||||
@Schema(description = "转账发起应用引用(指向 dy_mch_app 主键, 须为网站应用 web_app)")
|
||||
private Long transferAppRefId;
|
||||
}
|
||||
@@ -22,8 +22,5 @@ public class DouyinDirectChannelMerchantResult extends MchBaseResult {
|
||||
|
||||
@Schema(description = "抖音商户号(MCHID)")
|
||||
private String dyMchId;
|
||||
|
||||
@Schema(description = "转账场景ID(商家转账)")
|
||||
private String transferScene;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package cn.daxpay.open.channel.douyin.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;
|
||||
|
||||
/// # 抖音转账配置
|
||||
///
|
||||
/// 转账配置返回结果对象。冗余展示字段(发起应用名/douyinAppId/应用类型)由
|
||||
/// [cn.daxpay.open.channel.douyin.service.direct.DouyinTransferConfigService] 填充,
|
||||
/// 不经 MapStruct 自动映射。
|
||||
///
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "抖音转账配置")
|
||||
public class DouyinTransferConfigResult 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(真实抖音应用 AppId)")
|
||||
private String douyinAppId;
|
||||
|
||||
@Schema(description = "发起应用类型(须为 web_app 网站应用)")
|
||||
private String appType;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package cn.daxpay.open.channel.douyin.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] 平行, 描述每个字段含义和抖音文档示例。
|
||||
/// [userRecvPerceptionOptions] 为收款人在抖音中看到的感知文案可选值。
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Schema(title = "抖音转账场景选项")
|
||||
public class DouyinTransferSceneOptionResult {
|
||||
|
||||
@Schema(description = "转账场景ID")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "场景名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "报备字段定义(抖音协议固定中文 infoType)")
|
||||
private List<String> reportInfoTypes;
|
||||
|
||||
@Schema(description = "报备字段说明(与 reportInfoTypes 平行, 含抖音文档示例)")
|
||||
private List<String> reportInfoDescriptions;
|
||||
|
||||
@Schema(description = "用户收款感知可选值(收款人在抖音中看到的文案)")
|
||||
private List<String> userRecvPerceptionOptions;
|
||||
}
|
||||
@@ -2,8 +2,10 @@ package cn.daxpay.open.channel.douyin.service.direct;
|
||||
|
||||
import cn.daxpay.open.channel.douyin.dao.direct.DouyinDirectChannelMerchantManager;
|
||||
import cn.daxpay.open.channel.douyin.entity.direct.DouyinDirectChannelMerchant;
|
||||
import cn.daxpay.open.channel.douyin.enums.DouyinTransferSceneEnum;
|
||||
import cn.daxpay.open.channel.douyin.param.direct.DouyinDirectChannelMerchantCreateParam;
|
||||
import cn.daxpay.open.channel.douyin.result.direct.DouyinDirectChannelMerchantResult;
|
||||
import cn.daxpay.open.channel.douyin.result.direct.DouyinTransferSceneOptionResult;
|
||||
import cn.daxpay.open.payment.merchant.dao.channel.ChannelMerchantManager;
|
||||
import cn.daxpay.open.payment.masterdata.dao.product.PayProductConfigManager;
|
||||
import cn.daxpay.open.payment.merchant.entity.channel.ChannelMerchant;
|
||||
@@ -17,6 +19,9 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/// # 抖音直连通道商户管理
|
||||
///
|
||||
/// 一个抖音商户号(dyMchId)对应一个 channelMchNo, 商户的多个应用共享此绑定。
|
||||
@@ -61,7 +66,6 @@ public class DouyinDirectChannelMerchantService {
|
||||
entity.setChannelMchNo(channelMchNo);
|
||||
entity.setProduct(param.getProduct());
|
||||
entity.setDyMchId(param.getDyMchId());
|
||||
entity.setTransferScene(param.getTransferScene());
|
||||
douyinDirectChannelMerchantManager.save(entity);
|
||||
}
|
||||
|
||||
@@ -74,5 +78,17 @@ public class DouyinDirectChannelMerchantService {
|
||||
// 通道: 通道商户配置不存在
|
||||
.orElseThrow(() -> new DataNotExistException("error.payment.channel.channelMerchantNotExist"));
|
||||
}
|
||||
|
||||
/// 查询抖音转账场景选项列表(主数据枚举投影, 不查库, 供前端下拉与报备字段动态渲染)
|
||||
public List<DouyinTransferSceneOptionResult> findSceneOptions() {
|
||||
return Arrays.stream(DouyinTransferSceneEnum.values())
|
||||
.map(scene -> new DouyinTransferSceneOptionResult()
|
||||
.setCode(scene.getCode())
|
||||
.setName(scene.getName())
|
||||
.setReportInfoTypes(scene.getReportInfoTypes())
|
||||
.setReportInfoDescriptions(scene.getReportInfoDescriptions())
|
||||
.setUserRecvPerceptionOptions(scene.getUserRecvPerceptionOptions()))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import cn.daxpay.open.channel.douyin.client.credential.DouyinSdkCredential;
|
||||
import cn.daxpay.open.channel.douyin.dao.direct.DouyinDirectChannelMerchantManager;
|
||||
import cn.daxpay.open.channel.douyin.entity.direct.DouyinDirectChannelMerchant;
|
||||
import cn.daxpay.open.channel.douyin.entity.direct.DouyinDirectKeyConfig;
|
||||
import cn.daxpay.open.payment.auth.core.AppScopeEnum;
|
||||
import cn.daxpay.open.payment.douyin.facade.DouyinAppFacade;
|
||||
import cn.daxpay.open.payment.douyin.facade.DyAppView;
|
||||
import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum;
|
||||
@@ -41,6 +42,28 @@ public class DouyinDirectConfigAssembler {
|
||||
DyAppView app = douyinAppFacade.resolve(mchNo, channelMchNo, capability, null,
|
||||
ProductEnum.DOUYIN_PAY.getCode());
|
||||
|
||||
// 2-4. 组装凭证
|
||||
return assembleCredential(app, channelMchNo);
|
||||
}
|
||||
|
||||
/// 组装转账使用的通道调用凭证(下发给子应用)
|
||||
///
|
||||
/// 转账发起应用由「抖音转账配置」显式指定(网站应用, 支持手机H5获取OpenId), 不走支付能力绑定解析,
|
||||
/// 与支付链路([#buildConfig])的应用解析相互独立。
|
||||
///
|
||||
/// @param mchNo 商户号(应用归属校验)
|
||||
/// @param channelMchNo 通道商户号(定位密钥/商户绑定)
|
||||
/// @param transferAppRefId 转账发起应用引用(dy_mch_app 主键)
|
||||
/// @return 抖音 SDK 凭证, 字段对齐子应用 DouyinSdkCredential
|
||||
public DouyinSdkCredential buildTransferConfig(String mchNo, String channelMchNo, Long transferAppRefId) {
|
||||
// 1. 按引用加载转账发起应用(仅商户档, 直连商户不使用平台应用)
|
||||
DyAppView app = douyinAppFacade.getById(AppScopeEnum.MERCHANT, transferAppRefId);
|
||||
// 2-4. 组装凭证
|
||||
return assembleCredential(app, channelMchNo);
|
||||
}
|
||||
|
||||
/// 读取通道商户绑定与密钥配置, 组装凭证(第 2-4 步公共部分)
|
||||
private DouyinSdkCredential assembleCredential(DyAppView app, String channelMchNo) {
|
||||
// 2. 读取通道商户绑定(获取抖音商户号 dyMchId 作为 mchId)
|
||||
DouyinDirectChannelMerchant merchant = channelMerchantManager.lambdaQuery()
|
||||
.eq(DouyinDirectChannelMerchant::getChannelMchNo, channelMchNo)
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package cn.daxpay.open.channel.douyin.service.direct;
|
||||
|
||||
import cn.daxpay.open.channel.douyin.convert.direct.DouyinTransferConfigConvert;
|
||||
import cn.daxpay.open.channel.douyin.dao.direct.DouyinDirectChannelMerchantManager;
|
||||
import cn.daxpay.open.channel.douyin.dao.direct.DouyinTransferConfigManager;
|
||||
import cn.daxpay.open.channel.douyin.entity.direct.DouyinDirectChannelMerchant;
|
||||
import cn.daxpay.open.channel.douyin.entity.direct.DouyinTransferConfig;
|
||||
import cn.daxpay.open.channel.douyin.param.direct.DouyinTransferConfigParam;
|
||||
import cn.daxpay.open.channel.douyin.result.direct.DouyinTransferConfigResult;
|
||||
import cn.daxpay.open.payment.douyin.dao.merchant.DyMchAppManager;
|
||||
import cn.daxpay.open.payment.douyin.entity.merchant.DyMchApp;
|
||||
import cn.daxpay.open.payment.douyin.enums.DyAppTypeEnum;
|
||||
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;
|
||||
|
||||
/// # 抖音转账配置
|
||||
///
|
||||
/// 管理通道商户的转账配置(一对一: 转账发起应用)。
|
||||
/// 发起转账时由转账策略读取本配置按 [DouyinTransferConfig#getTransferAppRefId]
|
||||
/// 解析发起应用(网站应用)的 douyinAppId, 决定转出主体与收款人 openId 的来源。
|
||||
///
|
||||
/// 运营端写 [DouyinTransferConfig](MchBaseEntity) 显式 setMchNo, 避免上下文缺失。
|
||||
///
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class DouyinTransferConfigService {
|
||||
|
||||
private final DouyinTransferConfigManager douyinTransferConfigManager;
|
||||
private final DouyinDirectChannelMerchantManager douyinDirectChannelMerchantManager;
|
||||
private final DyMchAppManager dyMchAppManager;
|
||||
|
||||
/// 查询通道商户的转账配置(一对一, 未配置返回 null)
|
||||
///
|
||||
/// @param mchNo 商户号(归属校验)
|
||||
/// @param channelMchNo 通道商户号
|
||||
/// @return 转账配置(含冗余展示), 不存在返回 null
|
||||
public DouyinTransferConfigResult findByChannelMchNo(String mchNo, String channelMchNo) {
|
||||
assertChannelMerchant(mchNo, channelMchNo);
|
||||
return douyinTransferConfigManager.findByChannelMchNo(channelMchNo)
|
||||
.map(this::toResultWithMeta)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/// 保存或更新转账配置(一对一 upsert)
|
||||
///
|
||||
/// transferAppRefId 允许为空(支持清空), 但发起转账时必须已配置, 由转账策略校验。
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void saveOrUpdate(DouyinTransferConfigParam param) {
|
||||
// 校验通道商户存在与归属
|
||||
assertChannelMerchant(param.getMchNo(), param.getChannelMchNo());
|
||||
// 校验发起应用(若指定): 存在 + 归属 + 网站应用类型(仅网站应用支持手机H5获取OpenId)
|
||||
if (param.getTransferAppRefId() != null) {
|
||||
DyMchApp app = dyMchAppManager.findById(param.getTransferAppRefId())
|
||||
.orElseThrow(() -> new DataNotExistException("error.channel.douyin.transferAppNotExist"));
|
||||
if (!Objects.equals(app.getMchNo(), param.getMchNo())) {
|
||||
// 抖音: 转账发起应用不属于当前商户
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.douyin.transferAppNotBelong");
|
||||
}
|
||||
if (!Objects.equals(app.getAppType(), DyAppTypeEnum.WEB_APP.getCode())) {
|
||||
// 抖音: 转账发起应用必须是网站应用类型
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.douyin.transferAppTypeNotWebApp");
|
||||
}
|
||||
}
|
||||
// upsert: 存在则全量覆盖(含清空), 不存在则新增
|
||||
Optional<DouyinTransferConfig> existing = douyinTransferConfigManager
|
||||
.findByChannelMchNo(param.getChannelMchNo());
|
||||
if (existing.isPresent()) {
|
||||
DouyinTransferConfig entity = existing.get();
|
||||
entity.setTransferAppRefId(param.getTransferAppRefId());
|
||||
douyinTransferConfigManager.updateById(entity);
|
||||
} else {
|
||||
DouyinTransferConfig entity = DouyinTransferConfigConvert.CONVERT.toEntity(param);
|
||||
// 运营端写 MchBaseEntity 必须显式 setMchNo(父类 setter 返回类型不匹配, 单独赋值)
|
||||
entity.setMchNo(param.getMchNo());
|
||||
douyinTransferConfigManager.save(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/// 删除通道商户的转账配置(通道商户删除时级联清理)
|
||||
public void deleteByChannelMchNo(String channelMchNo) {
|
||||
douyinTransferConfigManager.deleteByChannelMchNo(channelMchNo);
|
||||
}
|
||||
|
||||
/// 校验通道商户存在且归属匹配
|
||||
private void assertChannelMerchant(String mchNo, String channelMchNo) {
|
||||
DouyinDirectChannelMerchant channelMerchant = douyinDirectChannelMerchantManager
|
||||
.findByChannelMchNo(channelMchNo)
|
||||
.orElseThrow(() -> new DataNotExistException("error.payment.channel.channelMerchantNotExist"));
|
||||
if (!Objects.equals(channelMerchant.getMchNo(), mchNo)) {
|
||||
// 抖音: 通道商户与商户号不匹配
|
||||
throw new BizInfoException(CommonErrorCode.UN_SUPPORTED_OPERATE,
|
||||
"error.payment.douyin.channelMerchantMismatch");
|
||||
}
|
||||
}
|
||||
|
||||
/// 转Result并填充冗余展示(发起应用信息)
|
||||
private DouyinTransferConfigResult toResultWithMeta(DouyinTransferConfig entity) {
|
||||
DouyinTransferConfigResult result = entity.toResult();
|
||||
// 发起应用展示信息
|
||||
if (entity.getTransferAppRefId() != null) {
|
||||
dyMchAppManager.findById(entity.getTransferAppRefId())
|
||||
.ifPresent(app -> {
|
||||
result.setTransferAppName(app.getAppName());
|
||||
result.setDouyinAppId(app.getDouyinAppId());
|
||||
result.setAppType(app.getAppType());
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,10 @@ public class DouyinTransferService {
|
||||
req.setScene(context.getTransferScene());
|
||||
req.setUserName(context.getPayeeName());
|
||||
req.setRemark(StrUtil.sub(context.getTitle(), 0, 32));
|
||||
req.setPerception(context.getReason());
|
||||
// 收款感知使用请求参数(按场景枚举选项), 不再用转账原因顶替
|
||||
req.setPerception(context.getUserRecvPerception());
|
||||
// 转账场景报备信息(按场景要求填写)
|
||||
req.setReportInfos(context.getReportInfos());
|
||||
req.setNotifyUrl(this.buildNotifyUrl(context));
|
||||
req.setCredential(credential);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.daxpay.open.channel.douyin.strategy.merchant;
|
||||
|
||||
import cn.daxpay.open.channel.douyin.dao.direct.DouyinDirectChannelMerchantManager;
|
||||
import cn.daxpay.open.channel.douyin.dao.direct.DouyinDirectKeyConfigManager;
|
||||
import cn.daxpay.open.channel.douyin.dao.direct.DouyinTransferConfigManager;
|
||||
import cn.daxpay.open.channel.douyin.entity.direct.DouyinDirectChannelMerchant;
|
||||
import cn.daxpay.open.channel.douyin.entity.direct.DouyinDirectKeyConfig;
|
||||
import cn.daxpay.open.payment.douyin.dao.channel.DyChannelAppCapabilityManager;
|
||||
@@ -17,6 +18,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
/// 在通道商户删除时清理抖音直连相关的扩展数据:
|
||||
/// - 通道商户绑定(douyin_direct_channel_merchant)、密钥配置(douyin_direct_key_config)
|
||||
/// - 通道能力绑定(dy_channel_app_capability, 释放对商户/平台应用的引用)
|
||||
/// - 转账配置(douyin_transfer_config, 释放转账发起应用引用)
|
||||
///
|
||||
/// 注意:商户/平台级应用主数据(dy_mch_app / dy_platform_app)不在此清理 —— 它们可被多个通道商户引用,
|
||||
/// 归属商户/平台级生命周期管理。
|
||||
@@ -28,6 +30,7 @@ public class DouyinDirectChannelMerchantCleanupStrategy implements ChannelMercha
|
||||
private final DouyinDirectChannelMerchantManager douyinDirectChannelMerchantManager;
|
||||
private final DouyinDirectKeyConfigManager douyinDirectAppKeyConfigManager;
|
||||
private final DyChannelAppCapabilityManager dyChannelAppCapabilityManager;
|
||||
private final DouyinTransferConfigManager douyinTransferConfigManager;
|
||||
|
||||
/// 对应产品: 抖音支付直连
|
||||
@Override
|
||||
@@ -43,5 +46,7 @@ public class DouyinDirectChannelMerchantCleanupStrategy implements ChannelMercha
|
||||
douyinDirectAppKeyConfigManager.deleteByField(DouyinDirectKeyConfig::getChannelMchNo, channelMchNo);
|
||||
// 清理通道能力绑定(释放对商户/平台抖音应用的引用)
|
||||
dyChannelAppCapabilityManager.deleteByChannelMchNo(channelMchNo);
|
||||
// 清理转账配置(释放转账发起应用引用)
|
||||
douyinTransferConfigManager.deleteByChannelMchNo(channelMchNo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package cn.daxpay.open.channel.douyin.strategy.transfer;
|
||||
|
||||
import cn.daxpay.open.channel.douyin.client.credential.DouyinSdkCredential;
|
||||
import cn.daxpay.open.channel.douyin.dao.direct.DouyinDirectChannelMerchantManager;
|
||||
import cn.daxpay.open.channel.douyin.entity.direct.DouyinDirectChannelMerchant;
|
||||
import cn.daxpay.open.channel.douyin.dao.direct.DouyinTransferConfigManager;
|
||||
import cn.daxpay.open.channel.douyin.entity.direct.DouyinTransferConfig;
|
||||
import cn.daxpay.open.channel.douyin.enums.DouyinTransferSceneEnum;
|
||||
import cn.daxpay.open.channel.douyin.service.direct.DouyinDirectConfigAssembler;
|
||||
import cn.daxpay.open.channel.douyin.service.payment.transfer.DouyinTransferService;
|
||||
import cn.daxpay.open.payment.strategy.transfer.AbsTransferStrategy;
|
||||
@@ -11,8 +12,8 @@ import cn.daxpay.open.payment.trade.transfer.bo.TransferResultBo;
|
||||
import cn.daxpay.open.payment.trade.transfer.enums.TransferPayeeTypeEnum;
|
||||
import cn.daxpay.open.payment.trade.transfer.param.TransferParam;
|
||||
import cn.daxpay.open.platform.core.code.CommonErrorCode;
|
||||
import cn.daxpay.open.platform.core.code.DaxPayErrorCode;
|
||||
import cn.daxpay.open.platform.core.exception.BizInfoException;
|
||||
import cn.daxpay.open.platform.core.exception.config.ConfigErrorException;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -22,8 +23,9 @@ import org.springframework.stereotype.Service;
|
||||
///
|
||||
/// 抖音商家转账的通道策略。
|
||||
/// 通道差异:
|
||||
/// - openid 收款人([TransferPayeeTypeEnum#OPENID])
|
||||
/// - transfer_scene_id 取自通道商户配置, 未配置时报错
|
||||
/// - openid 收款人([TransferPayeeTypeEnum#OPENID]), 收款人 openId 由「转账发起应用」(网站应用)承接 H5 授权
|
||||
/// - transfer_scene_id 为主数据枚举(1001-1007), 发起转账时由前端选择传入, 无需预配置
|
||||
/// - 转账发起应用由通道商户的转账配置([DouyinTransferConfig])显式指定
|
||||
/// - 金额大于等于 2000 元必填收款人姓名(子应用加密上送)
|
||||
@Slf4j
|
||||
@Service
|
||||
@@ -35,7 +37,7 @@ public class DouyinTransferStrategy extends AbsTransferStrategy {
|
||||
|
||||
private final DouyinTransferService douyinTransferService;
|
||||
private final DouyinDirectConfigAssembler douyinDirectConfigAssembler;
|
||||
private final DouyinDirectChannelMerchantManager douyinDirectChannelMerchantManager;
|
||||
private final DouyinTransferConfigManager douyinTransferConfigManager;
|
||||
|
||||
@Override
|
||||
public String getChannel() {
|
||||
@@ -57,6 +59,17 @@ public class DouyinTransferStrategy extends AbsTransferStrategy {
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.douyin.transferNameRequired");
|
||||
}
|
||||
// 转账场景ID必填且必须为合法枚举
|
||||
if (StrUtil.isBlank(param.getTransferScene())) {
|
||||
// 抖音: 转账场景ID必填
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.douyin.transferSceneIdRequired");
|
||||
}
|
||||
if (DouyinTransferSceneEnum.findByCode(param.getTransferScene()) == null) {
|
||||
// 抖音: 不支持的转账场景ID[{0}]
|
||||
throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR,
|
||||
"error.channel.douyin.transferSceneNameInvalid", param.getTransferScene());
|
||||
}
|
||||
}
|
||||
|
||||
/// 发起转账
|
||||
@@ -73,23 +86,20 @@ public class DouyinTransferStrategy extends AbsTransferStrategy {
|
||||
return douyinTransferService.sync(context, credential);
|
||||
}
|
||||
|
||||
/// 组装通道调用凭证并注入转账场景
|
||||
/// 组装通道调用凭证
|
||||
///
|
||||
/// 转账场景(transfer_scene_id)从通道商户配置读取, 经上下文回写, 由编排层在"处理中"镜像落库。
|
||||
/// 转账场景(transfer_scene_id)来自请求参数(前端选择的主数据枚举), 已在 [#doValidateParam] 校验合法性。
|
||||
/// 转账发起应用(决定转出主体与收款人 openId 来源)由通道商户的转账配置显式指定,
|
||||
/// 读取 [DouyinTransferConfig#getTransferAppRefId] 组装凭证。
|
||||
private DouyinSdkCredential buildCredential(TransferStrategyContext context) {
|
||||
DouyinDirectChannelMerchant channelMerchant = douyinDirectChannelMerchantManager.lambdaQuery()
|
||||
.eq(DouyinDirectChannelMerchant::getChannelMchNo, context.getChannelMchNo())
|
||||
.oneOpt()
|
||||
.orElseThrow(() -> new BizInfoException(DaxPayErrorCode.CONFIG_NOT_EXIST,
|
||||
"error.payment.channel.channelMerchantNotExist"));
|
||||
if (StrUtil.isBlank(channelMerchant.getTransferScene())) {
|
||||
// 抖音: 转账场景未配置
|
||||
throw new BizInfoException(DaxPayErrorCode.CONFIG_NOT_EXIST,
|
||||
"error.channel.douyin.transferSceneNotConfigured");
|
||||
DouyinTransferConfig transferConfig = douyinTransferConfigManager
|
||||
.findByChannelMchNo(context.getChannelMchNo())
|
||||
.orElseThrow(() -> new ConfigErrorException("error.channel.douyin.transferAppNotConfigured"));
|
||||
if (transferConfig.getTransferAppRefId() == null) {
|
||||
// 抖音: 转账发起应用未配置
|
||||
throw new ConfigErrorException("error.channel.douyin.transferAppNotConfigured");
|
||||
}
|
||||
context.setTransferScene(channelMerchant.getTransferScene());
|
||||
return douyinDirectConfigAssembler.buildConfig(
|
||||
context.getMchNo(), context.getChannelMchNo(), null);
|
||||
return douyinDirectConfigAssembler.buildTransferConfig(
|
||||
context.getMchNo(), context.getChannelMchNo(), transferConfig.getTransferAppRefId());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,5 +25,14 @@
|
||||
"transferEncryptFailed": "Douyin transfer name encryption failed: {0}",
|
||||
"transferOnlyOpenid": "Douyin only supports openid payee",
|
||||
"transferNameRequired": "Douyin: payee name required for amount >= 2000 CNY",
|
||||
"transferSceneNotConfigured": "Douyin: transfer scene not configured"
|
||||
"transferSceneNotConfigured": "Douyin: transfer scene not configured",
|
||||
"transferSceneNotFound": "Douyin: transfer scene config not found",
|
||||
"transferSceneNameInvalid": "Douyin: unsupported transfer scene ID: {0}",
|
||||
"transferSceneNotBelong": "Douyin: transfer scene config does not belong to the current merchant",
|
||||
"transferSceneIdRequired": "Douyin: Transfer scene ID is required",
|
||||
"transferSceneIdInvalid": "Douyin: invalid transfer scene config id: {0}",
|
||||
"transferAppNotExist": "Douyin: Transfer initiator app not found",
|
||||
"transferAppNotBelong": "Douyin: Transfer initiator app does not belong to the current merchant",
|
||||
"transferAppTypeNotWebApp": "Douyin: Transfer initiator app must be a Web App",
|
||||
"transferAppNotConfigured": "Douyin: Transfer initiator app not configured"
|
||||
}
|
||||
|
||||
@@ -25,5 +25,14 @@
|
||||
"transferEncryptFailed": "Enkripsi nama transfer Douyin gagal: {0}",
|
||||
"transferOnlyOpenid": "Douyin hanya mendukung penerima openid",
|
||||
"transferNameRequired": "Douyin: nama penerima wajib untuk jumlah >= 2000 CNY",
|
||||
"transferSceneNotConfigured": "Douyin: skenario transfer belum dikonfigurasi"
|
||||
"transferSceneNotConfigured": "Douyin: skenario transfer belum dikonfigurasi",
|
||||
"transferSceneNotFound": "Douyin: konfigurasi skenario transfer tidak ditemukan",
|
||||
"transferSceneNameInvalid": "Douyin: ID skenario transfer tidak didukung: {0}",
|
||||
"transferSceneNotBelong": "Douyin: konfigurasi skenario transfer bukan milik merchant saat ini",
|
||||
"transferSceneIdRequired": "Douyin: ID adegan transfer wajib diisi",
|
||||
"transferSceneIdInvalid": "Douyin: format id konfigurasi skenario transfer tidak valid: {0}",
|
||||
"transferAppNotExist": "Douyin: Aplikasi inisiator transfer tidak ditemukan",
|
||||
"transferAppNotBelong": "Douyin: Aplikasi inisiator transfer bukan milik merchant saat ini",
|
||||
"transferAppTypeNotWebApp": "Douyin: Aplikasi inisiator transfer harus berjenis Web App",
|
||||
"transferAppNotConfigured": "Douyin: Aplikasi inisiator transfer belum dikonfigurasi"
|
||||
}
|
||||
|
||||
@@ -25,5 +25,14 @@
|
||||
"transferEncryptFailed": "抖音送金氏名暗号化エラー: {0}",
|
||||
"transferOnlyOpenid": "抖音はopenid受取人のみサポートしています",
|
||||
"transferNameRequired": "抖音: 2000元以上は受取人氏名が必須です",
|
||||
"transferSceneNotConfigured": "抖音: 送金シーンが未設定です"
|
||||
"transferSceneNotConfigured": "抖音: 送金シーンが未設定です",
|
||||
"transferSceneNotFound": "抖音: 送金シーン設定が存在しません",
|
||||
"transferSceneNameInvalid": "抖音: サポートされていない送金シーンID: {0}",
|
||||
"transferSceneNotBelong": "抖音: 送金シーン設定は現在の加盟店に属していません",
|
||||
"transferSceneIdRequired": "Douyin: 送金シーンIDは必須です",
|
||||
"transferSceneIdInvalid": "抖音: 送金シーン設定IDの形式エラー: {0}",
|
||||
"transferAppNotExist": "Douyin: 振込发起アプリが見つかりません",
|
||||
"transferAppNotBelong": "Douyin: 振込发起アプリは現在のマーチャントに属していません",
|
||||
"transferAppTypeNotWebApp": "Douyin: 振込发起アプリはサイトアプリ(Webアプリ)タイプである必要があります",
|
||||
"transferAppNotConfigured": "Douyin: 振込发起アプリが未設定です"
|
||||
}
|
||||
|
||||
@@ -25,5 +25,14 @@
|
||||
"transferEncryptFailed": "더우인 송금 이름 암호화 실패: {0}",
|
||||
"transferOnlyOpenid": "더우인은 openid 수취인만 지원합니다",
|
||||
"transferNameRequired": "더우인: 2000원 이상은 수취인 이름 필수",
|
||||
"transferSceneNotConfigured": "더우인: 송금 시나리오 미설정"
|
||||
"transferSceneNotConfigured": "더우인: 송금 시나리오 미설정",
|
||||
"transferSceneNotFound": "더우인: 송금 시나리오 설정이 존재하지 않습니다",
|
||||
"transferSceneNameInvalid": "더우인: 지원되지 않는 송금 시나리오 ID: {0}",
|
||||
"transferSceneNotBelong": "더우인: 송금 시나리오 설정이 현재 가맹점에 속하지 않습니다",
|
||||
"transferSceneIdRequired": "Douyin: 이체 장면 ID는 필수입니다",
|
||||
"transferSceneIdInvalid": "더우인: 송금 시나리오 설정 ID 형식 오류: {0}",
|
||||
"transferAppNotExist": "Douyin: 이체 발신 앱을 찾을 수 없습니다",
|
||||
"transferAppNotBelong": "Douyin: 이체 발신 앱이 현재 가맹점에 속하지 않습니다",
|
||||
"transferAppTypeNotWebApp": "Douyin: 이체 발신 앱은 웹 앱(Web App) 유형이어야 합니다",
|
||||
"transferAppNotConfigured": "Douyin: 이체 발신 앱이 구성되지 않았습니다"
|
||||
}
|
||||
|
||||
@@ -25,5 +25,14 @@
|
||||
"transferEncryptFailed": "Penyulitan nama pindahan Douyin gagal: {0}",
|
||||
"transferOnlyOpenid": "Douyin hanya menyokong penerima openid",
|
||||
"transferNameRequired": "Douyin: nama penerima wajib untuk jumlah >= 2000 CNY",
|
||||
"transferSceneNotConfigured": "Douyin: senario pindahan belum dikonfigurasi"
|
||||
"transferSceneNotConfigured": "Douyin: senario pindahan belum dikonfigurasi",
|
||||
"transferSceneNotFound": "Douyin: konfigurasi senario pindahan tidak dijumpai",
|
||||
"transferSceneNameInvalid": "Douyin: ID senario pindahan tidak disokong: {0}",
|
||||
"transferSceneNotBelong": "Douyin: konfigurasi senario pindahan bukan milik merchant semasa",
|
||||
"transferSceneIdRequired": "Douyin: ID babak pemindahan diperlukan",
|
||||
"transferSceneIdInvalid": "Douyin: format id konfigurasi senario pindahan tidak sah: {0}",
|
||||
"transferAppNotExist": "Douyin: Aplikasi pemula pemindahan tidak dijumpai",
|
||||
"transferAppNotBelong": "Douyin: Aplikasi pemula pemindahan tidak milik merchant semasa",
|
||||
"transferAppTypeNotWebApp": "Douyin: Aplikasi pemula pemindahan mesti jenis Web App",
|
||||
"transferAppNotConfigured": "Douyin: Aplikasi pemula pemindahan belum dikonfigurasi"
|
||||
}
|
||||
|
||||
@@ -25,5 +25,14 @@
|
||||
"transferEncryptFailed": "การเข้ารหัสชื่อการโอน Douyin ล้มเหลว: {0}",
|
||||
"transferOnlyOpenid": "Douyin รองรับผู้รับ openid เท่านั้น",
|
||||
"transferNameRequired": "Douyin: ต้องใส่ชื่อผู้รับเมื่อมากกว่าหรือเท่ากับ 2000 หยวน",
|
||||
"transferSceneNotConfigured": "Douyin: ยังไม่ได้กำหนดค่าสถานการณ์การโอน"
|
||||
"transferSceneNotConfigured": "Douyin: ยังไม่ได้กำหนดค่าสถานการณ์การโอน",
|
||||
"transferSceneNotFound": "Douyin: ไม่พบการกำหนดค่าสถานการณ์การโอน",
|
||||
"transferSceneNameInvalid": "Douyin: ไม่รองรับรหัสสถานการณ์การโอน: {0}",
|
||||
"transferSceneNotBelong": "Douyin: การกำหนดค่าสถานการณ์การโอนไม่ใช่ของผู้ค้าปัจจุบัน",
|
||||
"transferSceneIdRequired": "Douyin: ต้องระบุรหัสสถานการณ์การโอน",
|
||||
"transferSceneIdInvalid": "Douyin: รูปแบบรหัสการกำหนดค่าสถานการณ์การโอนไม่ถูกต้อง: {0}",
|
||||
"transferAppNotExist": "Douyin: ไม่พบแอปเริ่มการโอน",
|
||||
"transferAppNotBelong": "Douyin: แอปเริ่มการโอนไม่ได้เป็นของร้านค้าปัจจุบัน",
|
||||
"transferAppTypeNotWebApp": "Douyin: แอปเริ่มการโอนต้องเป็นประเภทเว็บแอป (Web App)",
|
||||
"transferAppNotConfigured": "Douyin: ยังไม่ได้กำหนดค่าแอปเริ่มการโอน"
|
||||
}
|
||||
|
||||
@@ -25,5 +25,14 @@
|
||||
"transferEncryptFailed": "Mã hóa tên chuyển khoản Douyin thất bại: {0}",
|
||||
"transferOnlyOpenid": "Douyin chỉ hỗ trợ người nhận openid",
|
||||
"transferNameRequired": "Douyin: bắt buộc nhập tên người nhận khi >= 2000 CNY",
|
||||
"transferSceneNotConfigured": "Douyin: chưa cấu hình kịch bản chuyển khoản"
|
||||
"transferSceneNotConfigured": "Douyin: chưa cấu hình kịch bản chuyển khoản",
|
||||
"transferSceneNotFound": "Douyin: cấu hình kịch bản chuyển khoản không tồn tại",
|
||||
"transferSceneNameInvalid": "Douyin: ID kịch bản chuyển khoản không được hỗ trợ: {0}",
|
||||
"transferSceneNotBelong": "Douyin: cấu hình kịch bản chuyển khoản không thuộc merchant hiện tại",
|
||||
"transferSceneIdRequired": "Douyin: ID cảnh chuyển khoản là bắt buộc",
|
||||
"transferSceneIdInvalid": "Douyin: định dạng id cấu hình kịch bản chuyển khoản không hợp lệ: {0}",
|
||||
"transferAppNotExist": "Douyin: Không tìm thấy ứng dụng khởi tạo chuyển khoản",
|
||||
"transferAppNotBelong": "Douyin: Ứng dụng khởi tạo chuyển khoản không thuộc merchant hiện tại",
|
||||
"transferAppTypeNotWebApp": "Douyin: Ứng dụng khởi tạo chuyển khoản phải là loại Web App",
|
||||
"transferAppNotConfigured": "Douyin: Ứng dụng khởi tạo chuyển khoản chưa được thiết lập"
|
||||
}
|
||||
|
||||
@@ -25,5 +25,14 @@
|
||||
"transferEncryptFailed": "抖音转账姓名加密失败: {0}",
|
||||
"transferOnlyOpenid": "抖音仅支持 openid 收款人",
|
||||
"transferNameRequired": "抖音: 大于等于2000元必须填收款人姓名",
|
||||
"transferSceneNotConfigured": "抖音: 转账场景未配置"
|
||||
"transferSceneNotConfigured": "抖音: 转账场景未配置",
|
||||
"transferSceneNotFound": "抖音: 转账场景配置不存在",
|
||||
"transferSceneNameInvalid": "抖音: 不支持的转账场景ID: {0}",
|
||||
"transferSceneNotBelong": "抖音: 转账场景配置不属于当前商户",
|
||||
"transferSceneIdRequired": "抖音: 转账场景ID必填",
|
||||
"transferSceneIdInvalid": "抖音: 转账场景配置ID格式错误: {0}",
|
||||
"transferAppNotExist": "抖音: 转账发起应用不存在",
|
||||
"transferAppNotBelong": "抖音: 转账发起应用不属于当前商户",
|
||||
"transferAppTypeNotWebApp": "抖音: 转账发起应用必须是网站应用类型",
|
||||
"transferAppNotConfigured": "抖音: 转账发起应用未配置"
|
||||
}
|
||||
|
||||
@@ -25,5 +25,14 @@
|
||||
"transferEncryptFailed": "抖音轉賬姓名加密失敗: {0}",
|
||||
"transferOnlyOpenid": "抖音僅支持 openid 收款人",
|
||||
"transferNameRequired": "抖音: 大於等於2000元必須填收款人姓名",
|
||||
"transferSceneNotConfigured": "抖音: 轉賬場景未配置"
|
||||
"transferSceneNotConfigured": "抖音: 轉賬場景未配置",
|
||||
"transferSceneNotFound": "抖音: 轉賬場景設定不存在",
|
||||
"transferSceneNameInvalid": "抖音: 不支援的轉賬場景ID: {0}",
|
||||
"transferSceneNotBelong": "抖音: 轉賬場景設定不屬於目前商戶",
|
||||
"transferSceneIdRequired": "抖音: 轉賬場景ID必填",
|
||||
"transferSceneIdInvalid": "抖音: 轉賬場景設定ID格式錯誤: {0}",
|
||||
"transferAppNotExist": "抖音: 轉帳發起應用不存在",
|
||||
"transferAppNotBelong": "抖音: 轉帳發起應用不屬於目前商戶",
|
||||
"transferAppTypeNotWebApp": "抖音: 轉賬發起應用必須是網站應用類型",
|
||||
"transferAppNotConfigured": "抖音: 轉帳發起應用未設定"
|
||||
}
|
||||
|
||||
@@ -25,5 +25,14 @@
|
||||
"transferEncryptFailed": "抖音轉帳姓名加密失敗: {0}",
|
||||
"transferOnlyOpenid": "抖音僅支持 openid 收款人",
|
||||
"transferNameRequired": "抖音: 大於等於2000元必須填收款人姓名",
|
||||
"transferSceneNotConfigured": "抖音: 轉帳場景未配置"
|
||||
"transferSceneNotConfigured": "抖音: 轉帳場景未配置",
|
||||
"transferSceneNotFound": "抖音: 轉帳場景設定不存在",
|
||||
"transferSceneNameInvalid": "抖音: 不支援的轉帳場景ID: {0}",
|
||||
"transferSceneNotBelong": "抖音: 轉帳場景設定不屬於目前商戶",
|
||||
"transferSceneIdRequired": "抖音: 轉賬場景ID必填",
|
||||
"transferSceneIdInvalid": "抖音: 轉帳場景設定ID格式錯誤: {0}",
|
||||
"transferAppNotExist": "抖音: 轉帳發起應用不存在",
|
||||
"transferAppNotBelong": "抖音: 轉帳發起應用不屬於目前商戶",
|
||||
"transferAppTypeNotWebApp": "抖音: 轉帳發起應用必須是網站應用類型",
|
||||
"transferAppNotConfigured": "抖音: 轉帳發起應用未設定"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user