package bundle import ( "context" "encoding/json" "errors" "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" "fonchain-fiee/pkg/config" "fonchain-fiee/pkg/model/login" "fonchain-fiee/pkg/service" "fonchain-fiee/pkg/service/bundle/common" bundleModel "fonchain-fiee/pkg/service/bundle/model" "io" "math" "net/http" "strconv" "time" "dubbo.apache.org/dubbo-go/v3/common/logger" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" ) func CreateStripeCheckoutSession(c *gin.Context) { var req order.CreateStripeCheckoutSessionRequest if err := c.ShouldBindBodyWith(&req, binding.JSON); err != nil { service.Error(c, err) return } // 获取 用户信息 userInfo := login.GetUserInfoFromC(c) // 检查 订单信息 detail, detailErr := service.BundleProvider.OrderRecordsDetail(context.Background(), &bundle.OrderRecordsDetailRequest{ OrderNo: req.OutTradeNo, }) if detailErr != nil { service.Error(c, detailErr) return } fmt.Println("detail.OrderRecord.CustomerID :", detail.OrderRecord.CustomerID) // 判断 是否是 本人操作 if strconv.FormatUint(userInfo.ID, 10) != detail.OrderRecord.CustomerID { service.Error(c, errors.New(common.NotMatchOrderInfo)) return } fmt.Println("detail.OrderRecord.TotalAmount :", detail.OrderRecord.TotalAmount) fmt.Println("req.ProductAllPrice :", req.ProductAllPrice) fmt.Println("detail.OrderRecord.TotalAmount*100 :", detail.OrderRecord.TotalAmount*100) //金额校验 orderAmountInCents := int64(math.Round(float64(detail.OrderRecord.TotalAmount * 100))) reqAmountInCents := int64(math.Round(float64(req.ProductAllPrice))) if orderAmountInCents != reqAmountInCents { fmt.Println("111111111111111111111111111111111111") service.Error(c, errors.New(common.InvalidOrderAmount)) return } fmt.Println("detail.OrderRecord.Status :", detail.OrderRecord.Status) fmt.Println("detail.OrderRecord.CheckoutSessionId :", detail.OrderRecord.CheckoutSessionId) fmt.Println("detail.OrderRecord.PayTime :", detail.OrderRecord.PayTime) // 如果 当前订单 是 已签未支付 且 存在 checkoutSessionId 需要 查询 支付结果 if detail.OrderRecord.Status == bundleModel.OrderSigned && detail.OrderRecord.CheckoutSessionId != "" && detail.OrderRecord.PayTime == "" { // 查询支付结果 stripeInfosRes, stripeInfosErr := service.OrderProvider.QueryStripeInfoByCheckSessionIds(context.Background(), &order.QueryStripeInfoRequest{ CheckoutSessionIds: []string{detail.OrderRecord.CheckoutSessionId}, }) if stripeInfosErr != nil { service.Error(c, errors.New(common.ErrorQueryStripeInfo)) return } if stripeInfosRes != nil && len(stripeInfosRes.StripeInfos) > 0 { for _, stripeInfo := range stripeInfosRes.StripeInfos { if stripeInfo.OutTradeNo == detail.OrderRecord.OrderNo && stripeInfo.PaymentIntentStatus == "paid" { _, updateOrderRecordErr := service.BundleProvider.UpdateOrderRecord(context.Background(), &bundle.OrderRecord{ Uuid: detail.OrderRecord.Uuid, Status: bundleModel.OrderPaid, PayTime: common.GetBeijingTime(), }) fmt.Println("detail.OrderRecord.Uuid :", detail.OrderRecord.Uuid) if updateOrderRecordErr != nil { service.Error(c, detailErr) return } service.Success(c, &service.Response{ Msg: common.HadPay, Code: 0, }) return } } } } //调用微服务获取支付地址 result, err := service.OrderProvider.CreateStripeCheckoutSession(context.Background(), &req) if err != nil { service.Error(c, err) return } fmt.Println("result.CheckoutSessionId :", result.CheckoutSessionId) fmt.Println("result.CheckoutSessionUrl :", result.CheckoutSessionUrl) //更新订单状态 _, updateOrderRecordErr := service.BundleProvider.UpdateOrderRecord(context.Background(), &bundle.OrderRecord{ Uuid: detail.OrderRecord.Uuid, CheckoutSessionId: result.CheckoutSessionId, CheckoutSessionUrl: result.CheckoutSessionUrl, }) if updateOrderRecordErr != nil { service.Error(c, updateOrderRecordErr) return } service.Success(c, result) } func CreateAntomPay(c *gin.Context) { var req order.CreateStripeCheckoutSessionRequest if err := c.ShouldBindBodyWith(&req, binding.JSON); err != nil { service.Error(c, err) return } // 获取 用户信息 userInfo := login.GetUserInfoFromC(c) // 创建审批 防止重复提交 lockKey := fmt.Sprintf("create_antom_pay_%v", userInfo.ID) reply := cache.RedisClient.SetNX(lockKey, 0, 5*time.Second) if !reply.Val() { service.Error(c, errors.New(common.CreateBundleFailed)) return } // outTradeNo就是orderNo,根据这个去查询子表的source,如果是2就时单独的子套餐,如果是1就是主套餐 orderLimit, err := service.BundleProvider.OrderListByOrderNo(context.Background(), &bundle.OrderInfoByOrderNoRequest{ OrderNo: req.OutTradeNo, }) if err != nil { fmt.Println("=============== antom创建支付,查询订单source报错:", err) logger.Errorf("=============== antom创建支付,查询订单source报错:", err) service.Error(c, err) return } currencyType := 0 if req.ProductPriceCurrency == "cny" || req.ProductPriceCurrency == "CNY" { currencyType = 1 } if req.ProductPriceCurrency == "usd" || req.ProductPriceCurrency == "USD" { currencyType = 2 } bundleName := "" // 套餐名称 if orderLimit != nil && orderLimit.Type == 1 { // 这儿的type实际就是source // 检查 订单信息 type是1说明既有主套餐又有增值服务 detail, detailErr := service.BundleProvider.OrderRecordsDetail(context.Background(), &bundle.OrderRecordsDetailRequest{ OrderNo: req.OutTradeNo, }) if detailErr != nil { fmt.Println("=============== antom创建支付,查询主订单信息报错:", detailErr) logger.Errorf("=============== antom创建支付,查询主订单信息报错:", detailErr) service.Error(c, detailErr) return } fmt.Println("detail.OrderRecord.Status :", detail.OrderRecord.Status) fmt.Println("detail.OrderRecord.CheckoutSessionId :", detail.OrderRecord.CheckoutSessionId) fmt.Println("detail.OrderRecord.PayTime :", detail.OrderRecord.PayTime) bundleName = detail.OrderRecord.BundleName // 主套餐 // 如果 当前订单 是 已签未支付 且 存在 checkoutSessionId 需要 查询 支付结果 if detail.OrderRecord.Status == bundleModel.OrderSigned && detail.OrderRecord.CheckoutSessionId != "" && detail.OrderRecord.PayTime == "" { // 查询支付结果 stripeInfosRes, stripeInfosErr := service.PaymentProvider.QueryAntomPayByCheckoutSessionId(context.Background(), &payment.AntomPayQueryRequest{ CheckoutSessionIds: []string{detail.OrderRecord.CheckoutSessionId}, }) if stripeInfosErr != nil { service.Error(c, errors.New(common.ErrorQueryStripeInfo)) return } if stripeInfosRes != nil && len(stripeInfosRes.Infos) > 0 { for _, info := range stripeInfosRes.Infos { if info.OutTradeNo == detail.OrderRecord.OrderNo && info.Status == "paid" { // 更新主套餐和子套餐 TODO // 更新子套餐,TODO _, updateStatusErr := service.BundleProvider.UpdateOrderRecordByOrderNo(context.Background(), &bundle.OrderRecord{ OrderNo: req.OutTradeNo, PayTime: common.GetBeijingTime(), Status: bundleModel.OrderPaid, }) fmt.Println("detail.OrderRecord.Uuid :", detail.OrderRecord.Uuid) if updateStatusErr != nil { fmt.Println("=============== antom创建支付,更新主套餐和子套餐支付状态报错:", updateStatusErr) logger.Errorf("=============== antom创建支付,更新主套餐和子套餐支付状态报错:", updateStatusErr) service.Error(c, updateStatusErr) return } service.Success(c, &service.Response{ Msg: common.HadPay, Code: 0, }) return } } } } fmt.Println("detail.OrderRecord.CustomerID :", detail.OrderRecord.CustomerID) // 判断 是否是 本人操作 if strconv.FormatUint(userInfo.ID, 10) != detail.OrderRecord.CustomerID { service.Error(c, errors.New(common.NotMatchOrderInfo)) return } fmt.Println("detail.OrderRecord.TotalAmount :", detail.OrderRecord.TotalAmount) fmt.Println("req.ProductAllPrice :", req.ProductAllPrice) fmt.Println("detail.OrderRecord.TotalAmount*100 :", detail.OrderRecord.TotalAmount*100) //金额校验 orderAmountInCents := int64(math.Round(float64(detail.OrderRecord.TotalAmount) * 100)) reqAmountInCents := req.ProductAllPrice if orderAmountInCents != reqAmountInCents { fmt.Println("111111111111111111111111111111111111") service.Error(c, errors.New(common.InvalidOrderAmount)) return } } if orderLimit != nil && orderLimit.Type == 2 { res, listErr := service.BundleProvider.OnlyAddValueListByOrderNo(context.Background(), &bundle.OnlyAddValueListByOrderNoRequest{ OrderNo: req.OutTradeNo, }) if listErr != nil { fmt.Println("=============== antom创建支付,查询单独增值服务列表报错:", listErr) logger.Errorf("=============== antom创建支付,查询单独增值服务列表报错:", listErr) service.Error(c, listErr) return } if res != nil && len(res.AddBundleInfos) > 0 { firstAddBundle := res.AddBundleInfos[0] // 如果 当前订单 是 已签未支付 且 存在 checkoutSessionId 需要 查询 支付结果 if firstAddBundle.PaymentStatus == bundleModel.OrderSigned && firstAddBundle.CheckOutSessionId != "" { // 查询支付结果 stripeInfosRes, stripeInfosErr := service.PaymentProvider.QueryAntomPayByCheckoutSessionId(context.Background(), &payment.AntomPayQueryRequest{ CheckoutSessionIds: []string{firstAddBundle.CheckOutSessionId}, }) if stripeInfosErr != nil { service.Error(c, errors.New(common.ErrorQueryStripeInfo)) return } if stripeInfosRes != nil && len(stripeInfosRes.Infos) > 0 { for _, info := range stripeInfosRes.Infos { if info.OutTradeNo == firstAddBundle.OrderNo && info.Status == "paid" { // 更新子套餐,TODO _, updateStatusErr := service.BundleProvider.UpdateOrderRecordByOrderNo(context.Background(), &bundle.OrderRecord{ OrderNo: req.OutTradeNo, PayTime: common.GetBeijingTime(), Status: bundleModel.OrderPaid, }) fmt.Println("req.OutTradeNo :", req.OutTradeNo) if updateStatusErr != nil { fmt.Println("=============== antom创建支付,更新子套餐支付状态报错:", updateStatusErr) logger.Errorf("=============== antom创建支付,更新子套餐支付状态报错:", updateStatusErr) service.Error(c, updateStatusErr) return } service.Success(c, &service.Response{ Msg: common.HadPay, Code: 0, }) return } } } } fmt.Println("firstAddBundle.CustomerID :", firstAddBundle.CustomerID) // 判断 是否是 本人操作 if strconv.FormatUint(userInfo.ID, 10) != firstAddBundle.CustomerID { service.Error(c, errors.New(common.NotMatchOrderInfo)) return } orderAmountInCents := int64(0.0) for _, info := range res.AddBundleInfos { temp := int64(math.Round(float64(info.Amount) * 100)) orderAmountInCents += temp } fmt.Println("orderAmountInCents :", orderAmountInCents) fmt.Println("req.ProductAllPrice :", req.ProductAllPrice) //金额校验 reqAmountInCents := req.ProductAllPrice if orderAmountInCents != reqAmountInCents { fmt.Println("111111111111111111111111111111111111") service.Error(c, errors.New(common.InvalidOrderAmount)) return } } } if req.ProductAllPrice == 0 { ////创建对账单 todo 待修改 _, err = service.BundleProvider.CreateReconciliation(context.Background(), &bundle.ReconciliationInfo{ BundleOrderOn: req.OutTradeNo, BundleAddOrderOn: req.OutTradeNo, UserName: userInfo.Name, UserTel: userInfo.TelNum, BundleName: bundleName, PayAmount: float32(req.ProductAllPrice), CurrencyType: int32(currencyType), PayStatus: 2, PayTime: common.GetBeijingTime(), UserID: userInfo.ID, SerialNumber: common.GetZeroUuid(), }) if err != nil { fmt.Println("=============== antom创建支付,创建对账单报错:", err) logger.Errorf("=============== antom创建支付,创建对账单报错:", err) service.Error(c, err) return } _, updateStatusErr := service.BundleProvider.UpdateOrderRecordByOrderNo(context.Background(), &bundle.OrderRecord{ OrderNo: req.OutTradeNo, PayTime: common.GetBeijingTime(), Status: bundleModel.OrderPaid, }) if updateStatusErr != nil { fmt.Println("=============== antom创建支付,更新订单报错:", updateStatusErr) logger.Errorf("=============== antom创建支付,更新订单报错:", updateStatusErr) service.Error(c, updateStatusErr) return } switch orderLimit.Type { case common.OrderTypePackage: //如果是购买套餐 1:创建新的余量信息CreateBundleBalance 2 添加扩展记录BundleExtend _, err = service.BundleProvider.CreateBundleBalance(context.Background(), &bundle.CreateBundleBalanceReq{ UserId: int32(orderLimit.UserId), OrderUUID: orderLimit.OrderUUID, //AccountNumber: orderLimit.AccountNumber, //VideoNumber: orderLimit.VideoNumber, //ImageNumber: orderLimit.ImageNumber, //DataAnalysisNumber: orderLimit.DataNumber, ExpansionPacksNumber: 1, }) if err != nil { fmt.Println("=============== antom创建支付,OrderTypePackage报错:", err) logger.Errorf("=============== antom创建支付,OrderTypePackage报错:", err) service.Error(c, err) return } case common.OrderTypeAddon: //如果是购买增值服务 1:修改余量信息AddBundleBalance 2 添加扩展记录BundleExtend //_, err = service.BundleProvider.AddBundleBalance(context.Background(), &bundle.AddBundleBalanceReq{ // UserId: int32(orderLimit.UserId), // OrderUUID: orderLimit.OrderUUID, // AccountNumber: orderLimit.AccountNumber, // VideoNumber: orderLimit.VideoNumber, // ImageNumber: orderLimit.ImageNumber, // DataAnalysisNumber: orderLimit.DataNumber, // ExpansionPacksNumber: 1, //}) //if err != nil { // fmt.Println("=============== antom创建支付,OrderTypePackage报错:", err) // logger.Errorf("=============== antom创建支付,OrderTypePackage报错:", err) // service.Error(c, err) // return //} default: fmt.Println("=============== antom创建支付,无效的订单类型:", err) logger.Errorf("=============== antom创建支付,无效的订单类型:", err) service.Error(c, errors.New("无效的订单类型")) return } var timeUnit uint32 switch orderLimit.Unit { case "天": timeUnit = common.TimeUnitDay case "月": timeUnit = common.TimeUnitMonth case "年": timeUnit = common.TimeUnitYear default: timeUnit = 0 } _, err = service.BundleProvider.BundleExtend(context.Background(), &bundle.BundleExtendRequest{ UserId: int64(orderLimit.UserId), AccountAdditional: uint32(orderLimit.AccountNumber), VideoAdditional: uint32(orderLimit.VideoNumber), ImagesAdditional: uint32(orderLimit.ImageNumber), DataAdditional: uint32(orderLimit.DataNumber), AvailableDurationAdditional: uint32(orderLimit.Duration), TimeUnit: timeUnit, AssociatedorderNumber: req.OutTradeNo, //增值服务订单号 Type: 2, //自行购买 OperatorName: orderLimit.UserName, OperatorId: orderLimit.UserId, }) if err != nil { service.Error(c, err) return } service.Success(c) return } var antomReq payment.CreatePayRequest antomReq.Payee = "Antom" antomReq.Platform = "antom" antomReq.ChannelType = "antom" antomReq.ProductDescription = req.ProductDescription antomReq.BusinessType = "useless" antomReq.Domain = "fiee" antomReq.Amount = req.ProductAllPrice antomReq.Currency = req.ProductPriceCurrency antomReq.OutTradeNo = req.OutTradeNo antomReq.ReturnUrl = req.SuccessUrl //调用微服务获取支付地址 result, err := service.PaymentProvider.CreatePay(context.Background(), &antomReq) if err != nil { service.Error(c, err) return } fmt.Println("result.CheckoutSessionId :", result.CheckoutSessionId) fmt.Println("result.Url :", result.Url) //更新订单url和checkSessionId _, updateStatusErr := service.BundleProvider.UpdateOrderRecordByOrderNo(context.Background(), &bundle.OrderRecord{ OrderNo: req.OutTradeNo, CheckoutSessionId: result.CheckoutSessionId, CheckoutSessionUrl: result.Url, }) fmt.Println("=====================================") resp := &order.CreateStripeCheckoutSessionResponse{} resp.CheckoutSessionUrl = result.Url resp.CheckoutSessionId = result.CheckoutSessionId fmt.Println("req.OutTradeNo :", req.OutTradeNo) if updateStatusErr != nil { fmt.Println("=============== antom创建支付,更新订单的请求id报错:", updateStatusErr) logger.Errorf("=============== antom创建支付,更新订单的请求id报错:", updateStatusErr) service.Error(c, updateStatusErr) return } fmt.Println("resp:", resp) ////创建对账单 todo 待修改 _, err = service.BundleProvider.CreateReconciliation(context.Background(), &bundle.ReconciliationInfo{ BundleOrderOn: req.OutTradeNo, BundleAddOrderOn: req.OutTradeNo, UserName: userInfo.Name, UserTel: userInfo.TelNum, BundleName: bundleName, PayAmount: float32(req.ProductAllPrice) / 100, // 以后别用float存钱,无语 CurrencyType: int32(currencyType), PayStatus: 1, UserID: userInfo.ID, SerialNumber: result.CheckoutSessionId, }) if err != nil { fmt.Println("=============== antom创建支付,创建对账单报错:", err) logger.Errorf("=============== antom创建支付,创建对账单报错:", err) service.Error(c, err) return } service.Success(c, resp) } func StripeCheckoutSessionWebhook(c *gin.Context) { var req order.GetCheckoutWebhookRequest c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, int64(65536)) payloadBytes, err := io.ReadAll(c.Request.Body) if err != nil { service.Error(c, err) return } req.Payload = string(payloadBytes) req.Signature = c.GetHeader("Stripe-Signature") req.WebhookKey = config.Webhookkey fmt.Printf("webhookKey:%s\n", req.WebhookKey) resp, err := service.OrderProvider.CommonCheckoutWebhook(c, &req) if err != nil { service.Error(c, err) return } fmt.Println("resp.PaymentIntentStatus:", resp.PaymentIntentStatus) if resp.PaymentIntentStatus == "paid" { //支付成功 _, updateStatusErr := service.BundleProvider.UpdateOrderRecordByOrderNo(context.Background(), &bundle.OrderRecord{ OrderNo: resp.OutTradeNo, PayTime: common.GetBeijingTime(), Status: bundleModel.OrderPaid, }) if updateStatusErr != nil { service.Error(c, err) return } } service.Success(c) } func AntomWebhook(c *gin.Context) { var err error data, err := io.ReadAll(c.Request.Body) if err != nil { service.Error(c, err) return } // 将数据转换为字符串形式并记录日志(如果数据过大则不记录) dataStr := string(data) fmt.Println("================ Antom回调参数:", dataStr) // 将读取到的数据解析为 map[string]interface{} var reqMap map[string]interface{} if err := json.Unmarshal(data, &reqMap); err != nil { service.Error(c, err) return } // 提取需要的字段 notifyType, _ := reqMap["notifyType"].(string) resultMap, resultExists := reqMap["result"].(map[string]interface{}) if !resultExists { service.Error(c, errors.New("result 字段不存在或类型错误")) return } requestId, _ := reqMap["paymentRequestId"].(string) paymentId, _ := reqMap["paymentId"].(string) paymentTime, _ := reqMap["paymentTime"].(string) // 提取 result 字段中的子字段 resultStatus, _ := resultMap["resultStatus"].(string) resultMessage, _ := resultMap["resultMessage"].(string) // 打印提取的字段(可以根据需要处理) fmt.Println("通知类型:", notifyType) fmt.Println("订单号:", requestId) fmt.Println("支付ID:", paymentId) fmt.Println("支付时间:", paymentTime) fmt.Println("支付结果状态:", resultStatus) fmt.Println("支付结果消息:", resultMessage) /* * S: 当 notifyType 为PAYMENT_RESULT时,表示支付成功;当 notifyType 为PAYMENT_PENDING时,表示支付处理中。 * F: 表示支付失败。 * */ params := &payment.AntomNotifyPayRequest{ NotifyType: notifyType, RequestId: requestId, PaymentId: paymentId, PaymentTime: paymentTime, ResultStatus: resultStatus, ResultMessage: resultMessage, ChannelCode: "Antom", // fiee对应payment的渠道码 } resp, err := service.PaymentProvider.AntomWebhook(c, params) if err != nil { service.Error(c, err) return } 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, PayTime: common.GetBeijingTime(), Status: bundleModel.OrderPaid, }) if updateStatusErr != nil { service.Error(c, err) return } // 更新对账单 _, err = service.BundleProvider.UpdateReconciliationStatusBySerialNumber(context.Background(), &bundle.UpdateStatusAndPayTimeBySerialNumber{ PayTime: paymentTime, PaymentStatus: 2, SerialNumber: requestId, }) if err != nil { fmt.Println("=============== antom回调,更新对账单报错:", err) logger.Errorf("=============== antom回调,更新对账单报错:", err) service.Error(c, err) return } //购买套餐 switch orderLimit.Type { case common.OrderTypePackage: //如果是购买套餐 1:创建新的余量信息CreateBundleBalance 2 添加扩展记录BundleExtend _, err = service.BundleProvider.CreateBundleBalance(context.Background(), &bundle.CreateBundleBalanceReq{ UserId: int32(orderLimit.UserId), OrderUUID: orderLimit.OrderUUID, //AccountNumber: orderLimit.AccountNumber, //VideoNumber: orderLimit.VideoNumber, //ImageNumber: orderLimit.ImageNumber, //DataAnalysisNumber: orderLimit.DataNumber, ExpansionPacksNumber: 1, }) if err != nil { service.Error(c, err) return } case common.OrderTypeAddon: //如果是购买增值服务 1:修改余量信息AddBundleBalance 2 添加扩展记录BundleExtend //_, err = service.BundleProvider.AddBundleBalance(context.Background(), &bundle.AddBundleBalanceReq{ // UserId: int32(orderLimit.UserId), // OrderUUID: orderLimit.OrderUUID, // AccountNumber: orderLimit.AccountNumber, // VideoNumber: orderLimit.VideoNumber, // ImageNumber: orderLimit.ImageNumber, // DataAnalysisNumber: orderLimit.DataNumber, // ExpansionPacksNumber: 1, //}) //if err != nil { // service.Error(c, err) // return //} default: service.Error(c, errors.New("无效的订单类型")) return } var timeUnit uint32 switch orderLimit.Unit { case "天": timeUnit = common.TimeUnitDay case "月": timeUnit = common.TimeUnitMonth case "年": timeUnit = common.TimeUnitYear default: timeUnit = 0 } _, err = service.BundleProvider.BundleExtend(context.Background(), &bundle.BundleExtendRequest{ UserId: int64(orderLimit.UserId), AccountAdditional: uint32(orderLimit.AccountNumber), VideoAdditional: uint32(orderLimit.VideoNumber), ImagesAdditional: uint32(orderLimit.ImageNumber), DataAdditional: uint32(orderLimit.DataNumber), AvailableDurationAdditional: uint32(orderLimit.Duration), TimeUnit: timeUnit, AssociatedorderNumber: resp.OutTradeNo, //增值服务订单号 Type: 2, //自行购买 OperatorName: orderLimit.UserName, OperatorId: orderLimit.UserId, CompetitiveAdditional: uint32(orderLimit.CompetitiveAdditional), //添加竞品数 }) if err != nil { service.Error(c, err) return } // 处理媒体账号绑定逻辑 handleMediaAccountBinding(orderLimit.UserId, orderLimit.PurchaseType, int(orderLimit.AccountNumber), lastOrderInfo) } service.Success(c) } func HomePageRoll(c *gin.Context) { //var req order.HomePageRollRequest res, err := service.BundleProvider.OrderRecordsListV2(context.Background(), &bundle.OrderRecordsRequestV2{ Page: 1, PageSize: 5, }) if err != nil { service.Error(c, err) return } if len(res.BundleInfo) == 0 { service.Success(c, nil) return } type Roll struct { Tel string `json:"tel"` Name string `json:"name"` } var userIds []int64 for _, i := range res.BundleInfo { userIds = append(userIds, i.CustomerId) } userListResp, err := service.AccountFieeProvider.UserList(context.Background(), &accountFiee.UserListRequest{ Ids: userIds, Domain: "app", }) if err != nil { service.Error(c, err) return } // 建立用户ID -> 用户信息映射 userMap := make(map[int64]*accountFiee.UserListInfo, len(userListResp.UserList)) for _, u := range userListResp.UserList { userMap[int64(u.Id)] = u } var roll []Roll for _, i := range res.BundleInfo { tel := "" name := i.BundleName // Use bundle name as fallback if userInfo, exists := userMap[i.CustomerId]; exists && userInfo != nil { tel = userInfo.TelNum if len(tel) >= 2 { masked := "" for j := 0; j < len(tel)-2; j++ { masked += "*" } tel = masked + tel[len(tel)-2:] } } roll = append(roll, Roll{ Tel: tel, Name: name, }) } 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) } }