Files
junhong_cmp_fiber/internal/model/dto/wechat_config_dto.go
break ba0855d9eb feat(退款): AUG26-006 退款方式选择与原路退款
按 PRD 2.3/2.4/2.5 落地套餐退款的方式矩阵与原路渠道退款:

- 退款申请派生并冻结权威实收金额(线上取原成功支付记录,钱包/线下取订单实际收款),
  提交人不可填写或修改;按来源支付方式生成可选方式矩阵并在创建、提交、执行前重复校验。
- 审批切换为「每次提交一条不可变审批尝试记录 + 独立企业微信审批实例」,业务标识取尝试
  记录主键;终态消费按尝试记录优先、退款申请兜底双读,兼容存量无实例与已关联实例申请。
  新增活动退款部分唯一索引 (order_id) WHERE status IN (1,5,6)。
- 本地人工终审保持既有开关,补齐通过入口的 approval_instance_id IS NULL 守卫,使三个
  入口一致拒绝已关联审批实例的申请;重提按尝试模式重写(仅已拒绝/已退回/原路失败且无异常)。
- 权益时点:企微通过事务写退款终态、按方式确定的订单态、钱包回款、员工账单冲销与可靠
  失效事实;套餐失效/接续/停机仍由既有可靠机制最终一致执行,不把外部调用放入资金事务。
  订单支付状态按方式置位:凭证退款与退回原钱包在企微通过时置已退款,原路须渠道明确成功。
- 按官方契约实现微信直连 v3、微信 v2(双向证书)、富友(/commonRefund 与 /refundQuery)、
  支付宝四类原路退款;能力只由服务商类型与退款必需凭证完整性决定,无人工开关。
  渠道请求号在提交时冻结到尝试记录,并以 channel_submitted_at 条件认领保证资金动作至多
  提交一次(重复投递只查询不二次提交);不向任何渠道传递退款结果通知地址。
- 新增 refund:channel:recovery 恢复任务只查询回填;本地查询窗口超期(富友 72 小时、
  微信 v2 7 天)转原路退款失败、渠道状态已失败、分类超时未知并置异常转人工,不放行自动
  重提以避免重复退款。
- 同步退款 DTO/导出/审计资源与审计查询关联、商户凭证文档,并修正 fuiou 集成契约文档。

迁移 000218(退款尝试与渠道退款事实)、000219(微信 v2 客户端证书凭证)成对提供,
未修改既有迁移;测试库 junhong_cmp_test 完成 up/down/up 与行为核对,未调用真实渠道。
2026-09-14 11:55:16 +08:00

