package wechatmini import ( "context" "crypto/sha1" "encoding/hex" "encoding/json" "fmt" "io" "net/http" "net/url" "strings" "time" ) type Client struct { appID string appSecret string devPrefix string httpClient *http.Client } type Session struct { AppID string OpenID string UnionID string SessionKey string } type code2SessionResponse struct { OpenID string `json:"openid"` SessionKey string `json:"session_key"` UnionID string `json:"unionid"` ErrCode int `json:"errcode"` ErrMsg string `json:"errmsg"` } func NewClient(appID, appSecret, devPrefix string) *Client { return &Client{ appID: appID, appSecret: appSecret, devPrefix: devPrefix, httpClient: &http.Client{Timeout: 8 * time.Second}, } } func (c *Client) ExchangeCode(ctx context.Context, code string) (*Session, error) { code = strings.TrimSpace(code) if code == "" { return nil, fmt.Errorf("wechat code is required") } if c.devPrefix != "" && strings.HasPrefix(code, c.devPrefix) { suffix := strings.TrimPrefix(code, c.devPrefix) if suffix == "" { suffix = "default" } return &Session{ AppID: fallbackString(c.appID, "dev-mini-app"), OpenID: "dev_openid_" + normalizeDevID(suffix), UnionID: "", }, nil } if c.appID == "" || c.appSecret == "" { return nil, fmt.Errorf("wechat mini app credentials are not configured") } values := url.Values{} values.Set("appid", c.appID) values.Set("secret", c.appSecret) values.Set("js_code", code) values.Set("grant_type", "authorization_code") req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://api.weixin.qq.com/sns/jscode2session?"+values.Encode(), nil) if err != nil { return nil, err } resp, err := c.httpClient.Do(req) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } var parsed code2SessionResponse if err := json.Unmarshal(body, &parsed); err != nil { return nil, err } if parsed.ErrCode != 0 { return nil, fmt.Errorf("wechat code2session failed: %d %s", parsed.ErrCode, parsed.ErrMsg) } if parsed.OpenID == "" { return nil, fmt.Errorf("wechat code2session returned empty openid") } return &Session{ AppID: c.appID, OpenID: parsed.OpenID, UnionID: parsed.UnionID, SessionKey: parsed.SessionKey, }, nil } func normalizeDevID(value string) string { sum := sha1.Sum([]byte(value)) return hex.EncodeToString(sum[:])[:16] } func fallbackString(value, fallback string) string { if value != "" { return value } return fallback }