diff --git a/backend/app/http/posts.go b/backend/app/http/posts.go index 830697e..17e4fe6 100644 --- a/backend/app/http/posts.go +++ b/backend/app/http/posts.go @@ -271,15 +271,20 @@ func (ctl *posts) Buy(ctx fiber.Ctx, id int64, user *model.Users) (*wechat.JSAPI if err != nil { return nil, errors.Wrapf(err, " failed to get post: %d", id) } + payPrice := post.Price * int64(post.Discount) / 100 - // create order order, err := models.Orders.Create(ctx.Context(), user.ID, post.ID) if err != nil { return nil, errors.Wrap(err, "订单创建失败") } - payPrice := post.Price * int64(post.Discount) / 100 if user.Balance >= payPrice { + if err := models.Orders.SetMeta(ctx.Context(), order.ID, func(om fields.OrderMeta) fields.OrderMeta { + om.CostBalance = payPrice + return om + }); err != nil { + return nil, errors.Wrap(err, "订单创建失败") + } if err := ctl.job.Add(&jobs.BalancePayNotify{OrderNo: order.OrderNo}); err != nil { log.Errorf("add job error:%v", err) @@ -289,14 +294,15 @@ func (ctl *posts) Buy(ctx fiber.Ctx, id int64, user *model.Users) (*wechat.JSAPI return &wechat.JSAPIPayParams{ AppId: "balance", }, nil - } else { - payPrice = payPrice - user.Balance - err = models.Users.SetBalance(ctx.Context(), user.ID, 0) - if err != nil { - return nil, errors.Wrap(err, "余额支付失败") - } } + payPrice = payPrice - user.Balance + if err := models.Orders.SetMeta(ctx.Context(), order.ID, func(om fields.OrderMeta) fields.OrderMeta { + om.CostBalance = user.Balance + return om + }); err != nil { + return nil, errors.Wrap(err, "订单创建失败") + } prePayResp, err := ctl.wepay.V3TransactionJsapi(ctx.Context(), func(bm *wepay.BodyMap) { bm. Expire(30 * time.Minute). diff --git a/backend/app/jobs/balance_pay_notify.go b/backend/app/jobs/balance_pay_notify.go index 57541c6..63aa618 100644 --- a/backend/app/jobs/balance_pay_notify.go +++ b/backend/app/jobs/balance_pay_notify.go @@ -68,8 +68,11 @@ func (w *BalancePayNotifyWorker) Work(ctx context.Context, job *Job[BalancePayNo order.Status = fields.OrderStatusCompleted meta := order.Meta.Data - meta.CostBalance = payPrice - order.Meta = fields.ToJson(meta) + + if user.Balance-meta.CostBalance < 0 { + log.Errorf("User %d balance is not enough, current balance: %d, cost: %d", user.ID, user.Balance, payPrice) + return JobCancel(fmt.Errorf("User %d balance is not enough, current balance: %d, cost: %d", user.ID, user.Balance, payPrice)) + } log.Infof("Updated order details: %+v", order) tx, err := models.Transaction(ctx) diff --git a/backend/app/jobs/wechat_pay_notify.go b/backend/app/jobs/wechat_pay_notify.go index ceedec9..e1bc009 100644 --- a/backend/app/jobs/wechat_pay_notify.go +++ b/backend/app/jobs/wechat_pay_notify.go @@ -64,8 +64,13 @@ func (w *WechatPayNotifyWorker) Work(ctx context.Context, job *Job[WechatPayNoti return JobCancel(fmt.Errorf("Order already paid, currently status: %d", order.Status)) } - needToPay := order.Price * int64(order.Discount) / 100 + user, err := models.Users.GetByID(context.Background(), order.UserID) + if err != nil { + return errors.Wrapf(err, "get user by id(%d) failed", order.UserID) + } + meta := order.Meta.Data + needToPay := order.Price*int64(order.Discount)/100 - meta.CostBalance if int64(notify.Amount.Total) != needToPay { log.Errorf("Order %s amount mismatch: expected %d, got %d", job.Args.Notify.OutTradeNo, needToPay, notify.Amount.Total) return fmt.Errorf("amount mismatch for order %s", job.Args.Notify.OutTradeNo) @@ -75,9 +80,9 @@ func (w *WechatPayNotifyWorker) Work(ctx context.Context, job *Job[WechatPayNoti order.Currency = notify.Amount.Currency order.PaymentMethod = notify.TradeType order.Status = fields.OrderStatusCompleted - order.Meta = fields.ToJson(fields.OrderMeta{ - PayNotify: notify, - }) + + meta.PayNotify = notify + order.Meta = fields.ToJson(meta) log.Infof("Updated order details: %+v", order) tx, err := models.Transaction(ctx) @@ -86,6 +91,13 @@ func (w *WechatPayNotifyWorker) Work(ctx context.Context, job *Job[WechatPayNoti } defer tx.Rollback() + // update user balance + err = models.Users.SetBalance(ctx, user.ID, user.Balance-meta.CostBalance) + if err != nil { + log.WithError(err).Error("SetBalance error") + return JobCancel(errors.Wrap(err, "set user balance failed")) + } + if err := models.Users.BuyPosts(context.Background(), order.UserID, order.PostID, order.Price); err != nil { log.Errorf("BuyPosts error:%v", err) return errors.Wrap(err, "BuyPosts error") diff --git a/backend/app/models/orders.go b/backend/app/models/orders.go index a0491a8..c8c9f37 100644 --- a/backend/app/models/orders.go +++ b/backend/app/models/orders.go @@ -186,6 +186,29 @@ func (m *ordersModel) Create(ctx context.Context, userId, postId int64) (*model. return model, nil } +func (m *ordersModel) SetMeta(ctx context.Context, id int64, metaFunc func(fields.OrderMeta) fields.OrderMeta) error { + order, err := m.GetByID(ctx, id) + if err != nil { + m.log.Errorf("error getting order by ID: %v", err) + return errors.Wrap(err, "failed to get order") + } + + tbl := table.Orders + stmt := tbl. + UPDATE(tbl.Meta). + SET(fields.ToJson(metaFunc(order.Meta.Data))). + WHERE( + tbl.ID.EQ(Int64(id)), + ) + m.log.Infof("sql: %s", stmt.DebugSql()) + + if _, err := stmt.ExecContext(ctx, db); err != nil { + m.log.Errorf("error set order meta: %v", err) + return err + } + return nil +} + // DeleteByID soft deletes an order by ID func (m *ordersModel) SetStatus(ctx context.Context, orderNo string, status fields.OrderStatus) error { tbl := table.Orders diff --git a/backend/database/fields/orders.go b/backend/database/fields/orders.go index d154966..9a60f01 100644 --- a/backend/database/fields/orders.go +++ b/backend/database/fields/orders.go @@ -14,3 +14,7 @@ type OrderMeta struct { RefundNotify *wechat.V3DecryptRefundResult `json:"refund_notify"` CostBalance int64 `json:"cost_balance"` // 余额支付的金额 } + +func (m OrderMeta) ToJson() Json[OrderMeta] { + return ToJson(m) +}