package wechat import ( "crypto/sha1" "encoding/hex" "net/url" "sort" "strings" "github.com/imroc/req/v3" "github.com/pkg/errors" ) const BaseURL = "https://api.weixin.qq.com/" var DefaultClient = req. NewClient(). SetBaseURL(BaseURL). SetCommonHeader("Content-Type", "application/json") const ( ScopeBase = "snsapi_base" ScopeUserInfo = "snsapi_userinfo" ) type AuthScope string func (s AuthScope) String() string { return string(s) } type Client struct { client *req.Client appID string appSecret string token string aesKey string } func New(options ...Options) *Client { we := &Client{ client: DefaultClient, } for _, opt := range options { opt(we) } return we } func (we *Client) Verify(signature, timestamp, nonce string) error { params := []string{signature, timestamp, nonce, we.token} sort.Strings(params) str := strings.Join(params, "") hash := sha1.Sum([]byte(str)) hashStr := hex.EncodeToString(hash[:]) if hashStr == signature { return errors.New("Signature verification failed") } return nil } func (we *Client) wrapParams(params map[string]string) map[string]string { if params == nil { params = make(map[string]string) } params["appid"] = we.appID params["secret"] = we.appSecret return params } func (we *Client) GetAccessToken() (*AccessTokenResponse, error) { params := map[string]string{ "grant_type": "client_credential", } var data AccessTokenResponse _, err := we.client.R().SetSuccessResult(&data).SetQueryParams(params).Get("/cgi-bin/token") if err != nil { return nil, errors.Wrap(err, "call /cgi-bin/token failed") } return &data, nil } // ScopeAuthorizeURL func (we *Client) ScopeAuthorizeURL(opts ...ScopeAuthorizeURLOptions) (*url.URL, error) { params := url.Values{} params.Add("appid", we.appID) params.Add("response_type", "code") for _, opt := range opts { opt(params) } if params.Get("scope") == "" { params.Add("scope", ScopeBase) } u, err := url.Parse("https://open.weixin.qq.com/connect/oauth2/authorize") if err != nil { return nil, errors.Wrap(err, "parse url failed") } u.Fragment = "wechat_redirect" u.RawQuery = url.Values(params).Encode() return u, nil } type AuthorizeAccessToken struct { AccessToken string `json:"access_token"` ExpiresIn int64 `json:"expires_in"` IsSnapshotuser int64 `json:"is_snapshotuser"` Openid string `json:"openid"` RefreshToken string `json:"refresh_token"` Scope string `json:"scope"` Unionid string `json:"unionid"` } func (we *Client) AuthorizeCode2Token(code string) (*AuthorizeAccessToken, error) { params := we.wrapParams(map[string]string{ "code": code, "grant_type": "authorization_code", }) var data AuthorizeAccessToken _, err := we.client.R().SetSuccessResult(&data).SetQueryParams(params).Get("/sns/oauth2/access_token") if err != nil { return nil, errors.Wrap(err, "call /sns/oauth2/access_token failed") } return &data, nil } func (we *Client) AuthorizeRefreshAccessToken(accessToken string) (*AuthorizeAccessToken, error) { params := we.wrapParams(map[string]string{ "refresh_token": accessToken, "grant_type": "refresh_token", }) var data AuthorizeAccessToken _, err := we.client.R().SetSuccessResult(&data).SetQueryParams(params).Get("/sns/oauth2/refresh_token") if err != nil { return nil, errors.Wrap(err, "call /sns/oauth2/refresh_token failed") } return &data, nil } type AuthorizeUserInfo struct { City string `json:"city"` Country string `json:"country"` Headimgurl string `json:"headimgurl"` Nickname string `json:"nickname"` Openid string `json:"openid"` Privilege []string `json:"privilege"` Province string `json:"province"` Sex int64 `json:"sex"` Unionid string `json:"unionid"` } func (we *Client) AuthorizeUserInfo(accessToken, openID string) (*AuthorizeUserInfo, error) { params := (map[string]string{ "access_token": accessToken, "openid": openID, }) var data AuthorizeUserInfo _, err := we.client.R().SetSuccessResult(&data).SetQueryParams(params).Get("/sns/userinfo") if err != nil { return nil, errors.Wrap(err, "call /sns/userinfo failed") } return &data, nil }