183 lines
4.1 KiB
Go
183 lines
4.1 KiB
Go
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
|
|
}
|