From d7894c5825b87ec6bf9e1121011bc7481a97af46 Mon Sep 17 00:00:00 2001 From: zhx Date: Fri, 3 Jul 2026 19:52:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E6=95=B0=E6=8D=AE=E6=8C=87=E6=A0=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modules/payment/legacy_bill_source.go | 76 ++++++++++++++++++- .../mysql/recharge_overview_repository.go | 4 +- 2 files changed, 77 insertions(+), 3 deletions(-) diff --git a/server/admin/internal/modules/payment/legacy_bill_source.go b/server/admin/internal/modules/payment/legacy_bill_source.go index 4805ad26..740feb6a 100644 --- a/server/admin/internal/modules/payment/legacy_bill_source.go +++ b/server/admin/internal/modules/payment/legacy_bill_source.go @@ -72,6 +72,7 @@ var errLegacyGooglePaidUnsupported = errors.New("legacy google paid sync is not // MongoRechargeBillSource 读取 likei Mongo 的 in_app_purchase_details 集合作为充值账单事实。 type MongoRechargeBillSource struct { collection *mongo.Collection + database *mongo.Database config config.FinanceBillSourceConfig regionResolvers []legacyRegionCountryResolver regionSources []MoneyRegionSource @@ -91,7 +92,7 @@ func NewMongoRechargeBillSource(database *mongo.Database, sourceConfig config.Fi sourceConfig.AppCode = appctx.Normalize(sourceConfig.AppCode) sourceConfig.AppName = strings.TrimSpace(sourceConfig.AppName) sourceConfig.SysOrigin = strings.ToUpper(strings.TrimSpace(sourceConfig.SysOrigin)) - source := &MongoRechargeBillSource{collection: database.Collection(collectionName), config: sourceConfig} + source := &MongoRechargeBillSource{collection: database.Collection(collectionName), database: database, config: sourceConfig} for _, regionSource := range regionSources { if regionSource == nil { continue @@ -703,6 +704,10 @@ func (s *MongoRechargeBillSource) Overview(ctx context.Context, query legacyRech if s == nil || s.collection == nil { return overview, fmt.Errorf("legacy bill source is not configured") } + // 用户提现(银行卡余额兑换金币/转货运代理)与充值渠道筛选无关,先于渠道可匹配性判断统计。 + if err := s.overviewWithdrawal(ctx, query, &overview); err != nil { + return overview, err + } filter, regionCodes, matchable, err := s.billFilter(ctx, query) if err != nil { return overview, err @@ -859,6 +864,75 @@ func (s *MongoRechargeBillSource) legacyCountryRegionIndex(ctx context.Context) return index } +// legacyGoldWithdrawCollection 是 likei 的“银行卡余额兑换金币”申请集合; +// 该平台的用户工资转币商即此流程(币商在 likei 叫货运代理 FREIGHT),amount 为提交的美元余额。 +const legacyGoldWithdrawCollection = "user_bank_withdraw_gold_apply" + +func (s *MongoRechargeBillSource) overviewWithdrawal(ctx context.Context, query legacyRechargeBillQuery, overview *rechargeBillOverviewDTO) error { + if s.database == nil { + return nil + } + match := bson.M{"sysOrigin": s.config.SysOrigin} + timeRange := bson.M{} + if query.StartAtMS > 0 { + timeRange["$gte"] = time.UnixMilli(query.StartAtMS) + } + if query.EndAtMS > 0 { + timeRange["$lt"] = time.UnixMilli(query.EndAtMS) + } + if len(timeRange) > 0 { + match["createTime"] = timeRange + } + pipeline := mongo.Pipeline{bson.D{{Key: "$match", Value: match}}} + if query.RegionID > 0 { + codes, handled, err := s.regionCountryCodes(ctx, query.RegionID) + if err != nil { + return err + } + if !handled || len(codes) == 0 { + return nil + } + // 兑换申请没有国家码,按提交用户的资料国家落区域。 + pipeline = append(pipeline, + bson.D{{Key: "$lookup", Value: bson.M{ + "from": legacyUserProfileCollection, + "localField": "submitUserId", + "foreignField": "_id", + "as": "legacyBillUser", + }}}, + bson.D{{Key: "$addFields", Value: bson.M{ + "legacyBillCountry": bson.M{"$toUpper": bson.M{"$ifNull": bson.A{ + bson.M{"$first": "$legacyBillUser.countryCode"}, + "", + }}}, + }}}, + bson.D{{Key: "$match", Value: bson.M{"legacyBillCountry": bson.M{"$in": codes}}}}, + ) + } + pipeline = append(pipeline, bson.D{{Key: "$group", Value: bson.M{ + "_id": nil, + "n": bson.M{"$sum": 1}, + "amount": bson.M{"$sum": bson.M{"$ifNull": bson.A{"$amount", 0}}}, + }}}) + cursor, err := s.database.Collection(legacyGoldWithdrawCollection).Aggregate(ctx, pipeline) + if err != nil { + return fmt.Errorf("aggregate legacy gold withdraw for %s: %w", s.config.AppCode, err) + } + defer cursor.Close(ctx) + if cursor.Next(ctx) { + var row struct { + N int64 `bson:"n"` + Amount any `bson:"amount"` + } + if err := cursor.Decode(&row); err != nil { + return fmt.Errorf("decode legacy gold withdraw for %s: %w", s.config.AppCode, err) + } + overview.Withdrawal.TransferCount = row.N + overview.Withdrawal.TransferUsdMinor = legacyDecimalToMinor(row.Amount) + } + return cursor.Err() +} + func (s *MongoRechargeBillSource) overviewGooglePaid(ctx context.Context, filter bson.M, query legacyRechargeBillQuery, overview *rechargeBillOverviewDTO) error { rechargeType := strings.ToLower(strings.TrimSpace(query.RechargeType)) if rechargeType != "" && rechargeType != "google_play_recharge" { diff --git a/services/wallet-service/internal/storage/mysql/recharge_overview_repository.go b/services/wallet-service/internal/storage/mysql/recharge_overview_repository.go index 3376fcd9..fe55967e 100644 --- a/services/wallet-service/internal/storage/mysql/recharge_overview_repository.go +++ b/services/wallet-service/internal/storage/mysql/recharge_overview_repository.go @@ -218,7 +218,7 @@ func (r *Repository) overviewSalaryTransferStats(ctx context.Context, query ledg args = append(args, query.Status) } if query.RegionID > 0 { - where += ` AND COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata, '$.region_id')) AS SIGNED), 0) = ?` + where += ` AND COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.region_id')) AS SIGNED), 0) = ?` args = append(args, query.RegionID) } if query.StartAtMS > 0 { @@ -231,7 +231,7 @@ func (r *Repository) overviewSalaryTransferStats(ctx context.Context, query ledg } var stats ledger.RechargeBillSalaryTransferStats err := r.db.QueryRowContext(ctx, ` - SELECT COUNT(*), COALESCE(SUM(COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata, '$.salary_usd_minor')) AS SIGNED), 0)), 0) + SELECT COUNT(*), COALESCE(SUM(COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.salary_usd_minor')) AS SIGNED), 0)), 0) FROM wallet_transactions `+where, args...,