package app import ( "net/http" "bindbox-game/configs" "bindbox-game/internal/code" "bindbox-game/internal/pkg/core" "bindbox-game/internal/pkg/pay" "bindbox-game/internal/pkg/validation" "bindbox-game/internal/repository/mysql/model" ) type jsapiPreorderRequest struct { OrderNo string `json:"order_no" form:"order_no"` OpenID string `json:"openid" form:"openid"` } type jsapiPreorderResponse struct { TimeStamp string `json:"timeStamp"` NonceStr string `json:"nonceStr"` Package string `json:"package"` SignType string `json:"signType"` PaySign string `json:"paySign"` } // WechatJSAPIPreorder 小程序微信支付预下单并返回调起参数 // 入参:order_no(业务订单号)、openid(用户在小程序的openid) // 返回:用于wx.requestPayment的参数(timeStamp/nonceStr/package/signType/paySign) // 错误:参数绑定失败、配置缺失、订单不存在或状态不合法 // @Summary 小程序微信支付预下单 // @Description 根据`order_no`与`openid`创建或复用JSAPI预下单,返回`wx.requestPayment`调起参数;订单需为当前登录用户且状态为待支付 // @Tags APP端.支付 // @Accept json // @Produce json // @Security LoginVerifyToken // @Param RequestBody body jsapiPreorderRequest true "请求参数:业务订单号与openid" // @Success 200 {object} jsapiPreorderResponse // @Failure 400 {object} code.Failure "参数错误/配置缺失/订单不存在或状态不合法/微信预下单失败/签名构建失败" // @Router /api/app/pay/wechat/jsapi/preorder [post] func (h *handler) WechatJSAPIPreorder() core.HandlerFunc { return func(ctx core.Context) { req := new(jsapiPreorderRequest) rsp := new(jsapiPreorderResponse) if err := ctx.ShouldBindJSON(req); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err))) return } if ok, err := pay.ValidateConfig(); !ok { ctx.AbortWithError(core.Error(http.StatusBadRequest, 140001, err.Error())) return } c := configs.Get() if req.OrderNo == "" || req.OpenID == "" { ctx.AbortWithError(core.Error(http.StatusBadRequest, 140002, "order_no/openid required")) return } // 查询订单并校验状态=1 待支付,归属当前用户 order, err := h.readDB.Orders.WithContext(ctx.RequestContext()).Where(h.readDB.Orders.OrderNo.Eq(req.OrderNo), h.readDB.Orders.UserID.Eq(int64(ctx.SessionUserInfo().Id)), h.readDB.Orders.Status.Eq(1)).First() if err != nil || order == nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, 140006, "order not found or status invalid")) return } // 幂等:若已存在同一 out_trade_no 的预下单记录,直接复用其 prepay_id existed, _ := h.readDB.PaymentPreorders.WithContext(ctx.RequestContext()).Where(h.readDB.PaymentPreorders.OutTradeNo.Eq(order.OrderNo)).First() var prepayID string if existed != nil { prepayID = existed.PrepayID if order.PayPreorderID == 0 { _, _ = h.writeDB.Orders.WithContext(ctx.RequestContext()).Where(h.readDB.Orders.ID.Eq(order.ID)).Updates(map[string]any{ h.readDB.Orders.PayPreorderID.ColumnName().String(): existed.ID, }) } } else { wc, err := pay.NewWechatPayClient(ctx.RequestContext()) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, 140004, err.Error())) return } pid, err := wc.JSAPIPrepay(ctx.RequestContext(), c.Wechat.AppID, c.WechatPay.MchID, "订单"+req.OrderNo, req.OrderNo, order.ActualAmount, req.OpenID, c.WechatPay.NotifyURL) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, 140005, err.Error())) return } prepayID = pid pre := &model.PaymentPreorders{OrderID: order.ID, OrderNo: order.OrderNo, OutTradeNo: order.OrderNo, PrepayID: prepayID, AmountTotal: order.ActualAmount, PayerOpenid: req.OpenID, NotifyURL: c.WechatPay.NotifyURL, Status: "created"} if err := h.writeDB.PaymentPreorders.WithContext(ctx.RequestContext()).Omit(h.writeDB.PaymentPreorders.ExpiredAt).Create(pre); err == nil { _, _ = h.writeDB.Orders.WithContext(ctx.RequestContext()).Where(h.readDB.Orders.ID.Eq(order.ID)).Updates(map[string]any{h.readDB.Orders.PayPreorderID.ColumnName().String(): pre.ID}) } } ts, nonce, pkg, signType, paySign, err := pay.BuildJSAPIParams(c.Wechat.AppID, prepayID) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, 140003, err.Error())) return } rsp.TimeStamp = ts rsp.NonceStr = nonce rsp.Package = pkg rsp.SignType = signType rsp.PaySign = paySign ctx.Payload(rsp) } }