package financewithdrawal import ( "context" "fmt" "strings" "time" "hyapp-admin-server/internal/model" "hyapp-admin-server/internal/repository" ) type markdownSender interface { SendMarkdown(ctx context.Context, title string, text string) error } // ReviewPendingNotifier 只通知“当前待谁处理”,不参与提现创建、冻结或审核事务。 type ReviewPendingNotifier interface { NotifyReviewPending(ctx context.Context, application model.UserWithdrawalApplication, stage repository.WithdrawalApplicationReviewStage) error } type dingTalkReviewPendingNotifier struct { sender markdownSender } func NewDingTalkReviewPendingNotifier(sender markdownSender) ReviewPendingNotifier { if sender == nil { return nil } return &dingTalkReviewPendingNotifier{sender: sender} } func (n *dingTalkReviewPendingNotifier) NotifyReviewPending(ctx context.Context, application model.UserWithdrawalApplication, stage repository.WithdrawalApplicationReviewStage) error { if n == nil || n.sender == nil { return nil } title, text := withdrawalReviewPendingMarkdown(application, stage) return n.sender.SendMarkdown(ctx, title, text) } func withdrawalReviewPendingMarkdown(application model.UserWithdrawalApplication, stage repository.WithdrawalApplicationReviewStage) (string, string) { stageName := "运营审核阶段" pageURL := "https://admin-acc.global-interaction.com/finance/?view=withdrawalOperations" if stage == repository.WithdrawalApplicationReviewStageFinance { stageName = "财务审核阶段" pageURL = "https://admin-acc.global-interaction.com/finance/?view=withdrawalFinance" } title := fmt.Sprintf("用户提现待处理【%s】", stageName) lines := []string{ fmt.Sprintf("### 用户提现待处理【%s】", stageName), "", fmt.Sprintf("- 当前阶段:**%s**", stageName), fmt.Sprintf("- 申请ID:%d", application.ID), fmt.Sprintf("- APP:%s", withdrawalMarkdownValue(application.AppCode)), fmt.Sprintf("- 来源系统:%s", withdrawalMarkdownValue(application.SourceSystem)), fmt.Sprintf("- 来源单号:%s", withdrawalMarkdownValue(application.SourceApplicationID)), fmt.Sprintf("- 用户ID:%s", withdrawalMarkdownValue(application.UserID)), fmt.Sprintf("- 申请金额:$ %s", withdrawalMarkdownValue(application.WithdrawAmount)), fmt.Sprintf("- 提现方式:%s", withdrawalMarkdownValue(application.WithdrawMethod)), fmt.Sprintf("- 提现地址:%s", withdrawalMarkdownValue(application.WithdrawAddress)), fmt.Sprintf("- 申请时间:%s", formatWithdrawalNotifyMillis(application.CreatedAtMS)), } if hasWithdrawalPayoutSnapshot(application) { // 协作通知展示来源申请时的不可变快照,财务不应在审核时按当前费率重算。 lines = append(lines, fmt.Sprintf("- 手续费:$ %s(费率 %s%%)", withdrawalOptionalMarkdownValue(application.ServiceCharge), withdrawalOptionalMarkdownValue(application.ServiceChargeRatio)), fmt.Sprintf("- 实际打款金额:$ %s", withdrawalOptionalMarkdownValue(application.ActualAmount)), ) } if stage == repository.WithdrawalApplicationReviewStageFinance { // 财务提醒保留运营审核快照,接收人无需先打开后台就能确认这是一张已完成初审的申请。 lines = append(lines, fmt.Sprintf("- 运营审核人:%s", withdrawalMarkdownValue(application.OperationsReviewerName)), fmt.Sprintf("- 运营审核备注:%s", withdrawalMarkdownValue(application.OperationsAuditRemark)), fmt.Sprintf("- 运营通过时间:%s", formatWithdrawalNotifyMillis(int64Value(application.OperationsReviewedAtMS))), ) } lines = append(lines, "- 后台地址:"+pageURL) return title, strings.Join(lines, "\n") } func hasWithdrawalPayoutSnapshot(application model.UserWithdrawalApplication) bool { return application.ServiceChargeRatio != nil || application.ServiceCharge != nil || application.ActualAmount != nil || application.ActualAmountMinor != nil } func withdrawalOptionalMarkdownValue(value *string) string { if value == nil { return "-" } return withdrawalMarkdownValue(*value) } func formatWithdrawalNotifyMillis(value int64) string { if value <= 0 { return "-" } // 协作消息按运营团队使用的北京时间展示;接口和数据库仍以 Unix 毫秒 UTC 事实为准。 return time.UnixMilli(value).In(time.FixedZone("UTC+8", 8*60*60)).Format("2006-01-02 15:04:05") } func int64Value(value *int64) int64 { if value == nil { return 0 } return *value } func withdrawalMarkdownValue(value string) string { value = strings.TrimSpace(value) if value == "" { return "-" } runes := []rune(value) if len(runes) > 240 { value = string(runes[:240]) + "..." } return strings.NewReplacer("\r", " ", "\n", " ", "\t", " ").Replace(value) }