This commit is contained in:
Mrx
2026-02-06 16:00:04 +08:00
parent 9fe6a88670
commit e1fd1439d5
31 changed files with 1840 additions and 1659 deletions

View File

@@ -2,15 +2,19 @@ package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"time"
"qnc-server/app/main/api/internal/config"
"qnc-server/app/main/model"
"qnc-server/common/ctxdata"
"qnc-server/pkg/lzkit/lzUtils"
"strconv"
"time"
"github.com/google/uuid"
"github.com/wechatpay-apiv3/wechatpay-go/core"
"github.com/wechatpay-apiv3/wechatpay-go/core/auth/verifiers"
"github.com/wechatpay-apiv3/wechatpay-go/core/downloader"
@@ -251,9 +255,16 @@ func (w *WechatPayService) CreateWechatH5Order(ctx context.Context, amount float
}
// CreateWechatOrder 创建微信支付订单(集成 APP、H5、小程序
func (w *WechatPayService) CreateWechatOrder(ctx context.Context, amount float64, description string, outTradeNo string) (interface{}, error) {
// 根据 ctx 中的 platform 判断平台
platform := ctx.Value("platform").(string)
func (w *WechatPayService) CreateWechatOrder(ctx context.Context, amount float64, description string, outTradeNo string, code string) (interface{}, error) {
// 根据 ctx 中的 platform 判断平台(请求头 X-Platform: wxmini / wxh5 / app
platformValue := ctx.Value("platform")
if platformValue == nil {
return "", fmt.Errorf("平台信息不存在,请检查请求头 X-Platform")
}
platform, ok := platformValue.(string)
if !ok {
return "", fmt.Errorf("平台信息格式错误")
}
var prepayData interface{}
var err error
@@ -266,7 +277,30 @@ func (w *WechatPayService) CreateWechatOrder(ctx context.Context, amount float64
}
userAuthModel, findAuthModelErr := w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxMiniOpenID)
if findAuthModelErr != nil {
return "", findAuthModelErr
if errors.Is(findAuthModelErr, model.ErrNotFound) {
// 用户未绑定微信,尝试通过 code 自动绑定
if code == "" {
return "", fmt.Errorf("用户未绑定微信小程序账号,请先完成微信登录或提供授权码")
}
// 通过 code 获取 OpenID
openid, getOpenidErr := w.getWechatMiniOpenID(ctx, code)
if getOpenidErr != nil {
return "", fmt.Errorf("获取微信小程序OpenID失败: %v", getOpenidErr)
}
// 自动绑定微信账号
bindErr := w.bindWechatAuth(ctx, userID, model.UserAuthTypeWxMiniOpenID, openid)
if bindErr != nil {
return "", fmt.Errorf("绑定微信小程序账号失败: %v", bindErr)
}
// 重新查询绑定后的认证信息
userAuthModel, findAuthModelErr = w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxMiniOpenID)
if findAuthModelErr != nil {
return "", fmt.Errorf("查询绑定后的微信认证信息失败: %v", findAuthModelErr)
}
logx.Infof("用户 %s 已自动绑定微信小程序账号OpenID: %s", userID, openid)
} else {
return "", fmt.Errorf("查询用户微信认证信息失败: %v", findAuthModelErr)
}
}
prepayData, err = w.CreateWechatMiniProgramOrder(ctx, amount, description, outTradeNo, userAuthModel.AuthKey)
if err != nil {
@@ -279,7 +313,30 @@ func (w *WechatPayService) CreateWechatOrder(ctx context.Context, amount float64
}
userAuthModel, findAuthModelErr := w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxh5OpenID)
if findAuthModelErr != nil {
return "", findAuthModelErr
if errors.Is(findAuthModelErr, model.ErrNotFound) {
// 用户未绑定微信,尝试通过 code 自动绑定
if code == "" {
return "", fmt.Errorf("用户未绑定微信H5账号请先完成微信登录或提供授权码")
}
// 通过 code 获取 OpenID
openid, getOpenidErr := w.getWechatH5OpenID(ctx, code)
if getOpenidErr != nil {
return "", fmt.Errorf("获取微信H5 OpenID失败: %v", getOpenidErr)
}
// 自动绑定微信账号
bindErr := w.bindWechatAuth(ctx, userID, model.UserAuthTypeWxh5OpenID, openid)
if bindErr != nil {
return "", fmt.Errorf("绑定微信H5账号失败: %v", bindErr)
}
// 重新查询绑定后的认证信息
userAuthModel, findAuthModelErr = w.userAuthModel.FindOneByUserIdAuthType(ctx, userID, model.UserAuthTypeWxh5OpenID)
if findAuthModelErr != nil {
return "", fmt.Errorf("查询绑定后的微信认证信息失败: %v", findAuthModelErr)
}
logx.Infof("用户 %s 已自动绑定微信H5账号OpenID: %s", userID, openid)
} else {
return "", fmt.Errorf("查询用户微信认证信息失败: %v", findAuthModelErr)
}
}
prepayData, err = w.CreateWechatH5Order(ctx, amount, description, outTradeNo, userAuthModel.AuthKey)
if err != nil {
@@ -384,3 +441,112 @@ func (w *WechatPayService) GenerateOutTradeNo() string {
return combined
}
// getWechatMiniOpenID 通过 code 获取微信小程序 OpenID
func (w *WechatPayService) getWechatMiniOpenID(ctx context.Context, code string) (string, error) {
appID := w.config.WechatMini.AppID
appSecret := w.config.WechatMini.AppSecret
url := fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", appID, appSecret, code)
resp, err := http.Get(url)
if err != nil {
return "", fmt.Errorf("请求微信API失败: %v", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("读取响应失败: %v", err)
}
var data struct {
Openid string `json:"openid"`
ErrCode int `json:"errcode,omitempty"`
ErrMsg string `json:"errmsg,omitempty"`
}
if err := json.Unmarshal(body, &data); err != nil {
return "", fmt.Errorf("解析响应失败: %v", err)
}
if data.ErrCode != 0 {
return "", fmt.Errorf("微信API返回错误: errcode=%d, errmsg=%s", data.ErrCode, data.ErrMsg)
}
if data.Openid == "" {
return "", fmt.Errorf("openid为空")
}
return data.Openid, nil
}
// getWechatH5OpenID 通过 code 获取微信H5 OpenID
func (w *WechatPayService) getWechatH5OpenID(ctx context.Context, code string) (string, error) {
appID := w.config.WechatH5.AppID
appSecret := w.config.WechatH5.AppSecret
url := fmt.Sprintf("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code", appID, appSecret, code)
resp, err := http.Get(url)
if err != nil {
return "", fmt.Errorf("请求微信API失败: %v", err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("读取响应失败: %v", err)
}
var data struct {
Openid string `json:"openid"`
ErrCode int `json:"errcode,omitempty"`
ErrMsg string `json:"errmsg,omitempty"`
}
if err := json.Unmarshal(body, &data); err != nil {
return "", fmt.Errorf("解析响应失败: %v", err)
}
if data.ErrCode != 0 {
return "", fmt.Errorf("微信API返回错误: errcode=%d, errmsg=%s", data.ErrCode, data.ErrMsg)
}
if data.Openid == "" {
return "", fmt.Errorf("openid为空")
}
return data.Openid, nil
}
// bindWechatAuth 绑定微信认证信息到用户账号
func (w *WechatPayService) bindWechatAuth(ctx context.Context, userID string, authType string, openid string) error {
// 检查该 OpenID 是否已被其他用户绑定
existingAuth, err := w.userAuthModel.FindOneByAuthTypeAuthKey(ctx, authType, openid)
if err != nil && !errors.Is(err, model.ErrNotFound) {
return fmt.Errorf("查询认证信息失败: %v", err)
}
if existingAuth != nil {
// 如果 OpenID 已被其他用户绑定,检查是否是当前用户
if existingAuth.UserId != userID {
return fmt.Errorf("该微信账号已被其他用户绑定")
}
// 如果已经是当前用户绑定的,直接返回成功
return nil
}
// 创建新的认证记录
userAuth := &model.UserAuth{
Id: uuid.NewString(),
UserId: userID,
AuthType: authType,
AuthKey: openid,
}
_, err = w.userAuthModel.Insert(ctx, nil, userAuth)
if err != nil {
return fmt.Errorf("创建认证记录失败: %v", err)
}
return nil
}