Files
pay/internal/reconcile/assembly.go
T
wangjia 0ae7769abe test(pay): 补 settle 并发/跨包全链 e2e/reconcile 装配/alipay 验签健壮性/money 零小数位
- gateway: TestSettleConcurrentSameOrder 真并发钉住 MarkAttemptPaid 行锁不变量
  (文件型 sqlite,规避 in-memory cache=shared 的 SQLITE_LOCKED_SHAREDCACHE)。
- gateway: e2e_fullchain_test.go 补下单→回调→settle→webhook 实际 HTTP 投递→
  delivered 整链(httptest server + 真实 store.WebhookStore/webhook.Notifier)。
- reconcile: main.go 装配抽到 reconcile.Assemble(+Runner.TaskNames 访问器),
  补 assembly_test.go 钉住 7 个后台任务全部注册 + crypto 缺渠道/interval=0 分支。
- alipay: 补验签健壮性(未知多余字段/字段乱序/中文unicode/空值字段),意外定位
  vendor Encoder 对空值字段的真实语义与官方文档描述不同,已记录在测试注释。
- money: 补 TestZeroDecimalCurrencyJPYNotSupported,记录 JPY/KRW 当前未注册进
  exponents(接入会先踩 ErrUnknownCurrency,不是乘除法坑)。
2026-07-11 08:50:45 +08:00

45 lines
2.6 KiB
Go

package reconcile
import (
"time"
"github.com/wangjia/pay/config"
"github.com/wangjia/pay/internal/accounts"
"github.com/wangjia/pay/internal/gateway"
"github.com/wangjia/pay/internal/provider"
"github.com/wangjia/pay/internal/store"
)
// Assemble builds the full P6 background-reconcile Runner: order-expire /
// usage-refresh / refund-apply-sweep / refund-stuck-alert (local, DB-only,
// AddLocal — run synchronously once at startup by main.go's RunOnceLocal) +
// sync-pending / paid-spotcheck (network, Add — first tick handles it) +
// crypto warm(直跑)+ crypto-orphan-scan(AddCryptoJobs,network,只在 crypto 渠道
// 已注册时挂载).
//
// 抽出这个函数纯粹是为了让"预期任务集合是否都注册上"能被直接单测(main.go 原先把
// 这段写死在 main() 里,main() 本身因为要拉真实 DB/HTTP server 不适合单测——见
// assembly_test.go)。main.go 对应 `if config.C.Reconcile.Enabled` 分支现在只是薄薄
// 一层:调这里 + RunOnceLocal/Start/日志,行为与抽取前逐行一致,未改任何调度语义。
func Assemble(orderStore *store.OrderStore, refundStore *store.RefundStore, usage *UsageSource,
gw *gateway.Gateway, pReg *provider.Registry, acctReg *accounts.Registry,
orphanStore *store.OrphanStore, rc config.ReconcileConfig) *Runner {
runner := NewRunner()
// 纯本地(DB-only)任务用 AddLocal:会被启动预热 RunOnceLocal 同步跑一遍。
runner.AddLocal("order-expire", time.Duration(rc.ExpireEverySec)*time.Second,
OrderExpirerTask(orderStore, time.Duration(rc.OrderTTLMin)*time.Minute, time.Now))
runner.AddLocal("usage-refresh", time.Duration(rc.UsageEverySec)*time.Second,
RefreshUsageTask(usage))
runner.AddLocal("refund-apply-sweep", time.Duration(rc.RefundApplyEverySec)*time.Second,
RefundApplyTask(orderStore, refundStore, time.Duration(rc.RefundApplyLookbackMin)*time.Minute, time.Now, 200))
runner.AddLocal("refund-stuck-alert", time.Duration(rc.RefundApplyEverySec)*time.Second,
RefundStuckAlertTask(refundStore, time.Duration(rc.RefundStuckWarnMin)*time.Minute, time.Now))
// 网络型(出网 HTTP,10-15s 超时)任务用 Add:启动预热不跑,交各自 ticker 首跳。
runner.Add("sync-pending", time.Duration(rc.SyncEverySec)*time.Second,
SyncPendingTask(gw, 100))
runner.Add("paid-spotcheck", time.Duration(rc.SpotCheckEverySec)*time.Second,
PaidSpotCheckTask(orderStore, pReg, time.Duration(rc.SpotCheckWindowMin)*time.Minute, time.Now))
AddCryptoJobs(runner, pReg, acctReg, orderStore, orphanStore, rc) // crypto 预留冷启动 Warm(直跑,不受影响)+ 孤儿扫描(网络型,交 ticker 首跳)
return runner
}