121 lines
2.6 KiB
Go
121 lines
2.6 KiB
Go
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
|
|
}
|