diff --git a/_config/sql/update-tables.sql b/_config/sql/update-tables.sql index e69de29bb..7161a27c3 100644 --- a/_config/sql/update-tables.sql +++ b/_config/sql/update-tables.sql @@ -0,0 +1,35 @@ +-- ============================================================ +-- 支付宝转账场景预置数据(补跑段) +-- 表结构与索引(先删后增)已执行成功, 本段仅执行预置数据初始化: +-- 为现有直连通道商户预置8个转账场景行(enabled=false, is_default=false) +-- 场景为支付宝协议固定中文名称, 重复执行时靠场景唯一索引(部分索引)幂等跳过 +-- ============================================================ + +-- 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 $$; diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/controller/direct/WechatTransferConfigController.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/controller/direct/WechatTransferConfigController.java new file mode 100644 index 000000000..3cc71a8c9 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/controller/direct/WechatTransferConfigController.java @@ -0,0 +1,52 @@ +package cn.daxpay.open.channel.wechat.controller.direct; + +import cn.daxpay.open.channel.wechat.param.direct.WechatTransferConfigParam; +import cn.daxpay.open.channel.wechat.result.direct.WechatTransferConfigResult; +import cn.daxpay.open.channel.wechat.service.direct.WechatTransferConfigService; +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/wechat/transfer-config") +@RequiredArgsConstructor +public class WechatTransferConfigController { + + private final WechatTransferConfigService wechatTransferConfigService; + + @PermCode(code = PermCodes.Action.VIEW) + @Operation(summary = "查询通道商户的转账配置") + @GetMapping("/find-by-channel-mch-no") + public Result findByChannelMchNo( + @NotBlank(message = "{validation.field.mchNo.notBlank}") String mchNo, + @NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") String channelMchNo) { + return Res.ok(wechatTransferConfigService.findByChannelMchNo(mchNo, channelMchNo)); + } + + @PermCode(code = PermCodes.Action.MANAGE) + @Operation(summary = "保存或更新转账配置(一对一)") + @PostMapping("/save") + public Result save(@RequestBody @Validated WechatTransferConfigParam param) { + wechatTransferConfigService.saveOrUpdate(param); + return Res.ok(); + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/convert/direct/WechatTransferConfigConvert.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/convert/direct/WechatTransferConfigConvert.java new file mode 100644 index 000000000..bd8dc2030 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/convert/direct/WechatTransferConfigConvert.java @@ -0,0 +1,31 @@ +package cn.daxpay.open.channel.wechat.convert.direct; + +import cn.daxpay.open.channel.wechat.entity.direct.WechatTransferConfig; +import cn.daxpay.open.channel.wechat.param.direct.WechatTransferConfigParam; +import cn.daxpay.open.channel.wechat.result.direct.WechatTransferConfigResult; +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +/// # 微信转账配置转换 +/// +/// MapStruct 转换器, 负责转账配置在实体、参数和返回结果之间的转换, 更新时空值不覆盖。 +/// 冗余展示字段(发起应用名/wxAppId/场景名)由 Service 填充, 不经 Convert。 +/// +@Mapper +public interface WechatTransferConfigConvert { + + WechatTransferConfigConvert CONVERT = Mappers.getMapper(WechatTransferConfigConvert.class); + + /// 转换为返回对象 + WechatTransferConfigResult toResult(WechatTransferConfig entity); + + /// 转换为实体 + WechatTransferConfig toEntity(WechatTransferConfigParam param); + + /// 更新源数据到实体(空值不覆盖) + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) + void copy(WechatTransferConfigParam param, @MappingTarget WechatTransferConfig entity); +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/direct/WechatTransferConfigManager.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/direct/WechatTransferConfigManager.java new file mode 100644 index 000000000..95913175b --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/direct/WechatTransferConfigManager.java @@ -0,0 +1,29 @@ +package cn.daxpay.open.channel.wechat.dao.direct; + +import cn.daxpay.open.channel.wechat.entity.direct.WechatTransferConfig; +import cn.daxpay.open.platform.common.mybatisplus.impl.BaseManager; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +/// # 微信转账配置 +/// +/// 一个通道商户一条转账配置(一对一), 提供按通道商户号查询/删除。 +/// +@Repository +public class WechatTransferConfigManager extends BaseManager { + + /// 按通道商户号查询转账配置(一对一) + public Optional findByChannelMchNo(String channelMchNo) { + return lambdaQuery() + .eq(WechatTransferConfig::getChannelMchNo, channelMchNo) + .oneOpt(); + } + + /// 按通道商户号删除转账配置(逻辑删除, 通道商户删除时级联清理) + public void deleteByChannelMchNo(String channelMchNo) { + lambdaUpdate() + .eq(WechatTransferConfig::getChannelMchNo, channelMchNo) + .remove(); + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/direct/WechatTransferConfigMapper.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/direct/WechatTransferConfigMapper.java new file mode 100644 index 000000000..d9120ab4f --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/dao/direct/WechatTransferConfigMapper.java @@ -0,0 +1,13 @@ +package cn.daxpay.open.channel.wechat.dao.direct; + +import cn.daxpay.open.channel.wechat.entity.direct.WechatTransferConfig; +import com.github.yulichang.base.MPJBaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/// # 微信转账配置 +/// +/// 微信转账配置 MyBatis-Plus Mapper, 继承 MPJBaseMapper 支持多表联查。 +/// +@Mapper +public interface WechatTransferConfigMapper extends MPJBaseMapper { +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/entity/direct/WechatTransferConfig.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/entity/direct/WechatTransferConfig.java new file mode 100644 index 000000000..04ee38d11 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/entity/direct/WechatTransferConfig.java @@ -0,0 +1,42 @@ +package cn.daxpay.open.channel.wechat.entity.direct; + +import cn.daxpay.open.channel.wechat.convert.direct.WechatTransferConfigConvert; +import cn.daxpay.open.channel.wechat.result.direct.WechatTransferConfigResult; +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.wechat.strategy.direct.transfer.WechatTransferStrategy] +/// 读取本配置注入 transfer_scene 并按 [transferAppRefId] 解析发起应用(公众号)的 wxAppId。 +/// +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@TableName("wechat_transfer_config") +public class WechatTransferConfig extends MchBaseEntity implements ToResult { + + /// 通道商户号 + @TableField(updateStrategy = FieldStrategy.NEVER) + private String channelMchNo; + + /// 转账场景ID(微信 transfer_scene, 8 枚举之一, 允许为空待后续补配) + /// @see cn.daxpay.open.channel.wechat.enums.WechatTransferSceneEnum + private String transferScene; + + /// 转账发起应用引用(指向 wx_mch_app 主键, 须为公众号类型, 决定 appid 与 openid 来源) + private Long transferAppRefId; + + /// 转换 + @Override + public WechatTransferConfigResult toResult() { + return WechatTransferConfigConvert.CONVERT.toResult(this); + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/param/direct/WechatTransferConfigParam.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/param/direct/WechatTransferConfigParam.java new file mode 100644 index 000000000..faff12fe2 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/param/direct/WechatTransferConfigParam.java @@ -0,0 +1,31 @@ +package cn.daxpay.open.channel.wechat.param.direct; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.experimental.Accessors; + +/// # 微信转账配置保存参数 +/// +/// 一对一 upsert: 存在则更新, 不存在则新增。`transferScene` 与 `transferAppRefId` 均允许为空 +/// (支持分步配置), 但发起转账时两者必须齐备。 +/// +@Data +@Accessors(chain = true) +@Schema(title = "微信转账配置保存参数") +public class WechatTransferConfigParam { + + @NotBlank(message = "{validation.field.mchNo.notBlank}") + @Schema(description = "商户号") + private String mchNo; + + @NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") + @Schema(description = "通道商户号") + private String channelMchNo; + + @Schema(description = "转账场景ID(微信 transfer_scene, 8 枚举之一, 允许为空待补配)") + private String transferScene; + + @Schema(description = "转账发起应用引用(指向 wx_mch_app 主键, 须为公众号类型)") + private Long transferAppRefId; +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/direct/WechatTransferConfigResult.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/direct/WechatTransferConfigResult.java new file mode 100644 index 000000000..6f5dc8170 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/direct/WechatTransferConfigResult.java @@ -0,0 +1,46 @@ +package cn.daxpay.open.channel.wechat.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; + +/// # 微信转账配置 +/// +/// 转账配置返回结果对象。冗余展示字段(发起应用名/wxAppId/应用类型/场景名)由 +/// [cn.daxpay.open.channel.wechat.service.direct.WechatTransferConfigService] 填充, +/// 不经 MapStruct 自动映射。 +/// +@EqualsAndHashCode(callSuper = true) +@Data +@Accessors(chain = true) +@Schema(title = "微信转账配置") +public class WechatTransferConfigResult extends BaseResult { + + @Schema(description = "商户号") + private String mchNo; + + @Schema(description = "通道商户号") + private String channelMchNo; + + @Schema(description = "转账场景ID") + private String transferScene; + + @Schema(description = "转账发起应用引用") + private Long transferAppRefId; + + // ===== 冗余展示(由 Service 填充) ===== + + @Schema(description = "场景名称(枚举推导, 便于展示)") + private String sceneName; + + @Schema(description = "发起应用名称") + private String transferAppName; + + @Schema(description = "发起应用 AppId(真实微信 AppId)") + private String wxAppId; + + @Schema(description = "发起应用类型(须为 official_account 公众号)") + private String appType; +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/direct/WechatDirectConfigAssembler.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/direct/WechatDirectConfigAssembler.java index 1ac4c3adb..0a5a8fae7 100644 --- a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/direct/WechatDirectConfigAssembler.java +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/direct/WechatDirectConfigAssembler.java @@ -5,9 +5,11 @@ import cn.daxpay.open.channel.wechat.dao.direct.WechatDirectChannelMerchantManag import cn.daxpay.open.channel.wechat.entity.direct.WechatDirectChannelMerchant; import cn.daxpay.open.channel.wechat.entity.direct.WechatDirectKeyConfig; import cn.daxpay.open.channel.wechat.strategy.direct.pay.WechatDirectPayStrategy; +import cn.daxpay.open.payment.auth.core.AppScopeEnum; import cn.daxpay.open.payment.wx.facade.WxAppFacade; import cn.daxpay.open.payment.wx.facade.WxAppView; import cn.daxpay.open.platform.core.code.CommonErrorCode; +import cn.daxpay.open.platform.core.code.DaxPayErrorCode; import cn.daxpay.open.platform.core.enums.pay.channel.ProductEnum; import cn.daxpay.open.platform.core.exception.BizInfoException; import cn.daxpay.open.platform.core.exception.DataNotExistException; @@ -63,6 +65,25 @@ public class WechatDirectConfigAssembler { return this.assemble(null, channelMchNo, channelMerchant); } + /// 转账专用凭证组装(按发起应用引用解析, 不经 capability) + /// + /// 转账场景的应用由「微信转账配置」显式指定(公众号), 不走支付能力绑定解析。 + /// 直接按 [transferAppRefId] 加载商户档应用取 wxAppId, 装载密钥。 + /// + /// @param channelMchNo 通道商户号(密钥查询) + /// @param transferAppRefId 转账发起应用引用(wx_mch_app 主键) + /// @return 微信 SDK 凭证, wxAppId 来自转账配置指定的公众号应用 + public WechatSdkCredential buildTransferConfig(String channelMchNo, Long transferAppRefId) { + WxAppView app = wxAppFacade.getById(AppScopeEnum.MERCHANT, transferAppRefId); + if (app == null) { + // 微信: 转账发起应用未配置或已删除 + throw new BizInfoException(DaxPayErrorCode.CONFIG_NOT_EXIST, + "error.channel.wechat.transferAppNotConfigured"); + } + WechatDirectChannelMerchant channelMerchant = this.loadChannelMerchant(channelMchNo); + return this.assemble(app.wxAppId(), channelMchNo, channelMerchant); + } + /// 加载通道商户绑定(channelMchNo 是系统生成号, 不等于 wxMchId) private WechatDirectChannelMerchant loadChannelMerchant(String channelMchNo) { return wechatDirectChannelMerchantManager.findByChannelMchNo(channelMchNo) diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/direct/WechatTransferConfigService.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/direct/WechatTransferConfigService.java new file mode 100644 index 000000000..3b151ca14 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/direct/WechatTransferConfigService.java @@ -0,0 +1,133 @@ +package cn.daxpay.open.channel.wechat.service.direct; + +import cn.daxpay.open.channel.wechat.convert.direct.WechatTransferConfigConvert; +import cn.daxpay.open.channel.wechat.dao.direct.WechatDirectChannelMerchantManager; +import cn.daxpay.open.channel.wechat.dao.direct.WechatTransferConfigManager; +import cn.daxpay.open.channel.wechat.entity.direct.WechatDirectChannelMerchant; +import cn.daxpay.open.channel.wechat.entity.direct.WechatTransferConfig; +import cn.daxpay.open.channel.wechat.enums.WechatTransferSceneEnum; +import cn.daxpay.open.channel.wechat.param.direct.WechatTransferConfigParam; +import cn.daxpay.open.channel.wechat.result.direct.WechatTransferConfigResult; +import cn.daxpay.open.payment.wx.dao.merchant.WxMchAppManager; +import cn.daxpay.open.payment.wx.entity.merchant.WxMchApp; +import cn.daxpay.open.payment.wx.enums.WxAppTypeEnum; +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.Objects; +import java.util.Optional; + +/// # 微信转账配置 +/// +/// 管理通道商户的转账配置(一对一: 转账场景 + 转账发起应用)。 +/// 发起转账时由转账策略读取本配置注入场景并按 [WechatTransferConfig#getTransferAppRefId] +/// 解析发起应用(公众号)的 wxAppId, 替代通道商户表上的单值 transferScene。 +/// +/// 运营端写 [WechatTransferConfig](MchBaseEntity) 显式 setMchNo, 避免上下文缺失。 +/// +@Slf4j +@Service +@RequiredArgsConstructor +public class WechatTransferConfigService { + + private final WechatTransferConfigManager wechatTransferConfigManager; + private final WechatDirectChannelMerchantManager wechatDirectChannelMerchantManager; + private final WxMchAppManager wxMchAppManager; + + /// 查询通道商户的转账配置(一对一, 未配置返回 null) + /// + /// @param mchNo 商户号(归属校验) + /// @param channelMchNo 通道商户号 + /// @return 转账配置(含冗余展示), 不存在返回 null + public WechatTransferConfigResult findByChannelMchNo(String mchNo, String channelMchNo) { + assertChannelMerchant(mchNo, channelMchNo); + return wechatTransferConfigManager.findByChannelMchNo(channelMchNo) + .map(this::toResultWithMeta) + .orElse(null); + } + + /// 保存或更新转账配置(一对一 upsert) + /// + /// transferScene / transferAppRefId 均允许为空(支持分步配置或清空), + /// 但发起转账时两者必须齐备, 由转账策略校验。 + @Transactional(rollbackFor = Exception.class) + public void saveOrUpdate(WechatTransferConfigParam param) { + // 校验通道商户存在与归属 + assertChannelMerchant(param.getMchNo(), param.getChannelMchNo()); + // 校验发起应用(若指定): 存在 + 归属 + 公众号类型 + if (param.getTransferAppRefId() != null) { + WxMchApp app = wxMchAppManager.lambdaQuery() + .eq(WxMchApp::getId, param.getTransferAppRefId()) + .oneOpt() + .orElseThrow(() -> new DataNotExistException("error.channel.wechat.transferAppNotExist")); + if (!Objects.equals(app.getMchNo(), param.getMchNo())) { + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, + "error.channel.wechat.transferAppNotBelong"); + } + if (!Objects.equals(app.getAppType(), WxAppTypeEnum.OFFICIAL_ACCOUNT.getCode())) { + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, + "error.channel.wechat.transferAppTypeNotOfficialAccount"); + } + } + // upsert: 存在则全量覆盖(含清空), 不存在则新增 + Optional existing = wechatTransferConfigManager + .findByChannelMchNo(param.getChannelMchNo()); + if (existing.isPresent()) { + WechatTransferConfig entity = existing.get(); + entity.setTransferScene(param.getTransferScene()); + entity.setTransferAppRefId(param.getTransferAppRefId()); + wechatTransferConfigManager.updateById(entity); + } else { + WechatTransferConfig entity = WechatTransferConfigConvert.CONVERT.toEntity(param); + // 运营端写 MchBaseEntity 必须显式 setMchNo(父类 setter 返回类型不匹配, 单独赋值) + entity.setMchNo(param.getMchNo()); + wechatTransferConfigManager.save(entity); + } + } + + /// 删除通道商户的转账配置(通道商户删除时级联清理) + public void deleteByChannelMchNo(String channelMchNo) { + wechatTransferConfigManager.deleteByChannelMchNo(channelMchNo); + } + + /// 校验通道商户存在且归属匹配 + private void assertChannelMerchant(String mchNo, String channelMchNo) { + WechatDirectChannelMerchant channelMerchant = wechatDirectChannelMerchantManager + .findByChannelMchNo(channelMchNo) + .orElseThrow(() -> new DataNotExistException("error.payment.channel.channelMerchantNotExist")); + if (!Objects.equals(channelMerchant.getMchNo(), mchNo)) { + throw new BizInfoException(CommonErrorCode.UN_SUPPORTED_OPERATE, + "error.payment.wx.channelMerchantMismatch"); + } + } + + /// 转Result并填充冗余展示(场景名 + 发起应用信息) + private WechatTransferConfigResult toResultWithMeta(WechatTransferConfig entity) { + WechatTransferConfigResult result = entity.toResult(); + // 场景名(枚举推导) + if (StrUtil.isNotBlank(entity.getTransferScene())) { + WechatTransferSceneEnum scene = WechatTransferSceneEnum.findByCode(entity.getTransferScene()); + if (scene != null) { + result.setSceneName(scene.getName()); + } + } + // 发起应用展示信息 + if (entity.getTransferAppRefId() != null) { + wxMchAppManager.lambdaQuery() + .eq(WxMchApp::getId, entity.getTransferAppRefId()) + .oneOpt() + .ifPresent(app -> { + result.setTransferAppName(app.getAppName()); + result.setWxAppId(app.getWxAppId()); + result.setAppType(app.getAppType()); + }); + } + return result; + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/strategy/direct/merchant/WechatDirectChannelMerchantCleanupStrategy.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/strategy/direct/merchant/WechatDirectChannelMerchantCleanupStrategy.java index 6e3a820f6..84b48fe0e 100644 --- a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/strategy/direct/merchant/WechatDirectChannelMerchantCleanupStrategy.java +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/strategy/direct/merchant/WechatDirectChannelMerchantCleanupStrategy.java @@ -2,6 +2,7 @@ package cn.daxpay.open.channel.wechat.strategy.direct.merchant; import cn.daxpay.open.channel.wechat.dao.direct.WechatDirectChannelMerchantManager; import cn.daxpay.open.channel.wechat.dao.direct.WechatDirectKeyConfigManager; +import cn.daxpay.open.channel.wechat.dao.direct.WechatTransferConfigManager; import cn.daxpay.open.channel.wechat.entity.direct.WechatDirectChannelMerchant; import cn.daxpay.open.channel.wechat.entity.direct.WechatDirectKeyConfig; import cn.daxpay.open.payment.wx.service.channel.WxChannelAppCapabilityService; @@ -16,6 +17,7 @@ import org.springframework.transaction.annotation.Transactional; /// /// 在通道商户删除时清理: /// - 直连扩展表 + 密钥配置 +/// - 微信转账配置(场景+发起应用) /// - 主数据通道能力绑([WxChannelAppCapabilityService#deleteByChannelMchNo]) /// /// 与 [cn.daxpay.open.channel.wechat.strategy.isv.merchant.WechatIsvChannelMerchantCleanupStrategy] @@ -28,6 +30,7 @@ public class WechatDirectChannelMerchantCleanupStrategy implements ChannelMercha private final WechatDirectChannelMerchantManager wechatDirectChannelMerchantManager; private final WechatDirectKeyConfigManager wechatDirectAppKeyConfigManager; + private final WechatTransferConfigManager wechatTransferConfigManager; private final WxChannelAppCapabilityService wxChannelAppCapabilityService; /// 对应产品: 微信支付直连 @@ -42,6 +45,8 @@ public class WechatDirectChannelMerchantCleanupStrategy implements ChannelMercha public void deleteByChannelMchNo(String channelMchNo) { wechatDirectChannelMerchantManager.deleteByField(WechatDirectChannelMerchant::getChannelMchNo, channelMchNo); wechatDirectAppKeyConfigManager.deleteByField(WechatDirectKeyConfig::getChannelMchNo, channelMchNo); + // 微信转账配置(场景+发起应用) + wechatTransferConfigManager.deleteByChannelMchNo(channelMchNo); // 主数据: 通道商户 × 能力绑 wxChannelAppCapabilityService.deleteByChannelMchNo(channelMchNo); } diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/strategy/direct/transfer/WechatTransferStrategy.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/strategy/direct/transfer/WechatTransferStrategy.java index 78c755a4c..f6a15a36d 100644 --- a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/strategy/direct/transfer/WechatTransferStrategy.java +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/strategy/direct/transfer/WechatTransferStrategy.java @@ -1,8 +1,8 @@ package cn.daxpay.open.channel.wechat.strategy.direct.transfer; import cn.daxpay.open.channel.wechat.client.credential.WechatSdkCredential; -import cn.daxpay.open.channel.wechat.dao.direct.WechatDirectChannelMerchantManager; -import cn.daxpay.open.channel.wechat.entity.direct.WechatDirectChannelMerchant; +import cn.daxpay.open.channel.wechat.dao.direct.WechatTransferConfigManager; +import cn.daxpay.open.channel.wechat.entity.direct.WechatTransferConfig; import cn.daxpay.open.channel.wechat.service.direct.WechatDirectConfigAssembler; import cn.daxpay.open.channel.wechat.service.payment.transfer.WechatTransferService; import cn.daxpay.open.payment.strategy.transfer.AbsTransferStrategy; @@ -24,7 +24,7 @@ import org.springframework.stereotype.Service; /// 通道差异: /// - 仅支持 openid 收款人([TransferPayeeTypeEnum#OPENID]) /// - 金额档位姓名校验: 小于 0.3 元禁填姓名, 大于等于 2000 元必填姓名 -/// - transfer_scene 取自通道商户配置, 未配置时报错 +/// - transfer_scene 取自「微信转账配置」([WechatTransferConfig]), 发起应用由配置指定(公众号) @Slf4j @Service @RequiredArgsConstructor @@ -37,7 +37,7 @@ public class WechatTransferStrategy extends AbsTransferStrategy { private final WechatTransferService wechatTransferService; private final WechatDirectConfigAssembler wechatDirectConfigAssembler; - private final WechatDirectChannelMerchantManager wechatDirectChannelMerchantManager; + private final WechatTransferConfigManager wechatTransferConfigManager; @Override public String getChannel() { @@ -83,20 +83,25 @@ public class WechatTransferStrategy extends AbsTransferStrategy { /// 组装通道调用凭证并注入转账场景 /// - /// 转账场景(transfer_scene)从通道商户配置读取, 经上下文回写, 由编排层在"处理中"镜像落库。 + /// 转账场景(transfer_scene)与发起应用均取自「微信转账配置」([WechatTransferConfig]), + /// 经上下文回写场景, 由编排层在"处理中"镜像落库; 发起应用由凭证组装器按引用解析 wxAppId。 private WechatSdkCredential buildCredential(TransferStrategyContext context) { - WechatDirectChannelMerchant channelMerchant = wechatDirectChannelMerchantManager.lambdaQuery() - .eq(WechatDirectChannelMerchant::getChannelMchNo, context.getChannelMchNo()) - .oneOpt() + WechatTransferConfig transferConfig = wechatTransferConfigManager + .findByChannelMchNo(context.getChannelMchNo()) .orElseThrow(() -> new BizInfoException(DaxPayErrorCode.CONFIG_NOT_EXIST, - "error.payment.channel.channelMerchantNotExist")); - if (StrUtil.isBlank(channelMerchant.getTransferScene())) { + "error.channel.wechat.transferConfigNotConfigured")); + if (StrUtil.isBlank(transferConfig.getTransferScene())) { // 微信: 转账场景未配置 throw new BizInfoException(DaxPayErrorCode.CONFIG_NOT_EXIST, "error.channel.wechat.transferSceneNotConfigured"); } - context.setTransferScene(channelMerchant.getTransferScene()); - return wechatDirectConfigAssembler.buildConfig( - context.getMchNo(), context.getChannelMchNo(), null, null); + if (transferConfig.getTransferAppRefId() == null) { + // 微信: 转账发起应用未配置 + throw new BizInfoException(DaxPayErrorCode.CONFIG_NOT_EXIST, + "error.channel.wechat.transferAppNotConfigured"); + } + context.setTransferScene(transferConfig.getTransferScene()); + return wechatDirectConfigAssembler.buildTransferConfig( + context.getChannelMchNo(), transferConfig.getTransferAppRefId()); } } diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/entity/WechatTransferOrder.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/entity/WechatTransferOrder.java index d148c913f..f4c0aa1fd 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/entity/WechatTransferOrder.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/entity/WechatTransferOrder.java @@ -73,9 +73,12 @@ public class WechatTransferOrder extends MchBaseEntity { /// 收款人微信 openid private String payeeOpenid; - /// 转账场景(冗余自通道商户配置) + /// 转账场景(冗余自转账配置) private String transferScene; + /// 转账发起应用AppId(从转账配置解析, openid归属校验/对账回查用) + private String wxAppId; + /// 拉起转账确认参数(微信二次确认) private String transferBody; diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/error/channel/wechat.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/error/channel/wechat.json index ea28e259a..51517b897 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/error/channel/wechat.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/en-US/error/channel/wechat.json @@ -61,6 +61,10 @@ "transferOnlyOpenid": "WeChat only supports openid payee", "transferNameForbidden": "WeChat: payee name not allowed for amount below 0.3 CNY", "transferNameRequired": "WeChat: payee name required for amount >= 2000 CNY", - - "transferSceneNotConfigured": "WeChat: transfer scene not configured" + "transferSceneNotConfigured": "WeChat: transfer scene not configured", + "transferConfigNotConfigured": "WeChat: Transfer config not configured", + "transferAppNotConfigured": "WeChat: Transfer initiator app not configured", + "transferAppNotExist": "WeChat: Transfer initiator app not found", + "transferAppNotBelong": "WeChat: Transfer initiator app does not belong to the current merchant", + "transferAppTypeNotOfficialAccount": "WeChat: Transfer initiator app must be an Official Account" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/error/channel/wechat.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/error/channel/wechat.json index 384948704..696bf957c 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/error/channel/wechat.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/id-ID/error/channel/wechat.json @@ -61,6 +61,10 @@ "transferOnlyOpenid": "WeChat hanya mendukung penerima openid", "transferNameForbidden": "WeChat: nama penerima tidak diizinkan untuk jumlah di bawah 0.3 CNY", "transferNameRequired": "WeChat: nama penerima wajib untuk jumlah >= 2000 CNY", - - "transferSceneNotConfigured": "WeChat: skenario transfer belum dikonfigurasi" + "transferSceneNotConfigured": "WeChat: skenario transfer belum dikonfigurasi", + "transferConfigNotConfigured": "WeChat: Konfigurasi transfer belum dikonfigurasi", + "transferAppNotConfigured": "WeChat: Aplikasi inisiator transfer belum dikonfigurasi", + "transferAppNotExist": "WeChat: Aplikasi inisiator transfer tidak ditemukan", + "transferAppNotBelong": "WeChat: Aplikasi inisiator transfer bukan milik merchant saat ini", + "transferAppTypeNotOfficialAccount": "WeChat: Aplikasi inisiator transfer harus berjenis Official Account" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/error/channel/wechat.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/error/channel/wechat.json index a45ed2a1f..d2ac13a6b 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/error/channel/wechat.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ja-JP/error/channel/wechat.json @@ -61,6 +61,10 @@ "transferOnlyOpenid": "WeChatはopenid受取人のみサポートしています", "transferNameForbidden": "WeChat: 0.3元未満は受取人氏名を入力できません", "transferNameRequired": "WeChat: 2000元以上は受取人氏名が必須です", - - "transferSceneNotConfigured": "WeChat: 送金シーンが設定されていません" + "transferSceneNotConfigured": "WeChat: 送金シーンが設定されていません", + "transferConfigNotConfigured": "WeChat: 振込設定が未設定です", + "transferAppNotConfigured": "WeChat: 振込发起アプリが未設定です", + "transferAppNotExist": "WeChat: 振込发起アプリが見つかりません", + "transferAppNotBelong": "WeChat: 振込发起アプリは現在のマーチャントに属していません", + "transferAppTypeNotOfficialAccount": "WeChat: 振込发起アプリは公式アカウントタイプである必要があります" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/error/channel/wechat.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/error/channel/wechat.json index 54765abf0..c344b9540 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/error/channel/wechat.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ko-KR/error/channel/wechat.json @@ -61,6 +61,10 @@ "transferOnlyOpenid": "위챗은 openid 수취인만 지원합니다", "transferNameForbidden": "위챗: 0.3원 미만은 수취인 이름을 입력할 수 없습니다", "transferNameRequired": "위챗: 2000원 이상은 수취인 이름이 필수입니다", - - "transferSceneNotConfigured": "WeChat: 송금 장면이 구성되지 않았습니다" + "transferSceneNotConfigured": "WeChat: 송금 장면이 구성되지 않았습니다", + "transferConfigNotConfigured": "WeChat: 이체 설정이 구성되지 않았습니다", + "transferAppNotConfigured": "WeChat: 이체 발신 앱이 구성되지 않았습니다", + "transferAppNotExist": "WeChat: 이체 발신 앱을 찾을 수 없습니다", + "transferAppNotBelong": "WeChat: 이체 발신 앱이 현재 가맹점에 속하지 않습니다", + "transferAppTypeNotOfficialAccount": "WeChat: 이체 발신 앱은 공식계정 유형이어야 합니다" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/error/channel/wechat.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/error/channel/wechat.json index 602c3a212..f39ceef48 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/error/channel/wechat.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/ms-MY/error/channel/wechat.json @@ -61,6 +61,10 @@ "transferOnlyOpenid": "WeChat hanya menyokong penerima openid", "transferNameForbidden": "WeChat: nama penerima tidak dibenarkan untuk jumlah di bawah 0.3 CNY", "transferNameRequired": "WeChat: nama penerima wajib untuk jumlah >= 2000 CNY", - - "transferSceneNotConfigured": "WeChat: senario pindahan belum dikonfigurasi" + "transferSceneNotConfigured": "WeChat: senario pindahan belum dikonfigurasi", + "transferConfigNotConfigured": "WeChat: Konfigurasi pemindahan belum dikonfigurasi", + "transferAppNotConfigured": "WeChat: Aplikasi pemula pemindahan belum dikonfigurasi", + "transferAppNotExist": "WeChat: Aplikasi pemula pemindahan tidak dijumpai", + "transferAppNotBelong": "WeChat: Aplikasi pemula pemindahan tidak milik merchant semasa", + "transferAppTypeNotOfficialAccount": "WeChat: Aplikasi pemula pemindahan mesti jenis Official Account" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/error/channel/wechat.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/error/channel/wechat.json index 74e6f0de5..785a8c34b 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/error/channel/wechat.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/th-TH/error/channel/wechat.json @@ -61,6 +61,10 @@ "transferOnlyOpenid": "WeChat รองรับผู้รับ openid เท่านั้น", "transferNameForbidden": "WeChat: ไม่อนุญาตให้ใส่ชื่อผู้รับเมื่อต่ำกว่า 0.3 หยวน", "transferNameRequired": "WeChat: ต้องใส่ชื่อผู้รับเมื่อมากกว่าหรือเท่ากับ 2000 หยวน", - - "transferSceneNotConfigured": "WeChat: ยังไม่ได้กำหนดสถานการณ์การโอนเงิน" + "transferSceneNotConfigured": "WeChat: ยังไม่ได้กำหนดสถานการณ์การโอนเงิน", + "transferConfigNotConfigured": "WeChat: ยังไม่ได้กำหนดค่าการโอนเงิน", + "transferAppNotConfigured": "WeChat: ยังไม่ได้กำหนดค่าแอปเริ่มการโอน", + "transferAppNotExist": "WeChat: ไม่พบแอปเริ่มการโอน", + "transferAppNotBelong": "WeChat: แอปเริ่มการโอนไม่ได้เป็นของร้านค้าปัจจุบัน", + "transferAppTypeNotOfficialAccount": "WeChat: แอปเริ่มการโอนต้องเป็นประเภท Official Account" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/error/channel/wechat.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/error/channel/wechat.json index 7684373ed..b3ea49c5e 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/error/channel/wechat.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/vi-VN/error/channel/wechat.json @@ -61,6 +61,10 @@ "transferOnlyOpenid": "WeChat chỉ hỗ trợ người nhận openid", "transferNameForbidden": "WeChat: không được nhập tên người nhận khi dưới 0.3 CNY", "transferNameRequired": "WeChat: bắt buộc nhập tên người nhận khi >= 2000 CNY", - - "transferSceneNotConfigured": "WeChat: chưa cấu hình kịch bản chuyển khoản" + "transferSceneNotConfigured": "WeChat: chưa cấu hình kịch bản chuyển khoản", + "transferConfigNotConfigured": "WeChat: Cấu hình chuyển khoản chưa được thiết lập", + "transferAppNotConfigured": "WeChat: Ứng dụng khởi tạo chuyển khoản chưa được thiết lập", + "transferAppNotExist": "WeChat: Không tìm thấy ứng dụng khởi tạo chuyển khoản", + "transferAppNotBelong": "WeChat: Ứng dụng khởi tạo chuyển khoản không thuộc merchant hiện tại", + "transferAppTypeNotOfficialAccount": "WeChat: Ứng dụng khởi tạo chuyển khoản phải là loại Official Account" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/error/channel/wechat.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/error/channel/wechat.json index 56a209234..67366ac7e 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/error/channel/wechat.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-CN/error/channel/wechat.json @@ -61,6 +61,10 @@ "transferOnlyOpenid": "微信仅支持 openid 收款人", "transferNameForbidden": "微信: 小于0.3元不允许填收款人姓名", "transferNameRequired": "微信: 大于等于2000元必须填收款人姓名", - - "transferSceneNotConfigured": "微信: 转账场景未配置" + "transferSceneNotConfigured": "微信: 转账场景未配置", + "transferConfigNotConfigured": "微信: 转账配置未配置", + "transferAppNotConfigured": "微信: 转账发起应用未配置", + "transferAppNotExist": "微信: 转账发起应用不存在", + "transferAppNotBelong": "微信: 转账发起应用不属于当前商户", + "transferAppTypeNotOfficialAccount": "微信: 转账发起应用必须是公众号类型" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/error/channel/wechat.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/error/channel/wechat.json index 0f7cc4651..b9530237a 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/error/channel/wechat.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-HK/error/channel/wechat.json @@ -61,6 +61,10 @@ "transferOnlyOpenid": "微信僅支持 openid 收款人", "transferNameForbidden": "微信: 少於0.3元不允許填收款人姓名", "transferNameRequired": "微信: 大於等於2000元必須填收款人姓名", - - "transferSceneNotConfigured": "微信: 轉賬場景未設定" + "transferSceneNotConfigured": "微信: 轉賬場景未設定", + "transferConfigNotConfigured": "微信: 轉帳設定未設定", + "transferAppNotConfigured": "微信: 轉帳發起應用未設定", + "transferAppNotExist": "微信: 轉帳發起應用不存在", + "transferAppNotBelong": "微信: 轉帳發起應用不屬於目前商戶", + "transferAppTypeNotOfficialAccount": "微信: 轉帳發起應用必須是公眾號類型" } diff --git a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/error/channel/wechat.json b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/error/channel/wechat.json index 8b0c37037..201785824 100644 --- a/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/error/channel/wechat.json +++ b/daxpay-platform/daxpay-platform-common/common-i18n/src/main/resources/i18n/zh-TW/error/channel/wechat.json @@ -61,6 +61,10 @@ "transferOnlyOpenid": "微信僅支持 openid 收款人", "transferNameForbidden": "微信: 小於0.3元不允許填收款人姓名", "transferNameRequired": "微信: 大於等於2000元必須填收款人姓名", - - "transferSceneNotConfigured": "微信: 轉帳場景未設定" + "transferSceneNotConfigured": "微信: 轉帳場景未設定", + "transferConfigNotConfigured": "微信: 轉帳設定未設定", + "transferAppNotConfigured": "微信: 轉帳發起應用未設定", + "transferAppNotExist": "微信: 轉帳發起應用不存在", + "transferAppNotBelong": "微信: 轉帳發起應用不屬於目前商戶", + "transferAppTypeNotOfficialAccount": "微信: 轉帳發起應用必須是公眾號類型" }