package pay import ( "fmt" "time" "yunyan/comm" "yunyan/lego/base" "yunyan/lego/core" "yunyan/lego/sys/log" "yunyan/modules" "yunyan/pb" ) type apiComp struct { modules.MCompHttpGate service base.IRPCXService module *Pay options *Options } func (this *apiComp) Init(service core.IService, module core.IModule, comp core.IModuleComp, options core.IModuleOptions) (err error) { this.MCompHttpGate.Init(service, module, comp, options) this.service = service.(base.IRPCXService) this.module = module.(*Pay) this.options = options.(*Options) return } func (this *apiComp) Start() (err error) { if err = this.MCompHttpGate.Start(); err != nil { return } if m, e := this.service.GetModule(comm.ModuleAnalyze); e == nil { this.module.analyze, _ = m.(comm.IAnalyze) } return } // goodsGrant 一笔订单要发放的内容。商品来源有两套——历史口径读**各业务库的 goods 表** // (pb.DBGoods),v2 口径读**管理后台的商品配置**(console 公共库 app_goods,按应用名隔离)—— // 但发放、记账、埋点是同一套,抽出来共用。 type goodsGrant struct { Viptime int32 Compute int64 } // grantOfAppGoods 把后台商品配置换算成发放内容。 // 2026-09 起商品只发「VIP 天数 + 算力」;老商品没配 compute(=0)而配了旧三桶时,按当前换算系数折成算力, // 保证历史商品照常可卖、买到的东西不缩水。 func grantOfAppGoods(g *comm.AppGoods) *goodsGrant { c := g.Compute if c == 0 { c = comm.LegacyBucketsToCompute(comm.LoadComputeRates(), g.Tradenum, g.Meetnum, g.Ainum) } return &goodsGrant{Viptime: g.Viptime, Compute: c} } // deliverResourceByOrder 按订单给用户发货:商品取自**业务库 goods 表**(历史口径, // 微信/支付宝/Apple/Google 通知与 verifyorder 都走这条)。 // 商品对不上时 grant 为 nil:不加资源,但仍走一遍更新与记账,与改造前行为一致。 func (this *apiComp) deliverResourceByOrder(user *pb.DBUser, order *pb.DBPayOrder) (err error) { var goodss []*pb.DBGoods // 1. 检查订单状态 if order.Status != pb.PayOrderStatus_PAY_ORDER_PAID { err = fmt.Errorf("订单状态错误,无法下发资源: %v", order.Status) return } goodss, err = this.module.model.goodss() if err != nil { err = fmt.Errorf("查询商品信息失败: %v", err) return } var grant *goodsGrant for _, v := range goodss { if v.Id == order.ProductId { c := v.Compute if c == 0 { c = comm.LegacyBucketsToCompute(comm.LoadComputeRates(), v.Tradenum, v.Meetnum, v.Ainum) } grant = &goodsGrant{Viptime: v.Viptime, Compute: c} } } return this.deliverGrant(user, order, grant) } // deliverGrant 真正的发货:改用户资源 → 写消费日志 → 上报埋点。 // grant 为 nil 表示没匹配到商品(不加任何资源,也不写发放明细)。 func (this *apiComp) deliverGrant(user *pb.DBUser, order *pb.DBPayOrder, grant *goodsGrant) (err error) { var userlog *pb.DBUserUseLog if order.Status != pb.PayOrderStatus_PAY_ORDER_PAID { err = fmt.Errorf("订单状态错误,无法下发资源: %v", order.Status) return } now := time.Now() userlog = &pb.DBUserUseLog{ Uid: order.Uid, Logtype: pb.UserLogType_Pay, Ts: now.Unix(), } if v := grant; v != nil { if v.Viptime > 0 { user.Viplv = 1 prev := user.Vipexptime days := int64(v.Viptime) * 24 * 60 * 60 mode := "reset" if user.Vipexptime == 0 || user.Vipexptime < now.Unix() { user.Vipexptime = now.Unix() + days } else { user.Vipexptime = user.Vipexptime + days mode = "accumulate" } userlog.Addvipday = int64(v.Viptime) this.module.Debug("支付发货", log.Field{Key: "now", Value: now.Format("2006-01-02 15:04:05")}, log.Field{Key: "prev", Value: prev}, log.Field{Key: "days", Value: days}, log.Field{Key: "mode", Value: mode}, log.Field{Key: "Vipexptime", Value: user.Vipexptime}) } if v.Compute > 0 { user.Computebalance += v.Compute userlog.Compute = v.Compute this.module.Debug("支付发货", log.Field{Key: "Computebalance", Value: user.Computebalance}) } userlog.Extra = fmt.Sprintf(`{"order_id":"%s","product_id":"%s","product_name":"%s","amount":%d,"payment_method":%d,"add_vip_days":%d,"add_compute":%d}`, order.OrderId, order.ProductId, order.ProductName, order.Amount, order.PaymentMethod, userlog.Addvipday, userlog.Compute) } // 2. 更新用户信息 if err = this.module.model.updateUser(user); err != nil { err = fmt.Errorf("更新用户信息失败: %v", err) return } // 3. 写消费日志 if err = this.module.model.addIntegralLog(userlog); err != nil { err = fmt.Errorf("添加积分日志失败: %v", err) return } // 4. 埋点:订单支付完成 + 资源发放 if this.module.analyze != nil { // 归属用订单自己的来源设备快照(下单时冻结),与月结算口径一致; // 老订单没有快照时回退用户最后绑定信息。 paidPid, paidChannel := order.SrcProductid, order.SrcChannelid if paidPid == 0 { paidPid = user.Lastbindproductid } if paidChannel == "" { paidChannel = user.Lastbindchannelid } this.module.analyze.Report(&comm.StatEvent{ Type: comm.StatEventOrderPaid, ProductId: paidPid, ChannelId: paidChannel, Uid: order.Uid, Amount: order.Amount, }) if userlog.Addvipday > 0 || userlog.Addagentintegral > 0 || userlog.Addtradesecond > 0 || userlog.Addmeetsecond > 0 { this.module.analyze.Report(&comm.StatEvent{ Type: comm.StatEventResourceGrant, ProductId: paidPid, ChannelId: paidChannel, Uid: order.Uid, GrantVipDay: userlog.Addvipday, GrantAiIntegral: userlog.Addagentintegral, GrantTradeSecond: userlog.Addtradesecond, GrantMeetSecond: userlog.Addmeetsecond, }) } } return }