236 lines
16 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package dto
import (
"fmt"
"github.com/break/junhong_cmp_fiber/internal/model"
)
// CreateWechatConfigRequest 创建微信参数配置请求
type CreateWechatConfigRequest struct {
Name string `json:"name" validate:"required,min=1,max=100" required:"true" minLength:"1" maxLength:"100" description:"配置名称"`
Description string `json:"description" validate:"omitempty,max=500" maxLength:"500" description:"配置描述"`
ProviderType string `json:"provider_type" validate:"required,oneof=wechat wechat_v2 fuiou" required:"true" description:"支付渠道类型 (wechat:微信直连v3, wechat_v2:微信直连v2, fuiou:富友)"`
OaAppID string `json:"oa_app_id" validate:"omitempty,max=100" maxLength:"100" description:"公众号AppID"`
OaAppSecret string `json:"oa_app_secret" validate:"omitempty,max=200" maxLength:"200" description:"公众号AppSecret"`
OaToken string `json:"oa_token" validate:"omitempty,max=200" maxLength:"200" description:"公众号Token"`
OaAesKey string `json:"oa_aes_key" validate:"omitempty,max=200" maxLength:"200" description:"公众号AES加密Key"`
OaOAuthRedirectURL string `json:"oa_oauth_redirect_url" validate:"omitempty,max=500" maxLength:"500" description:"OAuth回调地址"`
MiniappAppID string `json:"miniapp_app_id" validate:"omitempty,max=100" maxLength:"100" description:"小程序AppID"`
MiniappAppSecret string `json:"miniapp_app_secret" validate:"omitempty,max=200" maxLength:"200" description:"小程序AppSecret"`
WxMchID string `json:"wx_mch_id" validate:"omitempty,max=100" maxLength:"100" description:"微信商户号"`
WxAPIV3Key string `json:"wx_api_v3_key" validate:"omitempty,max=200" maxLength:"200" description:"微信APIv3密钥"`
WxAPIV2Key string `json:"wx_api_v2_key" validate:"omitempty,max=200" maxLength:"200" description:"微信APIv2密钥"`
WxCertContent string `json:"wx_cert_content" validate:"omitempty" description:"微信支付证书内容(PEM格式)"`
WxKeyContent string `json:"wx_key_content" validate:"omitempty" description:"微信支付密钥内容(PEM格式)"`
WxSerialNo string `json:"wx_serial_no" validate:"omitempty,max=200" maxLength:"200" description:"微信证书序列号"`
WxNotifyURL string `json:"wx_notify_url" validate:"omitempty,max=500" maxLength:"500" description:"微信支付回调地址"`
// v2 退款接口为双向证书接口,支付与查单不依赖该证书。
WxClientCertContent string `json:"wx_client_cert_content" validate:"omitempty" description:"微信支付API客户端证书内容(PEM格式v2 退款双向证书所需)"`
WxClientKeyContent string `json:"wx_client_key_content" validate:"omitempty" description:"微信支付API客户端证书私钥内容(PEM格式v2 退款双向证书所需)"`
FyInsCd string `json:"fy_ins_cd" validate:"omitempty,max=50" maxLength:"50" description:"富友机构号"`
FyMchntCd string `json:"fy_mchnt_cd" validate:"omitempty,max=50" maxLength:"50" description:"富友商户号"`
FyTermID string `json:"fy_term_id" validate:"omitempty,max=50" maxLength:"50" description:"富友终端号"`
FyPrivateKey string `json:"fy_private_key" validate:"omitempty" description:"富友私钥(PEM格式)"`
FyPublicKey string `json:"fy_public_key" validate:"omitempty" description:"富友公钥(PEM格式)"`
FyAPIURL string `json:"fy_api_url" validate:"omitempty,max=500" maxLength:"500" description:"富友API地址"`
FyNotifyURL string `json:"fy_notify_url" validate:"omitempty,max=500" maxLength:"500" description:"富友支付回调地址"`
// 支付宝配置字段(并存能力,不受 provider_type 限制)
AliAppID string `json:"ali_app_id" validate:"omitempty,max=100" maxLength:"100" description:"支付宝应用ID"`
AliPrivateKey string `json:"ali_private_key" validate:"omitempty" description:"支付宝应用私钥(PEM格式)"`
AliPublicKey string `json:"ali_public_key" validate:"omitempty" description:"支付宝公钥(PEM格式用于验签)"`
AliNotifyURL string `json:"ali_notify_url" validate:"omitempty,max=500" maxLength:"500" description:"支付宝异步通知地址"`
AliReturnURL string `json:"ali_return_url" validate:"omitempty,max=500" maxLength:"500" description:"支付宝同步跳转地址"`
AliProduction bool `json:"ali_production" description:"是否生产环境 (true:生产, false:沙箱)"`
AliPayExpireMinutes int `json:"ali_pay_expire_minutes" validate:"omitempty,min=1,max=1440" minimum:"1" maximum:"1440" description:"支付过期分钟数 (默认30分钟)"`
}
// UpdateWechatConfigRequest 更新微信参数配置请求
type UpdateWechatConfigRequest struct {
Name *string `json:"name" validate:"omitempty,min=1,max=100" minLength:"1" maxLength:"100" description:"配置名称"`
Description *string `json:"description" validate:"omitempty,max=500" maxLength:"500" description:"配置描述"`
ProviderType *string `json:"provider_type" validate:"omitempty,oneof=wechat wechat_v2 fuiou" description:"支付渠道类型 (wechat:微信直连v3, wechat_v2:微信直连v2, fuiou:富友)"`
OaAppID *string `json:"oa_app_id" validate:"omitempty,max=100" maxLength:"100" description:"公众号AppID"`
OaAppSecret *string `json:"oa_app_secret" validate:"omitempty,max=200" maxLength:"200" description:"公众号AppSecret"`
OaToken *string `json:"oa_token" validate:"omitempty,max=200" maxLength:"200" description:"公众号Token"`
OaAesKey *string `json:"oa_aes_key" validate:"omitempty,max=200" maxLength:"200" description:"公众号AES加密Key"`
OaOAuthRedirectURL *string `json:"oa_oauth_redirect_url" validate:"omitempty,max=500" maxLength:"500" description:"OAuth回调地址"`
MiniappAppID *string `json:"miniapp_app_id" validate:"omitempty,max=100" maxLength:"100" description:"小程序AppID"`
MiniappAppSecret *string `json:"miniapp_app_secret" validate:"omitempty,max=200" maxLength:"200" description:"小程序AppSecret"`
WxMchID *string `json:"wx_mch_id" validate:"omitempty,max=100" maxLength:"100" description:"微信商户号"`
WxAPIV3Key *string `json:"wx_api_v3_key" validate:"omitempty,max=200" maxLength:"200" description:"微信APIv3密钥"`
WxAPIV2Key *string `json:"wx_api_v2_key" validate:"omitempty,max=200" maxLength:"200" description:"微信APIv2密钥"`
WxCertContent *string `json:"wx_cert_content" validate:"omitempty" description:"微信支付证书内容(PEM格式)"`
WxKeyContent *string `json:"wx_key_content" validate:"omitempty" description:"微信支付密钥内容(PEM格式)"`
WxSerialNo *string `json:"wx_serial_no" validate:"omitempty,max=200" maxLength:"200" description:"微信证书序列号"`
WxNotifyURL *string `json:"wx_notify_url" validate:"omitempty,max=500" maxLength:"500" description:"微信支付回调地址"`
// v2 退款接口为双向证书接口,支付与查单不依赖该证书。
WxClientCertContent *string `json:"wx_client_cert_content" validate:"omitempty" description:"微信支付API客户端证书内容(PEM格式v2 退款双向证书所需)"`
WxClientKeyContent *string `json:"wx_client_key_content" validate:"omitempty" description:"微信支付API客户端证书私钥内容(PEM格式v2 退款双向证书所需)"`
FyInsCd *string `json:"fy_ins_cd" validate:"omitempty,max=50" maxLength:"50" description:"富友机构号"`
FyMchntCd *string `json:"fy_mchnt_cd" validate:"omitempty,max=50" maxLength:"50" description:"富友商户号"`
FyTermID *string `json:"fy_term_id" validate:"omitempty,max=50" maxLength:"50" description:"富友终端号"`
FyPrivateKey *string `json:"fy_private_key" validate:"omitempty" description:"富友私钥(PEM格式)"`
FyPublicKey *string `json:"fy_public_key" validate:"omitempty" description:"富友公钥(PEM格式)"`
FyAPIURL *string `json:"fy_api_url" validate:"omitempty,max=500" maxLength:"500" description:"富友API地址"`
FyNotifyURL *string `json:"fy_notify_url" validate:"omitempty,max=500" maxLength:"500" description:"富友支付回调地址"`
// 支付宝配置字段(并存能力,不受 provider_type 限制)
AliAppID *string `json:"ali_app_id" validate:"omitempty,max=100" maxLength:"100" description:"支付宝应用ID"`
AliPrivateKey *string `json:"ali_private_key" validate:"omitempty" description:"支付宝应用私钥(PEM格式)"`
AliPublicKey *string `json:"ali_public_key" validate:"omitempty" description:"支付宝公钥(PEM格式用于验签)"`
AliNotifyURL *string `json:"ali_notify_url" validate:"omitempty,max=500" maxLength:"500" description:"支付宝异步通知地址"`
AliReturnURL *string `json:"ali_return_url" validate:"omitempty,max=500" maxLength:"500" description:"支付宝同步跳转地址"`
AliProduction *bool `json:"ali_production" description:"是否生产环境 (true:生产, false:沙箱)"`
AliPayExpireMinutes *int `json:"ali_pay_expire_minutes" validate:"omitempty,min=1,max=1440" minimum:"1" maximum:"1440" description:"支付过期分钟数"`
}
// UpdateWechatConfigParams 更新微信参数配置聚合参数 (用于文档生成)
type UpdateWechatConfigParams struct {
IDReq
UpdateWechatConfigRequest
}
// WechatConfigListRequest 微信参数配置列表查询请求
type WechatConfigListRequest struct {
Page int `json:"page" query:"page" validate:"omitempty,min=1" minimum:"1" description:"页码"`
PageSize int `json:"page_size" query:"page_size" validate:"omitempty,min=1,max=100" minimum:"1" maximum:"100" description:"每页数量"`
ProviderType *string `json:"provider_type" query:"provider_type" validate:"omitempty,oneof=wechat wechat_v2 fuiou" description:"支付渠道类型 (wechat:微信直连v3, wechat_v2:微信直连v2, fuiou:富友)"`
IsActive *bool `json:"is_active" query:"is_active" description:"是否激活 (true:已激活, false:未激活)"`
}
// WechatConfigResponse 微信参数配置响应
type WechatConfigResponse struct {
ID uint `json:"id" description:"配置ID"`
Name string `json:"name" description:"配置名称"`
Description string `json:"description" description:"配置描述"`
ProviderType string `json:"provider_type" description:"支付渠道类型 (wechat:微信直连v3, wechat_v2:微信直连v2, fuiou:富友)"`
IsActive bool `json:"is_active" description:"是否激活"`
OaAppID string `json:"oa_app_id" description:"公众号AppID"`
OaAppSecret string `json:"oa_app_secret" description:"公众号AppSecret(已脱敏)"`
OaToken string `json:"oa_token" description:"公众号Token(已脱敏)"`
OaAesKey string `json:"oa_aes_key" description:"公众号AES加密Key(已脱敏)"`
OaOAuthRedirectURL string `json:"oa_oauth_redirect_url" description:"OAuth回调地址"`
MiniappAppID string `json:"miniapp_app_id" description:"小程序AppID"`
MiniappAppSecret string `json:"miniapp_app_secret" description:"小程序AppSecret(已脱敏)"`
WxMchID string `json:"wx_mch_id" description:"微信商户号"`
WxAPIV3Key string `json:"wx_api_v3_key" description:"微信APIv3密钥(已脱敏)"`
WxAPIV2Key string `json:"wx_api_v2_key" description:"微信APIv2密钥(已脱敏)"`
WxCertContent string `json:"wx_cert_content" description:"微信支付证书内容(配置状态)"`
WxKeyContent string `json:"wx_key_content" description:"微信支付密钥内容(配置状态)"`
WxSerialNo string `json:"wx_serial_no" description:"微信证书序列号"`
WxNotifyURL string `json:"wx_notify_url" description:"微信支付回调地址"`
WxClientCertContent string `json:"wx_client_cert_content" description:"微信支付API客户端证书内容(配置状态v2 退款双向证书所需)"`
WxClientKeyContent string `json:"wx_client_key_content" description:"微信支付API客户端证书私钥内容(配置状态v2 退款双向证书所需)"`
FyInsCd string `json:"fy_ins_cd" description:"富友机构号"`
FyMchntCd string `json:"fy_mchnt_cd" description:"富友商户号"`
FyTermID string `json:"fy_term_id" description:"富友终端号"`
FyPrivateKey string `json:"fy_private_key" description:"富友私钥(配置状态)"`
FyPublicKey string `json:"fy_public_key" description:"富友公钥(配置状态)"`
FyAPIURL string `json:"fy_api_url" description:"富友API地址"`
FyNotifyURL string `json:"fy_notify_url" description:"富友支付回调地址"`
// 支付宝配置字段(敏感字段已脱敏)
AliAppID string `json:"ali_app_id" description:"支付宝应用ID"`
AliPrivateKey string `json:"ali_private_key" description:"支付宝应用私钥(配置状态)"`
AliPublicKey string `json:"ali_public_key" description:"支付宝公钥(配置状态)"`
AliNotifyURL string `json:"ali_notify_url" description:"支付宝异步通知地址"`
AliReturnURL string `json:"ali_return_url" description:"支付宝同步跳转地址"`
AliProduction bool `json:"ali_production" description:"是否生产环境"`
AliPayExpireMinutes int `json:"ali_pay_expire_minutes" description:"支付过期分钟数"`
CreatedAt string `json:"created_at" description:"创建时间"`
UpdatedAt string `json:"updated_at" description:"更新时间"`
}
// WechatConfigListResponse 微信参数配置列表响应
type WechatConfigListResponse struct {
List []*WechatConfigResponse `json:"items" description:"配置列表"`
Total int64 `json:"total" description:"总数"`
Page int `json:"page" description:"当前页"`
PageSize int `json:"size" description:"每页数量"`
}
// MaskShortSecret 对短密钥进行脱敏处理
// 长度小于 8 返回 "***"否则保留前4位和后4位
func MaskShortSecret(val string) string {
if len(val) < 8 {
return "***"
}
return fmt.Sprintf("%s***%s", val[:4], val[len(val)-4:])
}
// MaskLongSecret 对长密钥/证书进行脱敏处理
// 空值返回 "[未配置]",否则返回 "[已配置]"
func MaskLongSecret(val string) string {
if val == "" {
return "[未配置]"
}
return "[已配置]"
}
// FromWechatConfigModel 从模型转换为响应 DTO敏感字段自动脱敏
func FromWechatConfigModel(m *model.WechatConfig) *WechatConfigResponse {
resp := &WechatConfigResponse{
ID: m.ID,
Name: m.Name,
ProviderType: m.ProviderType,
IsActive: m.IsActive,
OaAppID: m.OaAppID,
OaAppSecret: MaskShortSecret(m.OaAppSecret),
OaToken: MaskShortSecret(m.OaToken),
OaAesKey: MaskShortSecret(m.OaAesKey),
OaOAuthRedirectURL: m.OaOAuthRedirectURL,
MiniappAppID: m.MiniappAppID,
MiniappAppSecret: MaskShortSecret(m.MiniappAppSecret),
WxMchID: m.WxMchID,
WxAPIV3Key: MaskShortSecret(m.WxAPIV3Key),
WxAPIV2Key: MaskShortSecret(m.WxAPIV2Key),
WxCertContent: MaskLongSecret(m.WxCertContent),
WxKeyContent: MaskLongSecret(m.WxKeyContent),
WxSerialNo: m.WxSerialNo,
WxNotifyURL: m.WxNotifyURL,
FyInsCd: m.FyInsCd,
FyMchntCd: m.FyMchntCd,
FyTermID: m.FyTermID,
FyPrivateKey: MaskLongSecret(m.FyPrivateKey),
FyPublicKey: MaskLongSecret(m.FyPublicKey),
FyAPIURL: m.FyAPIURL,
FyNotifyURL: m.FyNotifyURL,
AliAppID: m.AliAppID,
AliPrivateKey: MaskLongSecret(m.AliPrivateKey),
AliPublicKey: MaskLongSecret(m.AliPublicKey),
AliNotifyURL: m.AliNotifyURL,
AliReturnURL: m.AliReturnURL,
AliProduction: m.AliProduction,
AliPayExpireMinutes: m.AliPayExpireMinutes,
CreatedAt: m.CreatedAt.Format("2006-01-02 15:04:05"),
UpdatedAt: m.UpdatedAt.Format("2006-01-02 15:04:05"),
}
if m.Description != nil {
resp.Description = *m.Description
}
return resp
}