bindbox-game/internal/api/user/pay_wechat_app.go
邹方成 45815bfb7d chore: 清理无用文件与优化代码结构
refactor(utils): 修复密码哈希比较逻辑错误
feat(user): 新增按状态筛选优惠券接口
docs: 添加虚拟发货与任务中心相关文档
fix(wechat): 修正Code2Session上下文传递问题
test: 补充订单折扣与积分转换测试用例
build: 更新配置文件与构建脚本
style: 清理多余的空行与注释
2025-12-18 17:35:55 +08:00

103 lines
4.5 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 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)
}
}