fonchain-fiee/pkg/service/bundle/pay.go
2026-02-03 16:46:57 +08:00

926 lines
31 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}
}