feat(资产钱包自动续费): 新增全局配置、每日扫描续购与可靠复机
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 10m15s
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:
167
pkg/constants/asset_auto_renewal.go
Normal file
167
pkg/constants/asset_auto_renewal.go
Normal 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 "未知"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user