From 0802c372b0d0c1126a923202dd5f5e3c9f0c7730 Mon Sep 17 00:00:00 2001 From: JNG <365252428@qq.com> Date: Tue, 3 Feb 2026 16:46:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/bundle/bundle.pb.go | 13 ++- pkg/service/bundle/pay.go | 180 +++++++++++++++++++++++++++++++++++--- 2 files changed, 181 insertions(+), 12 deletions(-) diff --git a/api/bundle/bundle.pb.go b/api/bundle/bundle.pb.go index 1d2b76f..aecb116 100644 --- a/api/bundle/bundle.pb.go +++ b/api/bundle/bundle.pb.go @@ -3387,6 +3387,7 @@ type AddInfo struct { Num int32 `protobuf:"varint,2,opt,name=num,proto3" json:"num"` ValueAddUUID string `protobuf:"bytes,3,opt,name=valueAddUUID,proto3" json:"valueAddUUID"` EquityType int32 `protobuf:"varint,4,opt,name=equityType,proto3" json:"equityType"` + ServiceType int32 `protobuf:"varint,5,opt,name=serviceType,proto3" json:"serviceType"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -3449,6 +3450,13 @@ func (x *AddInfo) GetEquityType() int32 { return 0 } +func (x *AddInfo) GetServiceType() int32 { + if x != nil { + return x.ServiceType + } + return 0 +} + type OrderAddRecord struct { state protoimpl.MessageState `protogen:"open.v1"` BundleUuid string `protobuf:"bytes,1,opt,name=bundleUuid,proto3" json:"bundleUuid"` @@ -16196,14 +16204,15 @@ const file_pb_bundle_proto_rawDesc = "" + "\baddInfos\x18' \x03(\v2\x0f.bundle.AddInfoR\baddInfos\x12 \n" + "\vreSignature\x18( \x01(\x05R\vreSignature\x12\"\n" + "\fpurchaseType\x18) \x01(\x04R\fpurchaseType\x12*\n" + - "\x10renewalOrderUUID\x18* \x01(\tR\x10renewalOrderUUID\"y\n" + + "\x10renewalOrderUUID\x18* \x01(\tR\x10renewalOrderUUID\"\x9b\x01\n" + "\aAddInfo\x12\x18\n" + "\aorderNo\x18\x01 \x01(\tR\aorderNo\x12\x10\n" + "\x03num\x18\x02 \x01(\x05R\x03num\x12\"\n" + "\fvalueAddUUID\x18\x03 \x01(\tR\fvalueAddUUID\x12\x1e\n" + "\n" + "equityType\x18\x04 \x01(\x05R\n" + - "equityType\"\xdd\x03\n" + + "equityType\x12 \n" + + "\vserviceType\x18\x05 \x01(\x05R\vserviceType\"\xdd\x03\n" + "\x0eOrderAddRecord\x12\x1e\n" + "\n" + "bundleUuid\x18\x01 \x01(\tR\n" + diff --git a/pkg/service/bundle/pay.go b/pkg/service/bundle/pay.go index d2e5328..a939582 100644 --- a/pkg/service/bundle/pay.go +++ b/pkg/service/bundle/pay.go @@ -7,6 +7,7 @@ import ( "fmt" "fonchain-fiee/api/accountFiee" "fonchain-fiee/api/bundle" + castProto "fonchain-fiee/api/cast" "fonchain-fiee/api/order" "fonchain-fiee/api/payment" "fonchain-fiee/pkg/cache" @@ -602,6 +603,20 @@ func AntomWebhook(c *gin.Context) { } fmt.Println("resp.Status:", resp.Status) if resp.Status == "paid" { + //添加余额 + orderLimit, err := service.BundleProvider.OrderListByOrderNo(context.Background(), &bundle.OrderInfoByOrderNoRequest{ + OrderNo: resp.OutTradeNo, + }) + if err != nil { + service.Error(c, err) + return + } + //获取上一笔订单信息:如果没有查询到代表首次购买 需要判断异常情况 + lastOrderInfo, _ := service.BundleProvider.OrderRecordsDetail(context.Background(), &bundle.OrderRecordsDetailRequest{ + CustomerID: strconv.FormatUint(orderLimit.UserId, 10), + Status: 2, + }) + //支付成功 _, updateStatusErr := service.BundleProvider.UpdateOrderRecordByOrderNo(context.Background(), &bundle.OrderRecord{ OrderNo: resp.OutTradeNo, @@ -625,14 +640,6 @@ func AntomWebhook(c *gin.Context) { return } - //添加余额 - orderLimit, err := service.BundleProvider.OrderListByOrderNo(context.Background(), &bundle.OrderInfoByOrderNoRequest{ - OrderNo: resp.OutTradeNo, - }) - if err != nil { - service.Error(c, err) - return - } //购买套餐 switch orderLimit.Type { case common.OrderTypePackage: @@ -698,9 +705,9 @@ func AntomWebhook(c *gin.Context) { service.Error(c, err) return } - if orderLimit.PurchaseType == 1 { + // 处理媒体账号绑定逻辑 + handleMediaAccountBinding(orderLimit.UserId, orderLimit.PurchaseType, int(orderLimit.AccountNumber), lastOrderInfo) - } } service.Success(c) } @@ -763,3 +770,156 @@ func HomePageRoll(c *gin.Context) { service.Success(c, roll) return } + +// MediaAccount 媒体账号信息 +type MediaAccount struct { + UUID string + PlatformID uint32 +} + +// handleMediaAccountBinding 处理媒体账号绑定逻辑(统一解绑方法) +// userId: 用户ID +// purchaseType: 购买类型(新购/续费) +// currentAccountNum: 本次购买的账号数 +// lastOrderInfo: 上次订单信息 +func handleMediaAccountBinding(userId uint64, purchaseType int32, currentAccountNum int, lastOrderInfo *bundle.OrderRecordsDetailResponse) { + // 1. 获取用户的媒体账号列表 + MediaList, err := service.CastProvider.MediaUserList(context.Background(), &castProto.MediaUserListReq{ + Page: 1, + PageSize: 999, + ArtistUuid: strconv.FormatUint(userId, 10), + }) + + if err != nil { + logger.Warnf("Failed to get media list for user %d: %v", userId, err) + return + } + + if MediaList == nil || len(MediaList.Data) == 0 { + logger.Infof("No media accounts found for user %d", userId) + return + } + + // 2. 收集过期的媒体账号信息(包含平台ID) + var expiredAccounts []MediaAccount + for _, media := range MediaList.Data { + if media.Expired == 1 { + expiredAccounts = append(expiredAccounts, MediaAccount{ + UUID: media.MediaAccountUuid, + PlatformID: media.PlatformID, + }) + } + } + + if len(expiredAccounts) == 0 { + logger.Infof("No expired media accounts found for user %d", userId) + return + } + + // 3. 判断是否首次购买 + isFirstPurchase := lastOrderInfo == nil || lastOrderInfo.OrderRecord == nil || len(lastOrderInfo.OrderRecord.AddInfos) == 0 + + // 4. 根据购买类型处理解绑逻辑 + var accountsToUnbind []string + + if purchaseType == common.NewPurchaseOrder { + // 新购订单 + if isFirstPurchase { + logger.Infof("First time purchase for user %d, no need to unbind accounts", userId) + return + } + // 非首次购买的新购:解绑所有过期账号 + for _, account := range expiredAccounts { + accountsToUnbind = append(accountsToUnbind, account.UUID) + } + logger.Infof("New purchase for user %d, will unbind all %d expired accounts", userId, len(accountsToUnbind)) + + } else if purchaseType == common.RenewalOrder { + // 续费订单 + if isFirstPurchase { + logger.Infof("No previous order found for renewal user %d, no need to unbind accounts", userId) + return + } + + // 获取上次购买的账号数 + var lastAccountNum int + for _, addInfo := range lastOrderInfo.OrderRecord.AddInfos { + if addInfo.ServiceType == 4 { // ServiceType 4 表示账号服务 + lastAccountNum += int(addInfo.Num) + } + } + + logger.Infof("Renewal order for user %d: current=%d, last=%d, expired=%d", + userId, currentAccountNum, lastAccountNum, len(expiredAccounts)) + + // 如果当前购买数量 >= 上次购买数量,不需要解绑 + if currentAccountNum >= lastAccountNum { + logger.Infof("No need to unbind accounts for renewal user %d: current(%d) >= last(%d)", + userId, currentAccountNum, lastAccountNum) + return + } + + // 当前购买数量 < 上次购买数量,按优先级解绑差额部分 + needUnbindCount := lastAccountNum - currentAccountNum + + // 定义平台优先级(数字越小优先级越高,优先保留) + // TikTok(1) > DM(4) > Bluesky(5) > Instagram(3) > YouTube(2) + platformPriority := map[uint32]int{ + 1: 1, // TikTok - 最高优先级,最后解绑 + 4: 2, // DM + 5: 3, // Bluesky + 3: 4, // Instagram + 2: 5, // YouTube - 最低优先级,最先解绑 + } + + // 按优先级排序(优先级低的排在前面,先解绑) + sortedAccounts := make([]MediaAccount, len(expiredAccounts)) + copy(sortedAccounts, expiredAccounts) + + for i := 0; i < len(sortedAccounts)-1; i++ { + for j := 0; j < len(sortedAccounts)-i-1; j++ { + priority1 := platformPriority[sortedAccounts[j].PlatformID] + priority2 := platformPriority[sortedAccounts[j+1].PlatformID] + if priority1 == 0 { + priority1 = 999 + } + if priority2 == 0 { + priority2 = 999 + } + // 降序排列:优先级数字大的排在前面(先解绑) + if priority1 < priority2 { + sortedAccounts[j], sortedAccounts[j+1] = sortedAccounts[j+1], sortedAccounts[j] + } + } + } + + // 取前needUnbindCount个账号进行解绑 + if needUnbindCount > len(sortedAccounts) { + needUnbindCount = len(sortedAccounts) + } + + for i := 0; i < needUnbindCount; i++ { + accountsToUnbind = append(accountsToUnbind, sortedAccounts[i].UUID) + } + logger.Infof("Renewal downgrade for user %d, will unbind %d accounts by priority", userId, len(accountsToUnbind)) + } + + // 5. 执行解绑操作 + if len(accountsToUnbind) > 0 { + successCount := 0 + failCount := 0 + for _, accountUUID := range accountsToUnbind { + if _, err := service.CastProvider.UpdateMediaAccInfo(context.Background(), &castProto.UpdateMediaAccInfoReq{ + MediaAccountUuid: accountUUID, + Expired: 2, + ExpiredSource: castProto.ExpiredMediaSourceENUM_UpdateMediaSource_CHARGE, + }); err != nil { + logger.Errorf("Failed to unbind account %s for user %d: %v", accountUUID, userId, err) + failCount++ + } else { + successCount++ + } + } + logger.Infof("Unbind completed for user %d: success=%d, fail=%d", userId, successCount, failCount) + } +}