auth.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. package auth
  2. import (
  3. context2 "context"
  4. "encoding/json"
  5. "fmt"
  6. "github.com/silenceper/wechat/v2/miniprogram/context"
  7. "github.com/silenceper/wechat/v2/util"
  8. )
  9. const (
  10. // code2SessionURL 小程序登录
  11. code2SessionURL = "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code"
  12. // checkEncryptedDataURL 检查加密信息
  13. checkEncryptedDataURL = "https://api.weixin.qq.com/wxa/business/checkencryptedmsg?access_token=%s"
  14. // getPhoneNumber 获取手机号
  15. getPhoneNumber = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s"
  16. // checkSessionURL 检验登录态
  17. checkSessionURL = "https://api.weixin.qq.com/wxa/checksession?access_token=%s&signature=%s&openid=%s&sig_method=hmac_sha256"
  18. // resetUserSessionKeyURL 重置登录态
  19. resetUserSessionKeyURL = "https://api.weixin.qq.com/wxa/resetusersessionkey?access_token=%s&signature=%s&openid=%s&sig_method=hmac_sha256"
  20. // getPluginOpenPIDURL 获取插件用户openPID
  21. getPluginOpenPIDURL = "https://api.weixin.qq.com/wxa/getpluginopenpid?access_token=%s"
  22. // getPaidUnionIDURL 支付后获取 UnionID
  23. getPaidUnionIDURL = "https://api.weixin.qq.com/wxa/getpaidunionid"
  24. // getUserEncryptKeyURL 获取用户encryptKey
  25. getUserEncryptKeyURL = "https://api.weixin.qq.com/wxa/business/getuserencryptkey?access_token=%s&signature=%s&openid=%s&sig_method=hmac_sha256"
  26. )
  27. // Auth 登录/用户信息
  28. type Auth struct {
  29. *context.Context
  30. }
  31. // NewAuth new auth
  32. func NewAuth(ctx *context.Context) *Auth {
  33. return &Auth{ctx}
  34. }
  35. // ResCode2Session 登录凭证校验的返回结果
  36. type ResCode2Session struct {
  37. util.CommonError
  38. OpenID string `json:"openid"` // 用户唯一标识
  39. SessionKey string `json:"session_key"` // 会话密钥
  40. UnionID string `json:"unionid"` // 用户在开放平台的唯一标识符,在满足UnionID下发条件的情况下会返回
  41. }
  42. // RspCheckEncryptedData .
  43. type RspCheckEncryptedData struct {
  44. util.CommonError
  45. Vaild bool `json:"vaild"` // 是否是合法的数据
  46. CreateTime uint64 `json:"create_time"` // 加密数据生成的时间戳
  47. }
  48. // Code2Session 登录凭证校验。
  49. func (auth *Auth) Code2Session(jsCode string) (result ResCode2Session, err error) {
  50. return auth.Code2SessionContext(context2.Background(), jsCode)
  51. }
  52. // Code2SessionContext 登录凭证校验。
  53. func (auth *Auth) Code2SessionContext(ctx context2.Context, jsCode string) (result ResCode2Session, err error) {
  54. var response []byte
  55. if response, err = util.HTTPGetContext(ctx, fmt.Sprintf(code2SessionURL, auth.AppID, auth.AppSecret, jsCode)); err != nil {
  56. return
  57. }
  58. if err = json.Unmarshal(response, &result); err != nil {
  59. return
  60. }
  61. if result.ErrCode != 0 {
  62. err = fmt.Errorf("Code2Session error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
  63. return
  64. }
  65. return
  66. }
  67. type (
  68. // GetPaidUnionIDRequest 支付后获取UnionID请求
  69. GetPaidUnionIDRequest struct {
  70. OpenID string `json:"openid"`
  71. TransactionID string `json:"transaction_id,omitempty"`
  72. MchID string `json:"mch_id,omitempty"`
  73. OutTradeNo string `json:"out_trade_no,omitempty"`
  74. }
  75. // GetPaidUnionIDResponse 支付后获取UnionID响应
  76. GetPaidUnionIDResponse struct {
  77. util.CommonError
  78. UnionID string `json:"unionid"`
  79. }
  80. )
  81. // GetPaidUnionID 用户支付完成后,获取该用户的 UnionId,无需用户授权
  82. // see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-info/basic-info/getPaidUnionid.html
  83. func (auth *Auth) GetPaidUnionID(req *GetPaidUnionIDRequest) (string, error) {
  84. var (
  85. accessToken string
  86. err error
  87. )
  88. if accessToken, err = auth.GetAccessToken(); err != nil {
  89. return "", err
  90. }
  91. var url string
  92. if req.TransactionID != "" {
  93. url = fmt.Sprintf("%s?access_token=%s&openid=%s&transaction_id=%s", getPaidUnionIDURL, accessToken, req.OpenID, req.TransactionID)
  94. } else {
  95. url = fmt.Sprintf("%s?access_token=%s&openid=%s&mch_id=%s&out_trade_no=%s", getPaidUnionIDURL, accessToken, req.OpenID, req.MchID, req.OutTradeNo)
  96. }
  97. var response []byte
  98. if response, err = util.HTTPGet(url); err != nil {
  99. return "", err
  100. }
  101. result := &GetPaidUnionIDResponse{}
  102. err = util.DecodeWithError(response, result, "GetPaidUnionID")
  103. return result.UnionID, err
  104. }
  105. // CheckEncryptedData .检查加密信息是否由微信生成(当前只支持手机号加密数据),只能检测最近3天生成的加密数据
  106. func (auth *Auth) CheckEncryptedData(encryptedMsgHash string) (result RspCheckEncryptedData, err error) {
  107. return auth.CheckEncryptedDataContext(context2.Background(), encryptedMsgHash)
  108. }
  109. // CheckEncryptedDataContext .检查加密信息是否由微信生成(当前只支持手机号加密数据),只能检测最近3天生成的加密数据
  110. func (auth *Auth) CheckEncryptedDataContext(ctx context2.Context, encryptedMsgHash string) (result RspCheckEncryptedData, err error) {
  111. var response []byte
  112. var (
  113. at string
  114. )
  115. if at, err = auth.GetAccessTokenContext(ctx); err != nil {
  116. return
  117. }
  118. // 由于GetPhoneNumberContext需要传入JSON,所以HTTPPostContext入参改为[]byte
  119. if response, err = util.HTTPPostContext(ctx, fmt.Sprintf(checkEncryptedDataURL, at), []byte("encrypted_msg_hash="+encryptedMsgHash), nil); err != nil {
  120. return
  121. }
  122. if err = util.DecodeWithError(response, &result, "CheckEncryptedDataAuth"); err != nil {
  123. return
  124. }
  125. return
  126. }
  127. // GetPhoneNumberResponse 新版获取用户手机号响应结构体
  128. type GetPhoneNumberResponse struct {
  129. util.CommonError
  130. PhoneInfo PhoneInfo `json:"phone_info"`
  131. }
  132. // PhoneInfo 获取用户手机号内容
  133. type PhoneInfo struct {
  134. PhoneNumber string `json:"phoneNumber"` // 用户绑定的手机号
  135. PurePhoneNumber string `json:"purePhoneNumber"` // 没有区号的手机号
  136. CountryCode string `json:"countryCode"` // 区号
  137. WaterMark struct {
  138. Timestamp int64 `json:"timestamp"`
  139. AppID string `json:"appid"`
  140. } `json:"watermark"` // 数据水印
  141. }
  142. // GetPhoneNumberContext 小程序通过code获取用户手机号
  143. func (auth *Auth) GetPhoneNumberContext(ctx context2.Context, code string) (*GetPhoneNumberResponse, error) {
  144. var response []byte
  145. var (
  146. at string
  147. err error
  148. )
  149. if at, err = auth.GetAccessTokenContext(ctx); err != nil {
  150. return nil, err
  151. }
  152. body := map[string]interface{}{
  153. "code": code,
  154. }
  155. bodyBytes, err := json.Marshal(body)
  156. if err != nil {
  157. return nil, err
  158. }
  159. header := map[string]string{"Content-Type": "application/json;charset=utf-8"}
  160. if response, err = util.HTTPPostContext(ctx, fmt.Sprintf(getPhoneNumber, at), bodyBytes, header); err != nil {
  161. return nil, err
  162. }
  163. var result GetPhoneNumberResponse
  164. err = util.DecodeWithError(response, &result, "phonenumber.getPhoneNumber")
  165. return &result, err
  166. }
  167. // GetPhoneNumber 小程序通过code获取用户手机号
  168. func (auth *Auth) GetPhoneNumber(code string) (*GetPhoneNumberResponse, error) {
  169. return auth.GetPhoneNumberContext(context2.Background(), code)
  170. }
  171. // CheckSession 检验登录态
  172. // see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/checkSessionKey.html
  173. func (auth *Auth) CheckSession(signature, openID string) error {
  174. var (
  175. accessToken string
  176. err error
  177. )
  178. if accessToken, err = auth.GetAccessToken(); err != nil {
  179. return err
  180. }
  181. var response []byte
  182. if response, err = util.HTTPGet(fmt.Sprintf(checkSessionURL, accessToken, signature, openID)); err != nil {
  183. return err
  184. }
  185. return util.DecodeWithCommonError(response, "CheckSession")
  186. }
  187. // ResetUserSessionKeyResponse 重置登录态响应
  188. type ResetUserSessionKeyResponse struct {
  189. util.CommonError
  190. OpenID string `json:"openid"`
  191. SessionKey string `json:"session_key"`
  192. }
  193. // ResetUserSessionKey 重置登录态
  194. // see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/ResetUserSessionKey.html
  195. func (auth *Auth) ResetUserSessionKey(signature, openID string) (*ResetUserSessionKeyResponse, error) {
  196. var (
  197. accessToken string
  198. err error
  199. )
  200. if accessToken, err = auth.GetAccessToken(); err != nil {
  201. return nil, err
  202. }
  203. var response []byte
  204. if response, err = util.HTTPGet(fmt.Sprintf(resetUserSessionKeyURL, accessToken, signature, openID)); err != nil {
  205. return nil, err
  206. }
  207. result := &ResetUserSessionKeyResponse{}
  208. err = util.DecodeWithError(response, result, "ResetUserSessionKey")
  209. return result, err
  210. }
  211. type (
  212. // GetPluginOpenPIDRequest 获取插件用户openPID请求
  213. GetPluginOpenPIDRequest struct {
  214. Code string `json:"code"`
  215. }
  216. // GetPluginOpenPIDResponse 获取插件用户openPID响应
  217. GetPluginOpenPIDResponse struct {
  218. util.CommonError
  219. OpenPID string `json:"openpid"`
  220. }
  221. )
  222. // GetPluginOpenPID 获取插件用户openPID
  223. // see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-info/basic-info/getPluginOpenPId.html
  224. func (auth *Auth) GetPluginOpenPID(code string) (string, error) {
  225. var (
  226. accessToken string
  227. err error
  228. )
  229. if accessToken, err = auth.GetAccessToken(); err != nil {
  230. return "", err
  231. }
  232. req := &GetPluginOpenPIDRequest{
  233. Code: code,
  234. }
  235. var response []byte
  236. if response, err = util.PostJSON(fmt.Sprintf(getPluginOpenPIDURL, accessToken), req); err != nil {
  237. return "", err
  238. }
  239. result := &GetPluginOpenPIDResponse{}
  240. err = util.DecodeWithError(response, result, "GetPluginOpenPID")
  241. return result.OpenPID, err
  242. }
  243. // GetUserEncryptKeyResponse 获取用户encryptKey响应
  244. type GetUserEncryptKeyResponse struct {
  245. util.CommonError
  246. KeyInfoList []KeyInfo `json:"key_info_list"`
  247. }
  248. // KeyInfo 用户最近三次的加密key
  249. type KeyInfo struct {
  250. EncryptKey string `json:"encrypt_key"`
  251. Version int64 `json:"version"`
  252. ExpireIn int64 `json:"expire_in"`
  253. Iv string `json:"iv"`
  254. CreateTime int64 `json:"create_time"`
  255. }
  256. // GetUserEncryptKey 获取用户encryptKey
  257. // see https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-info/internet/getUserEncryptKey.html
  258. func (auth *Auth) GetUserEncryptKey(signature, openID string) (*GetUserEncryptKeyResponse, error) {
  259. var (
  260. accessToken string
  261. err error
  262. )
  263. if accessToken, err = auth.GetAccessToken(); err != nil {
  264. return nil, err
  265. }
  266. var response []byte
  267. if response, err = util.HTTPGet(fmt.Sprintf(getUserEncryptKeyURL, accessToken, signature, openID)); err != nil {
  268. return nil, err
  269. }
  270. result := &GetUserEncryptKeyResponse{}
  271. err = util.DecodeWithError(response, result, "GetUserEncryptKey")
  272. return result, err
  273. }