package rechargeser import ( "context" "crypto/md5" "encoding/hex" "encoding/json" "fmt" "strconv" "strings" "time" "videoweb/pub/common/log" "videoweb/pub/common/srvdef" "videoweb/pub/common/useragent" "videoweb/pub/dataReport" "videoweb/pub/models/eventmod" "videoweb/pub/models/rechargepackagemod" "videoweb/pub/models/usermod" "videoweb/pub/models/vipordermod" "videoweb/pub/mongos" "videoweb/pub/pubdef" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo/options" ) // SubmitOrderResp 创建第三方支付订单的响应 type SubmitOrderResp struct { TradeNo string `json:"tradeNo"` // 本地订单号(可用于查询) PayUrl string `json:"payUrl"` // 支付链接 PayUrlMode string `json:"payUrlMode"` // 链接打开方式 IsOpenNewBrowser bool `json:"isOpenNewBrowser"` // 是否需要外部浏览器打开 } // CreateOrder 创建第三方支付 VIP 订单。 // 防重复:15 秒内同一用户+同一套餐的 pending 订单直接返回原支付链接。 func CreateOrder(ctx context.Context, userId int64, userName string, packageId int64, payMode string, sysType string, devType string, deviceID string, ip string, districtCode string, ) (*SubmitOrderResp, *srvdef.SrvErr) { pkg, serr := rechargepackagemod.FindOneRechargePackage(ctx, bson.M{ rechargepackagemod.FN.RechargePackage.Id: packageId, rechargepackagemod.FN.RechargePackage.PackageType: rechargepackagemod.PackageTypeVip, rechargepackagemod.FN.RechargePackage.Status: rechargepackagemod.StatusEnabled, }) if serr != nil || pkg == nil { return nil, srvdef.New(srvdef.ErrNoData, "会员套餐不存在或已下架", "") } now := time.Now() dupFilter := bson.M{ vipordermod.FN.VipOrder.UserId: userId, vipordermod.FN.VipOrder.CardId: packageId, vipordermod.FN.VipOrder.PayMode: payMode, vipordermod.FN.VipOrder.Status: pubdef.VipOrderPending, vipordermod.FN.VipOrder.CreatedAt: bson.M{"$gte": now.Add(-15 * time.Second)}, } existing, _ := vipordermod.FindOneVipOrder(ctx, dupFilter) if existing != nil && existing.PayUrl != "" { return &SubmitOrderResp{ TradeNo: existing.TradeNo, PayUrl: existing.PayUrl, PayUrlMode: existing.PayUrlMode, IsOpenNewBrowser: true, }, nil } tradeNo := pubdef.GetTrandNoWithUUID() amount := pkg.Price payInfo, serr := SubmitPayOrder(ctx, userId, userName, tradeNo, amount, payMode, sysType, devType, deviceID, ip) if serr != nil { return nil, serr } order := &vipordermod.VipOrder{ Id: mongos.Lid.VipOrder.Get(), OrderType: vipordermod.OrderTypeVip, UserId: userId, CardId: packageId, // 兼容历史字段,存购买套餐ID CardName: pkg.Name, Amount: amount, PayMode: payMode, TradeNo: tradeNo, Oid: payInfo.Oid, Status: pubdef.VipOrderPending, PayUrl: payInfo.PayUrl, PayUrlMode: payInfo.PayUrlMode, PayChannel: payInfo.PayChannel, PayRate: payInfo.PayRate, IPAddr: ip, SysType: sysType, DistrictCode: strings.TrimSpace(districtCode), CreatedAt: now, UpdatedAt: now, } if serr = vipordermod.InsertOneVipOrder(ctx, order); serr != nil { log.Error("rechargeser:CreateOrder InsertOneVipOrder failed", log.Any("tradeNo", tradeNo), log.E(serr)) return nil, serr } go reportOrderCreated(ctx, order, pkg, userId, ip) return &SubmitOrderResp{ TradeNo: tradeNo, PayUrl: payInfo.PayUrl, PayUrlMode: payInfo.PayUrlMode, IsOpenNewBrowser: true, }, nil } // GetOrderHistory 查询用户的 VIP 订单历史 func GetOrderHistory(ctx context.Context, userId int64, page, pageSize int64) (vipordermod.VipOrderList, int64, *srvdef.SrvErr) { filter := bson.M{ vipordermod.FN.VipOrder.UserId: userId, vipordermod.FN.VipOrder.OrderType: vipordermod.OrderTypeVip, } total, serr := vipordermod.CountVipOrder(ctx, filter) if serr != nil { return nil, 0, serr } skip := (page - 1) * pageSize opts := options.Find(). SetSort(bson.M{vipordermod.FN.VipOrder.CreatedAt: -1}). SetSkip(skip). SetLimit(pageSize) list, serr := vipordermod.FindManyVipOrder(ctx, filter, opts) return list, total, serr } // GetUserInfo 获取用户信息(供 app 层展示当前 VIP 状态) func GetUserInfo(ctx context.Context, userId int64) (*usermod.User, *srvdef.SrvErr) { return usermod.FindOneUser(ctx, bson.M{usermod.FN.User.Id: userId}) } func reportOrderCreated(ctx context.Context, order *vipordermod.VipOrder, pkg *rechargepackagemod.RechargePackage, userId int64, ip string) { user, _ := usermod.FindOneUser(ctx, bson.M{usermod.FN.User.Id: userId}) districtCode := "" if user != nil { districtCode = user.DistrictCode } ua := useragent.UA{SysType: order.SysType} eventId := newEventId(pubdef.TrackerAppId(), user.Id, order.TradeNo, order.CreatedAt.Unix()) commonField := dataReport.NewCommonField(eventId, dataReport.EventTypeOrderCreated, districtCode, ua, strconv.FormatInt(user.Id, 10), ip) commonField.Payload = dataReport.JsonRawMessage(&dataReport.OrderCreatedEvent{ OrderID: order.TradeNo, OrderType: "vip_subscription", ProductID: strconv.FormatInt(pkg.Id, 10), ProductName: pkg.Name, Amount: dataReport.JsonNumber(order.Amount), Currency: "CNY", CreateTime: dataReport.JsonNumber(order.CreatedAt.Unix()), }) eventData, _ := json.Marshal(commonField) if err := eventmod.InsertOneEvent(ctx, &eventmod.Event{ EventId: commonField.EventID, EventType: commonField.Event, Data: string(eventData), Status: eventmod.Default, CreatedAt: time.Now(), UpdatedAt: time.Now(), }); err != nil { log.Error("reportOrderCreated InsertOneEvent failed", log.Any("tradeNo", order.TradeNo), log.E(err)) } } func newEventId(appId string, uid int64, orderId interface{}, createTime int64) string { input := fmt.Sprintf("%v%v%v%v", appId, uid, orderId, createTime) h := md5.New() h.Write([]byte(input)) return hex.EncodeToString(h.Sum(nil)) }