diff --git a/app/main/api/internal/service/wechatpayService.go b/app/main/api/internal/service/wechatpayService.go index 237e6d4..ffda953 100644 --- a/app/main/api/internal/service/wechatpayService.go +++ b/app/main/api/internal/service/wechatpayService.go @@ -2,6 +2,7 @@ package service import ( "context" + "errors" "fmt" "net/http" "strconv" @@ -25,6 +26,14 @@ import ( "github.com/zeromicro/go-zero/core/logx" ) +// wxpayAPIHTTPStatus 安全读取微信支付 API 的 HTTP 状态码;本地签名/随机串失败时 result 可能为 nil。 +func wxpayAPIHTTPStatus(result *core.APIResult) int { + if result == nil || result.Response == nil { + return 0 + } + return result.Response.StatusCode +} + const ( TradeStateSuccess = "SUCCESS" // 支付成功 TradeStateRefund = "REFUND" // 转入退款 @@ -175,7 +184,7 @@ func (w *WechatPayService) CreateWechatAppOrder(ctx context.Context, amount floa resp, result, err := svc.Prepay(ctx, payRequest) logx.Infof("微信app支付订单:resp: %+v, result: %+v, err: %+v", resp, result, err) if err != nil { - return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode) + return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, wxpayAPIHTTPStatus(result)) } // 返回预支付交易会话标识 @@ -200,14 +209,23 @@ func jsapiRequestPaymentToMap(resp *jsapi.PrepayWithRequestPaymentResponse) (map if resp.Package != nil { m["package"] = *resp.Package } - if resp.SignType != nil { + if resp.SignType != nil && *resp.SignType != "" { m["signType"] = *resp.SignType + } else { + m["signType"] = "RSA" } if resp.PaySign != nil { m["paySign"] = *resp.PaySign } - if len(m) != 6 { - return nil, fmt.Errorf("微信 JSAPI 调起参数不完整") + var missing []string + for _, key := range []string{"appId", "timeStamp", "nonceStr", "package", "paySign"} { + if m[key] == "" { + missing = append(missing, key) + } + } + if len(missing) > 0 { + logx.Errorf("[WechatPay] JSAPI 调起参数缺项: missing=%v resp=%s", missing, resp.String()) + return nil, fmt.Errorf("微信 JSAPI 调起参数不完整: 缺少或为空 %v", missing) } return m, nil } @@ -237,7 +255,7 @@ func (w *WechatPayService) CreateWechatMiniProgramOrder(ctx context.Context, amo resp, result, err := svc.PrepayWithRequestPayment(ctx, payRequest) logx.Infof("微信小程序支付订单:resp: %+v, result: %+v, err: %+v", resp, result, err) if err != nil { - return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode) + return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, wxpayAPIHTTPStatus(result)) } return jsapiRequestPaymentToMap(resp) } @@ -270,7 +288,7 @@ func (w *WechatPayService) CreateWechatH5Order(ctx context.Context, amount float logx.Infof("微信h5支付订单:resp: %+v, result: %+v, err: %+v", resp, result, err) if err != nil { logx.Infof("微信h5支付订单:resp: %+v, result: %+v, err: %+v", resp, result, err) - return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, result.Response.StatusCode) + return "", fmt.Errorf("微信支付订单创建失败: %v, 状态码: %d", err, wxpayAPIHTTPStatus(result)) } return jsapiRequestPaymentToMap(resp) } @@ -316,31 +334,28 @@ func (w *WechatPayService) CreateWechatOrder(ctx context.Context, amount float64 if getUidErr != nil { return "", getUidErr } - // 微信内 H5:优先 wxh5_openid(与公众号/网页 AppID 一致);若无则尝试 wxmini_openid(走小程序 AppID 下单) + // 微信内置浏览器 JSAPI 必须使用与商户公众号一致的 openid(snsapi_base / snsapi_userinfo 授权后写入 wxh5_openid)。 + // 不可使用小程序 openid 兜底:AppID 与 openid 主体不一致会导致下单失败或调起异常。 h5Auth, h5Err := w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxh5OpenID) - if h5Err == nil { - logx.Infof("微信h5支付订单:userAuthModel(wxh5): %+v", h5Auth) - prepayData, err = w.CreateWechatH5Order(ctx, amount, description, outTradeNo, h5Auth.AuthKey) - if err != nil { - return "", err + if h5Err != nil { + if errors.Is(h5Err, model.ErrNotFound) { + logx.WithContext(ctx).Infof( + "[WechatPay] wxh5 缺少 user_auth(wxh5_openid) user_id=%d out_trade_no=%s,需先走公众号网页授权(建议 scope=snsapi_base)", + userID, outTradeNo, + ) + return "", fmt.Errorf("微信内支付需先完成公众号网页授权以获取 openid(建议使用 snsapi_base 静默授权)") } - } else if h5Err == model.ErrNotFound { - miniAuth, miniErr := w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxMiniOpenID) - if miniErr != nil { - return "", miniErr - } - logx.WithContext(ctx).Infof( - "[WechatPay] wxh5 无 wxh5_openid,使用 wxmini_openid 下单 out_trade_no=%s user_id=%d", - outTradeNo, userID, - ) - logx.Infof("微信h5支付订单:userAuthModel(wxmini fallback): %+v", miniAuth) - prepayData, err = w.CreateWechatMiniProgramOrder(ctx, amount, description, outTradeNo, miniAuth.AuthKey) - if err != nil { - return "", err - } - } else { return "", h5Err } + if strings.TrimSpace(h5Auth.AuthKey) == "" { + logx.WithContext(ctx).Errorf("[WechatPay] wxh5_openid 记录存在但 auth_key 为空 user_id=%d", userID) + return "", fmt.Errorf("微信内支付 openid 未就绪,请重新完成公众号网页授权") + } + logx.Infof("微信h5支付订单:userAuthModel(wxh5): %+v", h5Auth) + prepayData, err = w.CreateWechatH5Order(ctx, amount, description, outTradeNo, strings.TrimSpace(h5Auth.AuthKey)) + if err != nil { + return "", err + } case model.PlatformApp: // 如果是 APP 平台,调用 APP 支付订单创建 prepayData, err = w.CreateWechatAppOrder(ctx, amount, description, outTradeNo) @@ -355,7 +370,10 @@ func (w *WechatPayService) CreateWechatOrder(ctx context.Context, amount float64 if prepayData == nil { logx.WithContext(ctx).Errorf("[WechatPay] CreateWechatOrder 返回 prepayData 为 nil platform=%q", platform) - } else if m, isMap := prepayData.(map[string]string); isMap { + return nil, fmt.Errorf("微信支付返回数据为空 platform=%s", platform) + } + + if m, isMap := prepayData.(map[string]string); isMap { keys := make([]string, 0, len(m)) for k := range m { keys = append(keys, k) @@ -400,7 +418,7 @@ func (w *WechatPayService) QueryOrderStatus(ctx context.Context, transactionID s Mchid: core.String(w.config.Wxpay.MchID), }) if err != nil { - return nil, fmt.Errorf("订单查询失败: %v, 状态码: %d", err, result.Response.StatusCode) + return nil, fmt.Errorf("订单查询失败: %v, 状态码: %d", err, wxpayAPIHTTPStatus(result)) } return resp, nil