From 824b3f1fa6d075eb4a4173d31974b5a868c937a5 Mon Sep 17 00:00:00 2001 From: daxpay Date: Fri, 7 Aug 2026 16:39:12 +0800 Subject: [PATCH] =?UTF-8?q?feat(channel-wechat):=20=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E8=BD=AC=E8=B4=A6=E7=94=B3=E8=AF=B7=E9=85=8D=E5=A5=97(?= =?UTF-8?q?=E6=8A=A5=E5=A4=87=E5=85=9C=E5=BA=95/=E7=A1=AE=E8=AE=A4?= =?UTF-8?q?=E6=94=B6=E6=AC=BE=E6=9F=A5=E8=AF=A2/=E6=A0=87=E9=A2=98?= =?UTF-8?q?=E4=B8=8E=E6=8A=A5=E5=A4=87=E6=A0=A1=E9=AA=8C)=20+=20=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E7=A0=81=E8=AF=8D=E6=9D=A1(10=E8=AF=AD)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wechat/client/req/WechatTransferReq.java | 6 ++ .../WechatTransferConfirmController.java | 38 ++++++++ ...WechatDirectChannelMerchantController.java | 19 ++++ .../wechat/enums/WechatTransferSceneEnum.java | 94 +++++++++++++++++++ ...echatDirectChannelMerchantUpdateParam.java | 31 ++++++ .../direct/WechatTransferConfirmResult.java | 36 +++++++ .../WechatTransferSceneOptionResult.java | 35 +++++++ .../WechatDirectChannelMerchantService.java | 42 +++++++++ .../WechatTransferConfirmQueryService.java | 60 ++++++++++++ .../transfer/WechatTransferService.java | 26 +++++ .../transfer/WechatTransferStrategy.java | 16 ++++ .../dao/WechatTransferOrderManager.java | 7 ++ .../i18n/en-US/error/channel/wechat.json | 4 +- .../i18n/id-ID/error/channel/wechat.json | 4 +- .../i18n/ja-JP/error/channel/wechat.json | 4 +- .../i18n/ko-KR/error/channel/wechat.json | 4 +- .../i18n/ms-MY/error/channel/wechat.json | 4 +- .../i18n/th-TH/error/channel/wechat.json | 4 +- .../i18n/vi-VN/error/channel/wechat.json | 4 +- .../i18n/zh-CN/error/channel/wechat.json | 4 +- .../i18n/zh-HK/error/channel/wechat.json | 4 +- .../i18n/zh-TW/error/channel/wechat.json | 4 +- 22 files changed, 440 insertions(+), 10 deletions(-) create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/controller/callback/WechatTransferConfirmController.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/enums/WechatTransferSceneEnum.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/param/direct/WechatDirectChannelMerchantUpdateParam.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/direct/WechatTransferConfirmResult.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/direct/WechatTransferSceneOptionResult.java create mode 100644 daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/payment/transfer/WechatTransferConfirmQueryService.java diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/client/req/WechatTransferReq.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/client/req/WechatTransferReq.java index 01ecabf42..619642fcc 100644 --- a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/client/req/WechatTransferReq.java +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/client/req/WechatTransferReq.java @@ -1,8 +1,11 @@ package cn.daxpay.open.channel.wechat.client.req; import cn.daxpay.open.channel.wechat.client.credential.WechatSdkCredential; +import cn.daxpay.open.payment.trade.transfer.param.TransferReportInfo; import lombok.Data; +import java.util.List; + /// # 微信通道转账请求(发起/同步共用) /// /// 与子应用 dax-pay-channel-one 的 `WechatTransferReq` 镜像, 字段对齐。 @@ -34,6 +37,9 @@ public class WechatTransferReq { /// 异步通知地址(微信→平台) private String notifyUrl; + /// 转账场景报备信息(发起时必填, 同步可空) + private List reportInfos; + /// 通道调用凭证 private WechatSdkCredential credential; } diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/controller/callback/WechatTransferConfirmController.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/controller/callback/WechatTransferConfirmController.java new file mode 100644 index 000000000..bcf627eca --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/controller/callback/WechatTransferConfirmController.java @@ -0,0 +1,38 @@ +package cn.daxpay.open.channel.wechat.controller.callback; + +import cn.daxpay.open.channel.wechat.result.direct.WechatTransferConfirmResult; +import cn.daxpay.open.channel.wechat.service.payment.transfer.WechatTransferConfirmQueryService; +import cn.daxpay.open.platform.core.annotation.IgnoreAuth; +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.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/// # 微信转账确认收款(公开接口) +/// +/// C 端收款人(无登录态)凭 transferNo 查询确认收款信息, 用于在微信内拉起收款确认页。 +/// 与回调控制器同为 @IgnoreAuth, 不走 Sa-Token 认证。 +@IgnoreAuth +@Validated +@Tag(name = "微信转账确认收款(公开)") +@RestController +@RequestMapping("/unipay/transfer/wechat") +@RequiredArgsConstructor +public class WechatTransferConfirmController { + + private final WechatTransferConfirmQueryService wechatTransferConfirmQueryService; + + @Operation(summary = "查询微信转账确认收款信息") + @GetMapping("/confirm-info/{transferNo}") + public Result getConfirmInfo( + @PathVariable @NotBlank(message = "转账单号不可为空") String transferNo) { + return Res.ok(wechatTransferConfirmQueryService.queryByTransferNo(transferNo)); + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/controller/direct/WechatDirectChannelMerchantController.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/controller/direct/WechatDirectChannelMerchantController.java index 507cbd7e3..3b22eeb89 100644 --- a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/controller/direct/WechatDirectChannelMerchantController.java +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/controller/direct/WechatDirectChannelMerchantController.java @@ -5,9 +5,11 @@ import cn.daxpay.open.platform.core.annotation.PermCode; import cn.daxpay.open.platform.core.rest.Res; import cn.daxpay.open.platform.core.rest.result.Result; import cn.daxpay.open.channel.wechat.param.direct.WechatDirectChannelMerchantCreateParam; +import cn.daxpay.open.channel.wechat.param.direct.WechatDirectChannelMerchantUpdateParam; import cn.daxpay.open.channel.wechat.param.direct.WechatDirectKeyConfigParam; import cn.daxpay.open.channel.wechat.result.direct.WechatDirectChannelMerchantResult; import cn.daxpay.open.channel.wechat.result.direct.WechatDirectKeyConfigResult; +import cn.daxpay.open.channel.wechat.result.direct.WechatTransferSceneOptionResult; import cn.daxpay.open.channel.wechat.service.direct.WechatDirectChannelMerchantService; import cn.daxpay.open.channel.wechat.service.direct.WechatDirectKeyConfigService; import io.swagger.v3.oas.annotations.Operation; @@ -21,6 +23,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) @@ -50,6 +54,21 @@ public class WechatDirectChannelMerchantController { return Res.ok(); } + @PermCode(code = PermCodes.Action.MANAGE) + @Operation(summary = "更新微信直连通道商户(转账场景/微信商户号)") + @PostMapping("/update") + public Result update(@RequestBody @Validated WechatDirectChannelMerchantUpdateParam param) { + wechatDirectChannelMerchantService.update(param); + return Res.ok(); + } + + @PermCode(code = PermCodes.Action.VIEW) + @Operation(summary = "查询微信转账场景选项列表") + @GetMapping("/scene-options") + public Result> sceneOptions() { + return Res.ok(wechatDirectChannelMerchantService.findSceneOptions()); + } + @PermCode(code = PermCodes.Action.VIEW) @Operation(summary = "根据通道商户号查询密钥配置") @GetMapping("/find-key-config") diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/enums/WechatTransferSceneEnum.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/enums/WechatTransferSceneEnum.java new file mode 100644 index 000000000..bf2888130 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/enums/WechatTransferSceneEnum.java @@ -0,0 +1,94 @@ +package cn.daxpay.open.channel.wechat.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.List; + +/// # 微信转账场景枚举 +/// +/// 对应微信商家转账(新版 fund-app/mch-transfer)的转账场景, 场景ID 由商户在微信商户平台 +/// 「产品中心-商家转账」申请开通。每个场景要求不同的报备字段([reportInfoTypes]), +/// 字段值为微信协议固定的中文 [infoType], 不可更改。 +/// +/// 报备字段内容(infoContent)由商户在发起转账时填写, 留空用 `-` 兜底(1009 采购货款用空串)。 +/// +/// [reportInfoDescriptions] 与 [reportInfoTypes] 一一平行, 描述每个字段的含义和微信文档示例。 +/// [userRecvPerceptionOptions] 为收款人在微信中看到的感知文案可选值, 不传时微信按场景取默认(第一个)。 +@Getter +@AllArgsConstructor +public enum WechatTransferSceneEnum { + + /// 现金营销 + CASH_MARKETING("1000", "现金营销", + List.of("活动名称", "奖励说明"), + List.of("商户自定义内容,如「新会员有礼」", "商户自定义内容,如「注册会员抽奖一等奖」"), + List.of("现金奖励", "活动奖励")), + + /// 行政补贴 + ADMINISTRATIVE_SUBSIDY("1002", "行政补贴", + List.of("补贴类型"), + List.of("商户自定义内容,如「购车补贴」"), + List.of("行政补贴", "行政奖励")), + + /// 保险理赔 + INSURANCE_CLAIM("1004", "保险理赔", + List.of("保险产品备案编号", "保险名称", "保险操作单号"), + List.of("保险产品备案编号,如「01212121212」", "保险名称,如「意外险」", "保险操作单号,如「12121245442」"), + List.of("保险理赔款")), + + /// 佣金报酬 + COMMISSION_REWARD("1005", "佣金报酬", + List.of("岗位类型", "报酬说明"), + List.of("商户自定义内容,如「外卖员」", "商户自定义内容,如「7月份配送费」"), + List.of("劳务报酬", "报销款", "企业补贴", "开工利是")), + + /// 采购货款 + PROCUREMENT_PAYMENT("1009", "采购货款", + List.of("采购商品名称"), + List.of("商户自定义内容,如「戴尔笔记本电脑」"), + List.of("货款")), + + /// 二手回收 + SECONDHAND_RECYCLING("1010", "二手回收", + List.of("回收商品名称"), + List.of("商户自定义内容,如「塑料瓶」"), + List.of("二手回收货款")), + + /// 企业赔付 + ENTERPRISE_COMPENSATION("1011", "企业赔付", + List.of("赔付原因"), + List.of("商户自定义内容,如「商品质量问题退款」"), + List.of("退款", "商家赔付")), + + /// 公益补助 + PUBLIC_WELFARE_SUBSIDY("1013", "公益补助", + List.of("公益活动名称", "公益活动备案编号"), + List.of("请填写在民政部的备案名称", "请填写在民政部的备案编号"), + List.of("公益补助金")); + + /// 转账场景ID + private final String code; + + /// 场景名称 + private final String name; + + /// 报备字段定义(微信协议固定中文 infoType, 顺序即 infoContentList 下标) + private final List reportInfoTypes; + + /// 报备字段说明(与 reportInfoTypes 平行, 描述字段含义和微信文档示例) + private final List reportInfoDescriptions; + + /// 用户收款感知可选值(收款人在微信中看到的文案, 不传时取第一个为默认) + private final List userRecvPerceptionOptions; + + /// 根据场景ID 查找枚举 + public static WechatTransferSceneEnum findByCode(String code) { + for (WechatTransferSceneEnum scene : values()) { + if (scene.code.equals(code)) { + return scene; + } + } + return null; + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/param/direct/WechatDirectChannelMerchantUpdateParam.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/param/direct/WechatDirectChannelMerchantUpdateParam.java new file mode 100644 index 000000000..05d1135ad --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/param/direct/WechatDirectChannelMerchantUpdateParam.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 jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.experimental.Accessors; + +/// # 微信直连通道商户更新参数 +/// +/// 当前仅支持更新转账场景与微信商户号, 通道商户号创建后不可变。 +@Data +@Accessors(chain = true) +@Schema(title = "微信直连通道商户更新参数") +public class WechatDirectChannelMerchantUpdateParam { + + /// 通道商户号(定位用, 不可变) + @Schema(description = "通道商户号") + @NotBlank(message = "{validation.field.channelMerchantNo.notBlank}") + private String channelMchNo; + + /// 微信直连商户号 + @Schema(description = "微信直连商户号") + @Size(max = 32, message = "微信直连商户号不可超过32位") + private String wxMchId; + + /// 转账场景ID(商家转账到零钱, 微信转账时必填) + /// @see cn.daxpay.open.channel.wechat.enums.WechatTransferSceneEnum + @Schema(description = "转账场景ID") + private String transferScene; +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/direct/WechatTransferConfirmResult.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/direct/WechatTransferConfirmResult.java new file mode 100644 index 000000000..67f0f5e6f --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/direct/WechatTransferConfirmResult.java @@ -0,0 +1,36 @@ +package cn.daxpay.open.channel.wechat.result.direct; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.experimental.Accessors; + +/// # 微信转账确认收款信息 +/// +/// 供 C 端收款人在微信内拉起确认收款页(`WeixinJSBridge.invoke('requestMerchantTransfer')`)使用。 +/// [mchId]/[appId]/[packageInfo] 为拉起 JSAPI 必需参数, 全部由后端从订单与通道配置反查返回。 +@Data +@Accessors(chain = true) +@Schema(title = "微信转账确认收款信息") +public class WechatTransferConfirmResult { + + @Schema(description = "微信商户号(拉起 requestMerchantTransfer 用)") + private String mchId; + + @Schema(description = "商户 AppID(拉起 requestMerchantTransfer 用)") + private String appId; + + @Schema(description = "拉起确认参数 package_info") + private String packageInfo; + + @Schema(description = "转账金额(分)") + private Long amount; + + @Schema(description = "转账标题") + private String title; + + @Schema(description = "转账状态") + private String status; + + @Schema(description = "是否已终态(不可再操作)") + private boolean received; +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/direct/WechatTransferSceneOptionResult.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/direct/WechatTransferSceneOptionResult.java new file mode 100644 index 000000000..3dc674c7a --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/result/direct/WechatTransferSceneOptionResult.java @@ -0,0 +1,35 @@ +package cn.daxpay.open.channel.wechat.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 WechatTransferSceneOptionResult { + + @Schema(description = "转账场景ID") + private String code; + + @Schema(description = "场景名称") + private String name; + + @Schema(description = "报备字段定义(微信协议固定中文 infoType)") + private List reportInfoTypes; + + @Schema(description = "报备字段说明(与 reportInfoTypes 平行, 含微信文档示例)") + private List reportInfoDescriptions; + + @Schema(description = "用户收款感知可选值(收款人在微信中看到的文案)") + private List userRecvPerceptionOptions; +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/direct/WechatDirectChannelMerchantService.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/direct/WechatDirectChannelMerchantService.java index 6ce901a49..a08b8aa4a 100644 --- a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/direct/WechatDirectChannelMerchantService.java +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/direct/WechatDirectChannelMerchantService.java @@ -2,8 +2,11 @@ package cn.daxpay.open.channel.wechat.service.direct; import cn.daxpay.open.channel.wechat.dao.direct.WechatDirectChannelMerchantManager; import cn.daxpay.open.channel.wechat.entity.direct.WechatDirectChannelMerchant; +import cn.daxpay.open.channel.wechat.enums.WechatTransferSceneEnum; import cn.daxpay.open.channel.wechat.param.direct.WechatDirectChannelMerchantCreateParam; +import cn.daxpay.open.channel.wechat.param.direct.WechatDirectChannelMerchantUpdateParam; import cn.daxpay.open.channel.wechat.result.direct.WechatDirectChannelMerchantResult; +import cn.daxpay.open.channel.wechat.result.direct.WechatTransferSceneOptionResult; import cn.daxpay.open.channel.wechat.strategy.direct.merchant.WechatDirectChannelMerchantCleanupStrategy; import cn.daxpay.open.payment.merchant.dao.channel.ChannelMerchantManager; import cn.daxpay.open.payment.masterdata.dao.product.PayProductConfigManager; @@ -18,6 +21,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; + /// # 微信直连通道商户管理 /// /// 一个微信商户号(wxMchId)对应一个 channelMchNo, 商户的多个应用共享此绑定。 @@ -75,4 +81,40 @@ public class WechatDirectChannelMerchantService { // 微信: 通道商户配置不存在 .orElseThrow(() -> new DataNotExistException("error.payment.channel.channelMerchantNotExist")); } + + /// 更新微信直连通道商户(转账场景/微信商户号) + @Transactional(rollbackFor = Exception.class) + public void update(WechatDirectChannelMerchantUpdateParam param) { + WechatDirectChannelMerchant entity = wechatDirectChannelMerchantManager.lambdaQuery() + .eq(WechatDirectChannelMerchant::getChannelMchNo, param.getChannelMchNo()) + .oneOpt() + // 微信: 通道商户配置不存在 + .orElseThrow(() -> new DataNotExistException("error.payment.channel.channelMerchantNotExist")); + // 微信商户号变更时校验同一商户下不重复 + if (param.getWxMchId() != null && !param.getWxMchId().equals(entity.getWxMchId())) { + if (wechatDirectChannelMerchantManager.existsByMchNoAndWxMchId( + entity.getMchNo(), param.getWxMchId())) { + // 微信: 同一商户下该微信商户已存在 + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "error.channel.wechat.directMchDuplicate"); + } + entity.setWxMchId(param.getWxMchId()); + } + // 转账场景(允许清空, 清空后发起转账会报"场景未配置") + if (param.getTransferScene() != null) { + entity.setTransferScene(param.getTransferScene()); + } + wechatDirectChannelMerchantManager.updateById(entity); + } + + /// 查询微信转账场景选项列表(供前端下拉与报备字段动态渲染) + public List findSceneOptions() { + return Arrays.stream(WechatTransferSceneEnum.values()) + .map(scene -> new WechatTransferSceneOptionResult() + .setCode(scene.getCode()) + .setName(scene.getName()) + .setReportInfoTypes(scene.getReportInfoTypes()) + .setReportInfoDescriptions(scene.getReportInfoDescriptions()) + .setUserRecvPerceptionOptions(scene.getUserRecvPerceptionOptions())) + .toList(); + } } diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/payment/transfer/WechatTransferConfirmQueryService.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/payment/transfer/WechatTransferConfirmQueryService.java new file mode 100644 index 000000000..e2fad0083 --- /dev/null +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/payment/transfer/WechatTransferConfirmQueryService.java @@ -0,0 +1,60 @@ +package cn.daxpay.open.channel.wechat.service.payment.transfer; + +import cn.daxpay.open.channel.wechat.client.credential.WechatSdkCredential; +import cn.daxpay.open.channel.wechat.result.direct.WechatTransferConfirmResult; +import cn.daxpay.open.channel.wechat.service.direct.WechatDirectConfigAssembler; +import cn.daxpay.open.payment.common.context.MerchantContextLoader; +import cn.daxpay.open.payment.trade.transfer.dao.WechatTransferOrderManager; +import cn.daxpay.open.payment.trade.transfer.entity.WechatTransferOrder; +import cn.daxpay.open.platform.core.code.CommonErrorCode; +import cn.daxpay.open.platform.core.exception.BizInfoException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.Set; + +/// # 微信转账确认收款查询服务 +/// +/// 供 C 端收款人(无登录态)凭 transferNo 查询确认收款信息。 +/// 跨租户引导读订单([WechatTransferOrderManager#findByTransferNoNotTenant]) → +/// 装载商户上下文 → 通道凭证组装获取 wxMchId/wxAppId → 返回 packageInfo 等拉起参数。 +/// +/// 安全模型: 凭 transferNo 高熵不可猜防枚举, 仅返回该订单的公开收款信息。 +@Slf4j +@Service +@RequiredArgsConstructor +public class WechatTransferConfirmQueryService { + + /// 终态状态(不可再确认收款) + private static final Set TERMINAL_STATES = Set.of("success", "close", "fail"); + + private final WechatTransferOrderManager wechatTransferOrderManager; + private final MerchantContextLoader merchantContextLoader; + private final WechatDirectConfigAssembler wechatDirectConfigAssembler; + + /// 查询确认收款信息 + /// + /// @param transferNo 平台转账单号(URL 路径参数) + /// @return 确认收款信息(mchId/appId/packageInfo 等) + public WechatTransferConfirmResult queryByTransferNo(String transferNo) { + // 跨租户查订单(收款人 C 端无商户上下文) + WechatTransferOrder order = wechatTransferOrderManager.findByTransferNoNotTenant(transferNo) + .orElseThrow(() -> new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, + "pay.error.transfer.notFound")); + // 装载商户上下文, 后续通道配置查询走租户隔离 + merchantContextLoader.initMch(order.getMchNo()); + // 通道凭证组装(wxMchId + wxAppId) + WechatSdkCredential credential = wechatDirectConfigAssembler.buildConfig( + order.getMchNo(), order.getChannelMchNo(), null, null); + boolean terminal = TERMINAL_STATES.contains(order.getStatus()); + return new WechatTransferConfirmResult() + .setMchId(credential.getWxMchId()) + .setAppId(credential.getWxAppId()) + .setPackageInfo(terminal ? null : order.getTransferBody()) + .setAmount(order.getAmount()) + .setTitle(order.getTitle()) + .setStatus(order.getStatus()) + .setReceived(terminal); + } +} diff --git a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/payment/transfer/WechatTransferService.java b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/payment/transfer/WechatTransferService.java index f29adc81c..f65691fbf 100644 --- a/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/payment/transfer/WechatTransferService.java +++ b/daxpay-channel/daxpay-channel-wechat/src/main/java/cn/daxpay/open/channel/wechat/service/payment/transfer/WechatTransferService.java @@ -4,10 +4,12 @@ import cn.daxpay.open.channel.wechat.client.WechatChannelClient; import cn.daxpay.open.channel.wechat.client.credential.WechatSdkCredential; import cn.daxpay.open.channel.wechat.client.req.WechatTransferReq; import cn.daxpay.open.channel.wechat.client.resp.WechatTransferResp; +import cn.daxpay.open.channel.wechat.enums.WechatTransferSceneEnum; import cn.daxpay.open.payment.common.result.DaxResult; import cn.daxpay.open.payment.strategy.transfer.TransferStrategyContext; import cn.daxpay.open.payment.trade.enums.PayFundStatusEnum; import cn.daxpay.open.payment.trade.transfer.bo.TransferResultBo; +import cn.daxpay.open.payment.trade.transfer.param.TransferReportInfo; import cn.daxpay.open.platform.core.code.DaxPayErrorCode; import cn.daxpay.open.platform.core.exception.BizInfoException; import cn.daxpay.open.platform.system.service.config.infra.PlatformUrlConfigService; @@ -16,6 +18,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -54,6 +57,7 @@ public class WechatTransferService { req.setUserName(context.getUserName()); req.setRemark(StrUtil.sub(context.getTitle(), 0, 32)); req.setNotifyUrl(this.buildNotifyUrl(context)); + req.setReportInfos(this.ensureReportInfos(context)); req.setCredential(credential); DaxResult result = wechatChannelClient.transfer(req); @@ -121,4 +125,26 @@ public class WechatTransferService { return StrUtil.format("{}/unipay/callback/{}/{}/wechat/transfer", base, context.getMchNo(), context.getChannelMchNo()); } + + /// 确保报备信息非空 + /// + /// 微信 `/transfer-bills` 的 `transfer_scene_report_infos` 为必填。 + /// 商户传入的 [TransferStrategyContext#getReportInfos] 非空时直接透传; + /// 为空时按场景枚举 [WechatTransferSceneEnum] 的报备字段模板构建默认值(`-`)。 + private List ensureReportInfos(TransferStrategyContext context) { + List reportInfos = context.getReportInfos(); + if (reportInfos != null && !reportInfos.isEmpty()) { + return reportInfos; + } + // 兜底: 按场景枚举构建默认报备信息 + WechatTransferSceneEnum scene = WechatTransferSceneEnum.findByCode(context.getTransferScene()); + if (scene == null) { + return reportInfos; + } + List defaults = new ArrayList<>(); + for (String infoType : scene.getReportInfoTypes()) { + defaults.add(new TransferReportInfo().setInfoType(infoType).setInfoContent("-")); + } + return defaults; + } } 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 f6a15a36d..2c0b14f44 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 @@ -10,6 +10,7 @@ import cn.daxpay.open.payment.strategy.transfer.TransferStrategyContext; 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.payment.trade.transfer.param.TransferReportInfo; import cn.daxpay.open.platform.core.code.CommonErrorCode; import cn.daxpay.open.platform.core.code.DaxPayErrorCode; import cn.daxpay.open.platform.core.exception.BizInfoException; @@ -18,12 +19,15 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.util.List; + /// # 微信直连转账策略 /// /// 微信商家转账到零钱(V3)的通道策略。 /// 通道差异: /// - 仅支持 openid 收款人([TransferPayeeTypeEnum#OPENID]) /// - 金额档位姓名校验: 小于 0.3 元禁填姓名, 大于等于 2000 元必填姓名 +/// - 转账备注(标题)与场景报备信息必填(微信接口 transfer_remark / transfer_scene_report_infos) /// - transfer_scene 取自「微信转账配置」([WechatTransferConfig]), 发起应用由配置指定(公众号) @Slf4j @Service @@ -53,6 +57,18 @@ public class WechatTransferStrategy extends AbsTransferStrategy { throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, "error.channel.wechat.transferOnlyOpenid"); } + // 微信: 转账备注(标题)必填, 对应接口 transfer_remark + if (StrUtil.isBlank(param.getTitle())) { + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, + "error.channel.wechat.transferRemarkRequired"); + } + // 微信: 转账场景报备信息必填(接口 transfer_scene_report_infos 必填, 且 info_content 不可为空) + List reportInfos = param.getReportInfos(); + if (reportInfos == null || reportInfos.isEmpty() + || reportInfos.stream().anyMatch(info -> StrUtil.isBlank(info.getInfoContent()))) { + throw new BizInfoException(CommonErrorCode.VALIDATE_PARAMETERS_ERROR, + "error.channel.wechat.transferReportInfoRequired"); + } long amountFen = param.getAmount().movePointRight(2).longValue(); String payeeName = param.getPayeeName(); if (amountFen < SMALL_AMOUNT_LIMIT && StrUtil.isNotBlank(payeeName)) { diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/dao/WechatTransferOrderManager.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/dao/WechatTransferOrderManager.java index 65e79c13e..006f03bd7 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/dao/WechatTransferOrderManager.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/dao/WechatTransferOrderManager.java @@ -3,6 +3,7 @@ package cn.daxpay.open.payment.trade.transfer.dao; import cn.daxpay.open.platform.common.mybatisplus.impl.BaseManager; import cn.daxpay.open.platform.common.mybatisplus.query.generator.QueryGenerator; import cn.daxpay.open.platform.common.mybatisplus.util.MpUtil; +import cn.daxpay.open.platform.core.annotation.IgnoreTenant; import cn.daxpay.open.platform.core.rest.param.PageParam; import cn.daxpay.open.payment.trade.transfer.entity.WechatTransferOrder; import cn.daxpay.open.payment.trade.transfer.param.WechatTransferOrderQuery; @@ -24,6 +25,12 @@ public class WechatTransferOrderManager return findByField(WechatTransferOrder::getTransferNo, transferNo); } + /// 根据平台转账单号查询(忽略租户, 确认收款页引导读用) + @IgnoreTenant + public Optional findByTransferNoNotTenant(String transferNo) { + return findByField(WechatTransferOrder::getTransferNo, transferNo); + } + /// 根据商户转账号和应用号查询(幂等查重主路径) public Optional findByBizTransferNo(String bizTransferNo, String appId) { return lambdaQuery() 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 51517b897..cb6aa4b30 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 @@ -66,5 +66,7 @@ "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" + "transferAppTypeNotOfficialAccount": "WeChat: Transfer initiator app must be an Official Account", + "transferRemarkRequired": "WeChat: transfer remark (title) is required", + "transferReportInfoRequired": "WeChat: transfer scene report info is required, please fill in all report fields" } 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 696bf957c..091680b4d 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 @@ -66,5 +66,7 @@ "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" + "transferAppTypeNotOfficialAccount": "WeChat: Aplikasi inisiator transfer harus berjenis Official Account", + "transferRemarkRequired": "WeChat: catatan transfer (judul) wajib diisi", + "transferReportInfoRequired": "WeChat: informasi pelaporan skema transfer wajib diisi, silakan isi semua bidang pelaporan" } 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 d2ac13a6b..f982832e4 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 @@ -66,5 +66,7 @@ "transferAppNotConfigured": "WeChat: 振込发起アプリが未設定です", "transferAppNotExist": "WeChat: 振込发起アプリが見つかりません", "transferAppNotBelong": "WeChat: 振込发起アプリは現在のマーチャントに属していません", - "transferAppTypeNotOfficialAccount": "WeChat: 振込发起アプリは公式アカウントタイプである必要があります" + "transferAppTypeNotOfficialAccount": "WeChat: 振込发起アプリは公式アカウントタイプである必要があります", + "transferRemarkRequired": "WeChat: 振込備考(タイトル)は必須です", + "transferReportInfoRequired": "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 c344b9540..5f8e0dd69 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 @@ -66,5 +66,7 @@ "transferAppNotConfigured": "WeChat: 이체 발신 앱이 구성되지 않았습니다", "transferAppNotExist": "WeChat: 이체 발신 앱을 찾을 수 없습니다", "transferAppNotBelong": "WeChat: 이체 발신 앱이 현재 가맹점에 속하지 않습니다", - "transferAppTypeNotOfficialAccount": "WeChat: 이체 발신 앱은 공식계정 유형이어야 합니다" + "transferAppTypeNotOfficialAccount": "WeChat: 이체 발신 앱은 공식계정 유형이어야 합니다", + "transferRemarkRequired": "WeChat: 이체 메모(제목)는 필수입니다", + "transferReportInfoRequired": "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 f39ceef48..e803ed132 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 @@ -66,5 +66,7 @@ "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" + "transferAppTypeNotOfficialAccount": "WeChat: Aplikasi pemula pemindahan mesti jenis Official Account", + "transferRemarkRequired": "WeChat: catatan pindah wang (tajuk) wajib diisi", + "transferReportInfoRequired": "WeChat: maklumat laporan senario pindah wang wajib diisi, sila isi semua medan laporan" } 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 785a8c34b..58df4b2c2 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 @@ -66,5 +66,7 @@ "transferAppNotConfigured": "WeChat: ยังไม่ได้กำหนดค่าแอปเริ่มการโอน", "transferAppNotExist": "WeChat: ไม่พบแอปเริ่มการโอน", "transferAppNotBelong": "WeChat: แอปเริ่มการโอนไม่ได้เป็นของร้านค้าปัจจุบัน", - "transferAppTypeNotOfficialAccount": "WeChat: แอปเริ่มการโอนต้องเป็นประเภท Official Account" + "transferAppTypeNotOfficialAccount": "WeChat: แอปเริ่มการโอนต้องเป็นประเภท Official Account", + "transferRemarkRequired": "WeChat: หมายเหตุการโอน (หัวข้อ) จำเป็นต้องระบุ", + "transferReportInfoRequired": "WeChat: ข้อมูลรายงานสถานการณ์การโอนเงินจำเป็นต้องระบุ กรุณากรอกข้อมูลรายงานทั้งหมด" } 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 b3ea49c5e..032bcf4a5 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 @@ -66,5 +66,7 @@ "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" + "transferAppTypeNotOfficialAccount": "WeChat: Ứng dụng khởi tạo chuyển khoản phải là loại Official Account", + "transferRemarkRequired": "WeChat: ghi chú chuyển khoản (tiêu đề) là bắt buộc", + "transferReportInfoRequired": "WeChat: thông tin báo cáo kịch bản chuyển khoản là bắt buộc, vui lòng điền đầy đủ các trường báo cáo" } 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 67366ac7e..f09303574 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 @@ -66,5 +66,7 @@ "transferAppNotConfigured": "微信: 转账发起应用未配置", "transferAppNotExist": "微信: 转账发起应用不存在", "transferAppNotBelong": "微信: 转账发起应用不属于当前商户", - "transferAppTypeNotOfficialAccount": "微信: 转账发起应用必须是公众号类型" + "transferAppTypeNotOfficialAccount": "微信: 转账发起应用必须是公众号类型", + "transferRemarkRequired": "微信: 转账备注(标题)必填", + "transferReportInfoRequired": "微信: 转账场景报备信息必填, 请填写全部报备字段" } 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 b9530237a..2252403ad 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 @@ -66,5 +66,7 @@ "transferAppNotConfigured": "微信: 轉帳發起應用未設定", "transferAppNotExist": "微信: 轉帳發起應用不存在", "transferAppNotBelong": "微信: 轉帳發起應用不屬於目前商戶", - "transferAppTypeNotOfficialAccount": "微信: 轉帳發起應用必須是公眾號類型" + "transferAppTypeNotOfficialAccount": "微信: 轉帳發起應用必須是公眾號類型", + "transferRemarkRequired": "微信: 轉賬備註(標題)必填", + "transferReportInfoRequired": "微信: 轉賬場景報備資訊必填, 請填寫全部報備欄位" } 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 201785824..8148936cf 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 @@ -66,5 +66,7 @@ "transferAppNotConfigured": "微信: 轉帳發起應用未設定", "transferAppNotExist": "微信: 轉帳發起應用不存在", "transferAppNotBelong": "微信: 轉帳發起應用不屬於目前商戶", - "transferAppTypeNotOfficialAccount": "微信: 轉帳發起應用必須是公眾號類型" + "transferAppTypeNotOfficialAccount": "微信: 轉帳發起應用必須是公眾號類型", + "transferRemarkRequired": "微信: 轉帳備註(標題)必填", + "transferReportInfoRequired": "微信: 轉帳場景報備資訊必填, 請填寫全部報備欄位" }