feat(资产钱包自动续费): 新增全局配置、每日扫描续购与可靠复机
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 10m15s

- 新增单行配置表 tb_asset_auto_renewal_config 与尝试记录表 tb_asset_auto_renewal_attempt(迁移 000229/000230)
- 每日按上海自然日扫描,窗口内以同一资产钱包可用余额续购当前主套餐,资金/订单/套餐/审计同一事务闭合
- 唯一键保证每资产每日至多一次尝试,占位中断由后续扫描收敛,当日不重试
- 四类失败原因向客户与店铺各投递每日至多一条站内通知,并注册通知类型与个人客户白名单
- 续费成功后按条件经 Outbox 可靠投递复机,新增恢复扫描只查询回填,不使用即发即弃调用
- 配置读写仅超级管理员与平台账号,保存记录操作者、前后值快照并登记统一审计
- tasks 7.1–7.15 全部验证通过(本机隔离 PostgreSQL/Redis,零外部渠道调用)
This commit is contained in:
2026-09-17 16:39:26 +08:00
parent 70e6b186df
commit d52be16802
54 changed files with 5164 additions and 97 deletions

View File

@@ -0,0 +1,167 @@
package constants
import "time"
// 资产钱包自动续费配置适用范围。
const (
// AssetAutoRenewalScopeAll 表示配置适用于全部主套餐。
AssetAutoRenewalScopeAll = "all"
// AssetAutoRenewalScopeSpecified 表示配置只适用于指定主套餐集合。
AssetAutoRenewalScopeSpecified = "specified"
)
// 资产钱包自动续费配置的结构约束。
const (
// AssetAutoRenewalConfigSingletonID 是单行配置表的主键,恒为 1。
AssetAutoRenewalConfigSingletonID = 1
// AssetAutoRenewalConfigEnabledOff 表示总开关关闭。
AssetAutoRenewalConfigEnabledOff = 0
// AssetAutoRenewalConfigEnabledOn 表示总开关开启。
AssetAutoRenewalConfigEnabledOn = 1
// AssetAutoRenewalMinDaysBeforeExpiry 是到期前天数的最小值。
AssetAutoRenewalMinDaysBeforeExpiry = 1
// AssetAutoRenewalMaxDaysBeforeExpiry 是到期前天数的上限。
AssetAutoRenewalMaxDaysBeforeExpiry = 90
)
// 资产钱包自动续费尝试状态ENG-STATE-001生命周期状态使用整数编码
const (
// AssetAutoRenewalAttemptStatusProcessing 表示尝试已占位、执行尚未得出终态。
AssetAutoRenewalAttemptStatusProcessing = 1
// AssetAutoRenewalAttemptStatusSucceeded 表示本次尝试已完成续费。
AssetAutoRenewalAttemptStatusSucceeded = 2
// AssetAutoRenewalAttemptStatusFailed 表示本次尝试以失败终态结束。
AssetAutoRenewalAttemptStatusFailed = 3
// AssetAutoRenewalAttemptStatusSkipped 表示本次尝试因人工优先而跳过。
AssetAutoRenewalAttemptStatusSkipped = 4
)
// 资产钱包自动续费通知原因,固定为四类。
//
// 前三类由尝试失败产生通知(写入尝试记录的 failure_reason
// 第四类复机失败只由复机子结果resume_status=失败)产生通知,
// 绝不回写尝试记录的 failure_reason——复机结果只在复机字段表达同一事实不两处表达。
const (
// AssetAutoRenewalFailureInsufficientBalance 表示可用余额不支持续购。
AssetAutoRenewalFailureInsufficientBalance = "insufficient_balance"
// AssetAutoRenewalFailureNotRenewable 表示当前条件不允许自动续购。
AssetAutoRenewalFailureNotRenewable = "not_renewable"
// AssetAutoRenewalFailureOrderFailed 表示续购事务本身失败并整体回滚。
AssetAutoRenewalFailureOrderFailed = "order_failed"
// AssetAutoRenewalFailureResumeFailed 表示复机最终确认失败(只作为通知原因,不写 failure_reason
AssetAutoRenewalFailureResumeFailed = "resume_failed"
)
// 资产钱包自动续费尝试记录中允许出现的失败归类。
//
// 取值域是通知原因中的前三类加上中断收敛值:失败归类只描述「实际进入执行后得出的失败」,
// 因此不含复机失败。interrupted 是占位后进程中断的收敛表示,不属于四类通知原因,
// 由 IsAssetAutoRenewalNotifiableFailureReason 显式排除,收敛过程不发送任何通知。
const (
// AssetAutoRenewalFailureInterrupted 表示占位写入后进程中断,由后续扫描收敛。
AssetAutoRenewalFailureInterrupted = "interrupted"
)
// IsAssetAutoRenewalNotifiableFailureReason 判断该失败原因是否触发站内通知。
// 未登记的原因一律不通知fail-closed避免把收敛产生的 interrupted 误当执行失败通知出去。
func IsAssetAutoRenewalNotifiableFailureReason(reason string) bool {
switch reason {
case AssetAutoRenewalFailureInsufficientBalance, AssetAutoRenewalFailureNotRenewable,
AssetAutoRenewalFailureOrderFailed, AssetAutoRenewalFailureResumeFailed:
return true
default:
return false
}
}
// 资产钱包自动续费跳过原因。
const (
// AssetAutoRenewalSkipManualRenewed 表示人工已完成续购(已存在待生效主套餐)。
AssetAutoRenewalSkipManualRenewed = "manual_renewed"
// AssetAutoRenewalSkipManualOrderPending 表示该资产存在未关闭的人工主套餐订单。
AssetAutoRenewalSkipManualOrderPending = "manual_order_pending"
)
// 资产钱包自动续费尝试的复机状态。
const (
// AssetAutoRenewalResumeStatusSkipped 表示可复机条件不成立,未投递复机。
AssetAutoRenewalResumeStatusSkipped = 1
// AssetAutoRenewalResumeStatusRequested 表示已可靠投递复机请求,等待结果。
AssetAutoRenewalResumeStatusRequested = 2
// AssetAutoRenewalResumeStatusSucceeded 表示复机已确认成功。
AssetAutoRenewalResumeStatusSucceeded = 3
// AssetAutoRenewalResumeStatusFailed 表示复机已确认失败。
AssetAutoRenewalResumeStatusFailed = 4
// AssetAutoRenewalResumeStatusUnknown 表示复机结果未知,等待恢复扫描确认。
AssetAutoRenewalResumeStatusUnknown = 5
)
// 资产钱包自动续费尝试的操作者类型:本能力无人工处理入口,恒为系统任务。
const (
// AssetAutoRenewalOperatorTypeSystemTask 表示系统任务操作者。
AssetAutoRenewalOperatorTypeSystemTask = "system_task"
)
// 资产钱包自动续费尝试的批次与查询约束。
const (
// AssetAutoRenewalRecoveryBatchSize 是恢复扫描单批处理条数上限。
AssetAutoRenewalRecoveryBatchSize = 200
// AssetAutoRenewalConfigDisplayName 是配置资源的展示名。
AssetAutoRenewalConfigDisplayName = "资产钱包自动续费配置"
)
// 资产钱包自动续费的可靠事件类型。
const (
// OutboxEventTypeAssetAutoRenewalResumeRequested 表示续费成功后按条件触发的可靠复机请求。
OutboxEventTypeAssetAutoRenewalResumeRequested = "asset_auto_renewal.resume.requested"
)
// 资产钱包自动续费复机子任务的查询窗口。
const (
// AssetAutoRenewalResumeQueryWindow 是复机子任务自提交起允许自动查询确认的窗口。
// 语义:复机事件进入已投递/已提交后,恢复扫描只查询运营商状态回填;超过该窗口仍无法确认的
// 尝试会标记异常并转人工核对,不再重复查询同一笔无法收敛的结果,也不回滚任何续费事实。
AssetAutoRenewalResumeQueryWindow = 30 * time.Minute
)
// GetAssetAutoRenewalScopeName 返回自动续费适用范围的中文名称。
func GetAssetAutoRenewalScopeName(scope string) string {
switch scope {
case AssetAutoRenewalScopeAll:
return "全部主套餐"
case AssetAutoRenewalScopeSpecified:
return "指定主套餐"
default:
return "未知"
}
}
// GetAssetAutoRenewalFailureReasonName 返回自动续费失败原因的中文名称。
func GetAssetAutoRenewalFailureReasonName(reason string) string {
switch reason {
case AssetAutoRenewalFailureInsufficientBalance:
return "余额不足"
case AssetAutoRenewalFailureNotRenewable:
return "不可续费"
case AssetAutoRenewalFailureOrderFailed:
return "订单失败"
case AssetAutoRenewalFailureResumeFailed:
return "复机失败"
case AssetAutoRenewalFailureInterrupted:
return "中断收敛"
default:
return "未知"
}
}
// GetAssetAutoRenewalSkipReasonName 返回自动续费跳过原因的中文名称。
func GetAssetAutoRenewalSkipReasonName(reason string) string {
switch reason {
case AssetAutoRenewalSkipManualRenewed:
return "人工已完成续购"
case AssetAutoRenewalSkipManualOrderPending:
return "人工订单在途"
default:
return "未知"
}
}