wechat_service_api.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. package gopay
  2. import (
  3. "crypto/aes"
  4. "crypto/cipher"
  5. "crypto/hmac"
  6. "crypto/md5"
  7. "crypto/sha256"
  8. "encoding/base64"
  9. "encoding/hex"
  10. "encoding/json"
  11. "encoding/xml"
  12. "errors"
  13. "fmt"
  14. "hash"
  15. "io/ioutil"
  16. "net/http"
  17. "reflect"
  18. "strings"
  19. )
  20. //获取微信支付所需参数里的Sign值(通过支付参数计算Sign值)
  21. // 注意:BodyMap中如无 sign_type 参数,默认赋值 sign_type 为 MD5
  22. // appId:应用ID
  23. // mchId:商户ID
  24. // ApiKey:API秘钥值
  25. // 返回参数 sign:通过Appid、MchId、ApiKey和BodyMap中的参数计算出的Sign值
  26. func GetWeChatParamSign(appId, mchId, apiKey string, bm BodyMap) (sign string) {
  27. bm.Set("appid", appId)
  28. bm.Set("mch_id", mchId)
  29. var (
  30. signType string
  31. h hash.Hash
  32. )
  33. signType = bm.Get("sign_type")
  34. if signType == null {
  35. bm.Set("sign_type", SignType_MD5)
  36. }
  37. if signType == SignType_HMAC_SHA256 {
  38. h = hmac.New(sha256.New, []byte(apiKey))
  39. } else {
  40. h = md5.New()
  41. }
  42. h.Write([]byte(bm.EncodeWeChatSignParams(apiKey)))
  43. sign = strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
  44. return
  45. }
  46. //获取微信支付沙箱环境所需参数里的Sign值(通过支付参数计算Sign值)
  47. // 注意:沙箱环境默认 sign_type 为 MD5
  48. // appId:应用ID
  49. // mchId:商户ID
  50. // ApiKey:API秘钥值
  51. // 返回参数 sign:通过Appid、MchId、ApiKey和BodyMap中的参数计算出的Sign值
  52. func GetWeChatSanBoxParamSign(appId, mchId, apiKey string, bm BodyMap) (sign string, err error) {
  53. bm.Set("appid", appId)
  54. bm.Set("mch_id", mchId)
  55. bm.Set("sign_type", SignType_MD5)
  56. var (
  57. sandBoxApiKey string
  58. hashMd5 hash.Hash
  59. )
  60. if sandBoxApiKey, err = getSanBoxKey(mchId, GetRandomString(32), apiKey, SignType_MD5); err != nil {
  61. return
  62. }
  63. hashMd5 = md5.New()
  64. hashMd5.Write([]byte(bm.EncodeWeChatSignParams(sandBoxApiKey)))
  65. sign = strings.ToUpper(hex.EncodeToString(hashMd5.Sum(nil)))
  66. return
  67. }
  68. //解析微信支付异步通知的结果到BodyMap
  69. // req:*http.Request
  70. // 返回参数bm:Notify请求的参数
  71. // 返回参数err:错误信息
  72. func ParseWeChatNotifyResultToBodyMap(req *http.Request) (bm BodyMap, err error) {
  73. var bs []byte
  74. if bs, err = ioutil.ReadAll(req.Body); err != nil {
  75. return nil, fmt.Errorf("ioutil.ReadAll:%v", err.Error())
  76. }
  77. bm = make(BodyMap)
  78. if err = xml.Unmarshal(bs, &bm); err != nil {
  79. return nil, fmt.Errorf("xml.Unmarshal:%v", err.Error())
  80. }
  81. return
  82. }
  83. //解析微信支付异步通知的参数
  84. // req:*http.Request
  85. // 返回参数notifyReq:Notify请求的参数
  86. // 返回参数err:错误信息
  87. func ParseWeChatNotifyResult(req *http.Request) (notifyReq *WeChatNotifyRequest, err error) {
  88. notifyReq = new(WeChatNotifyRequest)
  89. if err = xml.NewDecoder(req.Body).Decode(notifyReq); err != nil {
  90. return nil, fmt.Errorf("xml.NewDecoder:%v", err.Error())
  91. }
  92. return
  93. }
  94. //验证微信支付异步通知的Sign值(Deprecated)
  95. // ApiKey:API秘钥值
  96. // signType:签名类型 MD5 或 HMAC-SHA256(默认请填写 MD5)
  97. // notifyReq:利用 gopay.ParseWeChatNotifyResult() 得到的结构体
  98. // 返回参数ok:是否验证通过
  99. // 返回参数sign:根据参数计算的sign值,非微信返回参数中的Sign
  100. func VerifyWeChatResultSign(apiKey, signType string, notifyReq *WeChatNotifyRequest) (ok bool, sign string) {
  101. body := make(BodyMap)
  102. body.Set("return_code", notifyReq.ReturnCode)
  103. body.Set("return_msg", notifyReq.ReturnMsg)
  104. body.Set("appid", notifyReq.Appid)
  105. body.Set("mch_id", notifyReq.MchId)
  106. body.Set("device_info", notifyReq.DeviceInfo)
  107. body.Set("nonce_str", notifyReq.NonceStr)
  108. body.Set("sign_type", notifyReq.SignType)
  109. body.Set("result_code", notifyReq.ResultCode)
  110. body.Set("err_code", notifyReq.ErrCode)
  111. body.Set("err_code_des", notifyReq.ErrCodeDes)
  112. body.Set("openid", notifyReq.Openid)
  113. body.Set("is_subscribe", notifyReq.IsSubscribe)
  114. body.Set("trade_type", notifyReq.TradeType)
  115. body.Set("bank_type", notifyReq.BankType)
  116. body.Set("total_fee", notifyReq.TotalFee)
  117. body.Set("settlement_total_fee", notifyReq.SettlementTotalFee)
  118. body.Set("fee_type", notifyReq.FeeType)
  119. body.Set("cash_fee", notifyReq.CashFee)
  120. body.Set("cash_fee_type", notifyReq.CashFeeType)
  121. body.Set("coupon_fee", notifyReq.CouponFee)
  122. body.Set("coupon_count", notifyReq.CouponCount)
  123. body.Set("coupon_type_0", notifyReq.CouponType0)
  124. body.Set("coupon_type_1", notifyReq.CouponType1)
  125. body.Set("coupon_id_0", notifyReq.CouponId0)
  126. body.Set("coupon_id_1", notifyReq.CouponId1)
  127. body.Set("coupon_fee_0", notifyReq.CouponFee0)
  128. body.Set("coupon_fee_1", notifyReq.CouponFee1)
  129. body.Set("transaction_id", notifyReq.TransactionId)
  130. body.Set("out_trade_no", notifyReq.OutTradeNo)
  131. body.Set("attach", notifyReq.Attach)
  132. body.Set("time_end", notifyReq.TimeEnd)
  133. newBody := make(BodyMap)
  134. for key := range body {
  135. vStr := body.Get(key)
  136. if vStr != null && vStr != "0" {
  137. newBody.Set(key, vStr)
  138. }
  139. }
  140. sign = getWeChatReleaseSign(apiKey, signType, newBody)
  141. ok = sign == notifyReq.Sign
  142. return
  143. }
  144. //微信同步返回参数验签或异步通知参数验签
  145. // ApiKey:API秘钥值
  146. // signType:签名类型(调用API方法时填写的类型)
  147. // bean:微信同步返回的结构体 wxRsp 或 异步通知解析的结构体 notifyReq
  148. // 返回参数ok:是否验签通过
  149. // 返回参数err:错误信息
  150. func VerifyWeChatSign(apiKey, signType string, bean interface{}) (ok bool, err error) {
  151. if bean == nil {
  152. return false, errors.New("bean is nil")
  153. }
  154. var (
  155. bm BodyMap
  156. bs []byte
  157. kind reflect.Kind
  158. bodySign string
  159. )
  160. kind = reflect.ValueOf(bean).Kind()
  161. if kind == reflect.Map {
  162. bm = bean.(BodyMap)
  163. goto Verify
  164. }
  165. if bs, err = json.Marshal(bean); err != nil {
  166. return false, fmt.Errorf("json.Marshal:%v", err.Error())
  167. }
  168. bm = make(BodyMap)
  169. if err = json.Unmarshal(bs, &bm); err != nil {
  170. return false, fmt.Errorf("json.Unmarshal:%v", err.Error())
  171. }
  172. Verify:
  173. bodySign = bm.Get("sign")
  174. bm.Remove("sign")
  175. return getWeChatReleaseSign(apiKey, signType, bm) == bodySign, nil
  176. }
  177. type WeChatNotifyResponse struct {
  178. ReturnCode string `xml:"return_code"`
  179. ReturnMsg string `xml:"return_msg"`
  180. }
  181. //返回数据给微信
  182. func (w *WeChatNotifyResponse) ToXmlString() (xmlStr string) {
  183. var buffer strings.Builder
  184. buffer.WriteString("<xml><return_code><![CDATA[")
  185. buffer.WriteString(w.ReturnCode)
  186. buffer.WriteString("]]></return_code>")
  187. buffer.WriteString("<return_msg><![CDATA[")
  188. buffer.WriteString(w.ReturnMsg)
  189. buffer.WriteString("]]></return_msg></xml>")
  190. xmlStr = buffer.String()
  191. return
  192. }
  193. //JSAPI支付,统一下单获取支付参数后,再次计算出小程序用的paySign
  194. // appId:APPID
  195. // nonceStr:随即字符串
  196. // prepayId:统一下单成功后得到的值
  197. // signType:签名类型
  198. // timeStamp:时间
  199. // ApiKey:API秘钥值
  200. // 微信小程序支付API:https://developers.weixin.qq.com/miniprogram/dev/api/open-api/payment/wx.requestPayment.html
  201. func GetMiniPaySign(appId, nonceStr, prepayId, signType, timeStamp, apiKey string) (paySign string) {
  202. var (
  203. buffer strings.Builder
  204. h hash.Hash
  205. )
  206. buffer.WriteString("appId=")
  207. buffer.WriteString(appId)
  208. buffer.WriteString("&nonceStr=")
  209. buffer.WriteString(nonceStr)
  210. buffer.WriteString("&package=")
  211. buffer.WriteString(prepayId)
  212. buffer.WriteString("&signType=")
  213. buffer.WriteString(signType)
  214. buffer.WriteString("&timeStamp=")
  215. buffer.WriteString(timeStamp)
  216. buffer.WriteString("&key=")
  217. buffer.WriteString(apiKey)
  218. if signType == SignType_HMAC_SHA256 {
  219. h = hmac.New(sha256.New, []byte(apiKey))
  220. } else {
  221. h = md5.New()
  222. }
  223. h.Write([]byte(buffer.String()))
  224. return strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
  225. }
  226. //微信内H5支付,统一下单获取支付参数后,再次计算出微信内H5支付需要用的paySign
  227. // appId:APPID
  228. // nonceStr:随即字符串
  229. // packages:统一下单成功后拼接得到的值
  230. // signType:签名类型
  231. // timeStamp:时间
  232. // ApiKey:API秘钥值
  233. // 微信内H5支付官方文档:https://pay.weixin.qq.com/wiki/doc/api/external/jsapi.php?chapter=7_7&index=6
  234. func GetH5PaySign(appId, nonceStr, packages, signType, timeStamp, apiKey string) (paySign string) {
  235. var (
  236. buffer strings.Builder
  237. h hash.Hash
  238. )
  239. buffer.WriteString("appId=")
  240. buffer.WriteString(appId)
  241. buffer.WriteString("&nonceStr=")
  242. buffer.WriteString(nonceStr)
  243. buffer.WriteString("&package=")
  244. buffer.WriteString(packages)
  245. buffer.WriteString("&signType=")
  246. buffer.WriteString(signType)
  247. buffer.WriteString("&timeStamp=")
  248. buffer.WriteString(timeStamp)
  249. buffer.WriteString("&key=")
  250. buffer.WriteString(apiKey)
  251. if signType == SignType_HMAC_SHA256 {
  252. h = hmac.New(sha256.New, []byte(apiKey))
  253. } else {
  254. h = md5.New()
  255. }
  256. h.Write([]byte(buffer.String()))
  257. paySign = strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
  258. return
  259. }
  260. //APP支付,统一下单获取支付参数后,再次计算APP支付所需要的的sign
  261. // appId:APPID
  262. // partnerid:partnerid
  263. // nonceStr:随即字符串
  264. // prepayId:统一下单成功后得到的值
  265. // signType:此处签名方式,务必与统一下单时用的签名方式一致
  266. // timeStamp:时间
  267. // ApiKey:API秘钥值
  268. // APP支付官方文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_12
  269. func GetAppPaySign(appid, partnerid, noncestr, prepayid, signType, timestamp, apiKey string) (paySign string) {
  270. var (
  271. buffer strings.Builder
  272. h hash.Hash
  273. )
  274. buffer.WriteString("appid=")
  275. buffer.WriteString(appid)
  276. buffer.WriteString("&noncestr=")
  277. buffer.WriteString(noncestr)
  278. buffer.WriteString("&package=Sign=WXPay")
  279. buffer.WriteString("&partnerid=")
  280. buffer.WriteString(partnerid)
  281. buffer.WriteString("&prepayid=")
  282. buffer.WriteString(prepayid)
  283. buffer.WriteString("&timestamp=")
  284. buffer.WriteString(timestamp)
  285. buffer.WriteString("&key=")
  286. buffer.WriteString(apiKey)
  287. if signType == SignType_HMAC_SHA256 {
  288. h = hmac.New(sha256.New, []byte(apiKey))
  289. } else {
  290. h = md5.New()
  291. }
  292. h.Write([]byte(buffer.String()))
  293. paySign = strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
  294. return
  295. }
  296. //解密开放数据到结构体
  297. // encryptedData:包括敏感数据在内的完整用户信息的加密数据,小程序获取到
  298. // iv:加密算法的初始向量,小程序获取到
  299. // sessionKey:会话密钥,通过 gopay.Code2Session() 方法获取到
  300. // beanPtr:需要解析到的结构体指针,操作完后,声明的结构体会被赋值
  301. // 文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html
  302. func DecryptWeChatOpenDataToStruct(encryptedData, iv, sessionKey string, beanPtr interface{}) (err error) {
  303. var (
  304. cipherText, aesKey, ivKey, plainText []byte
  305. block cipher.Block
  306. blockMode cipher.BlockMode
  307. )
  308. beanValue := reflect.ValueOf(beanPtr)
  309. if beanValue.Kind() != reflect.Ptr {
  310. return errors.New("传入beanPtr类型必须是以指针形式")
  311. }
  312. if beanValue.Elem().Kind() != reflect.Struct {
  313. return errors.New("传入interface{}必须是结构体")
  314. }
  315. cipherText, _ = base64.StdEncoding.DecodeString(encryptedData)
  316. aesKey, _ = base64.StdEncoding.DecodeString(sessionKey)
  317. ivKey, _ = base64.StdEncoding.DecodeString(iv)
  318. if len(cipherText)%len(aesKey) != 0 {
  319. return errors.New("encryptedData is error")
  320. }
  321. if block, err = aes.NewCipher(aesKey); err != nil {
  322. return fmt.Errorf("aes.NewCipher:%v", err.Error())
  323. }
  324. blockMode = cipher.NewCBCDecrypter(block, ivKey)
  325. plainText = make([]byte, len(cipherText))
  326. blockMode.CryptBlocks(plainText, cipherText)
  327. if len(plainText) > 0 {
  328. plainText = PKCS7UnPadding(plainText)
  329. }
  330. if err = json.Unmarshal(plainText, beanPtr); err != nil {
  331. return fmt.Errorf("json.Unmarshal:%v", err.Error())
  332. }
  333. return
  334. }
  335. //解密开放数据到 BodyMap
  336. // encryptedData:包括敏感数据在内的完整用户信息的加密数据,小程序获取到
  337. // iv:加密算法的初始向量,小程序获取到
  338. // sessionKey:会话密钥,通过 gopay.Code2Session() 方法获取到
  339. // 文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html
  340. func DecryptWeChatOpenDataToBodyMap(encryptedData, iv, sessionKey string) (bm BodyMap, err error) {
  341. var (
  342. cipherText, aesKey, ivKey, plainText []byte
  343. block cipher.Block
  344. blockMode cipher.BlockMode
  345. )
  346. cipherText, _ = base64.StdEncoding.DecodeString(encryptedData)
  347. aesKey, _ = base64.StdEncoding.DecodeString(sessionKey)
  348. ivKey, _ = base64.StdEncoding.DecodeString(iv)
  349. if len(cipherText)%len(aesKey) != 0 {
  350. return nil, errors.New("encryptedData is error")
  351. }
  352. if block, err = aes.NewCipher(aesKey); err != nil {
  353. return nil, fmt.Errorf("aes.NewCipher:%v", err.Error())
  354. } else {
  355. blockMode = cipher.NewCBCDecrypter(block, ivKey)
  356. plainText = make([]byte, len(cipherText))
  357. blockMode.CryptBlocks(plainText, cipherText)
  358. if len(plainText) > 0 {
  359. plainText = PKCS7UnPadding(plainText)
  360. }
  361. bm = make(BodyMap)
  362. if err = json.Unmarshal(plainText, &bm); err != nil {
  363. return nil, fmt.Errorf("json.Unmarshal:%v", err.Error())
  364. }
  365. return
  366. }
  367. }
  368. //App应用微信第三方登录,code换取access_token
  369. // appId:应用唯一标识,在微信开放平台提交应用审核通过后获得
  370. // appSecret:应用密钥AppSecret,在微信开放平台提交应用审核通过后获得
  371. // code:App用户换取access_token的code
  372. func GetAppWeChatLoginAccessToken(appId, appSecret, code string) (accessToken *AppWeChatLoginAccessToken, err error) {
  373. accessToken = new(AppWeChatLoginAccessToken)
  374. url := "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appId + "&secret=" + appSecret + "&code=" + code + "&grant_type=authorization_code"
  375. if _, _, errs := HttpAgent().Get(url).EndStruct(accessToken); len(errs) > 0 {
  376. return nil, errs[0]
  377. }
  378. return
  379. }
  380. //刷新App应用微信第三方登录后,获取的 access_token
  381. // appId:应用唯一标识,在微信开放平台提交应用审核通过后获得
  382. // appSecret:应用密钥AppSecret,在微信开放平台提交应用审核通过后获得
  383. // code:App用户换取access_token的code
  384. func RefreshAppWeChatLoginAccessToken(appId, refreshToken string) (accessToken *RefreshAppWeChatLoginAccessTokenRsp, err error) {
  385. accessToken = new(RefreshAppWeChatLoginAccessTokenRsp)
  386. url := "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=" + appId + "&grant_type=refresh_token&refresh_token=" + refreshToken
  387. if _, _, errs := HttpAgent().Get(url).EndStruct(accessToken); len(errs) > 0 {
  388. return nil, errs[0]
  389. }
  390. return
  391. }
  392. //获取微信小程序用户的OpenId、SessionKey、UnionId
  393. // appId:APPID
  394. // appSecret:AppSecret
  395. // wxCode:小程序调用wx.login 获取的code
  396. // 文档:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html
  397. func Code2Session(appId, appSecret, wxCode string) (sessionRsp *Code2SessionRsp, err error) {
  398. sessionRsp = new(Code2SessionRsp)
  399. url := "https://api.weixin.qq.com/sns/jscode2session?appid=" + appId + "&secret=" + appSecret + "&js_code=" + wxCode + "&grant_type=authorization_code"
  400. if _, _, errs := HttpAgent().Get(url).EndStruct(sessionRsp); len(errs) > 0 {
  401. return nil, errs[0]
  402. }
  403. return
  404. }
  405. //获取微信小程序全局唯一后台接口调用凭据(AccessToken:157字符)
  406. // appId:APPID
  407. // appSecret:AppSecret
  408. // 获取access_token文档:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/access-token/auth.getAccessToken.html
  409. func GetWeChatAppletAccessToken(appId, appSecret string) (accessToken *AccessToken, err error) {
  410. accessToken = new(AccessToken)
  411. url := "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + appSecret
  412. if _, _, errs := HttpAgent().Get(url).EndStruct(accessToken); len(errs) > 0 {
  413. return nil, errs[0]
  414. }
  415. return
  416. }
  417. //授权码查询openid(AccessToken:157字符)
  418. // appId:APPID
  419. // mchId:商户号
  420. // ApiKey:apiKey
  421. // authCode:用户授权码
  422. // nonceStr:随即字符串
  423. // 文档:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_13&index=9
  424. func GetOpenIdByAuthCode(appId, mchId, apiKey, authCode, nonceStr string) (openIdRsp *OpenIdByAuthCodeRsp, err error) {
  425. var (
  426. url string
  427. bm BodyMap
  428. bs []byte
  429. errs []error
  430. )
  431. url = "https://api.mch.weixin.qq.com/tools/authcodetoopenid"
  432. bm = make(BodyMap)
  433. bm.Set("appid", appId)
  434. bm.Set("mch_id", mchId)
  435. bm.Set("auth_code", authCode)
  436. bm.Set("nonce_str", nonceStr)
  437. bm.Set("sign", getWeChatReleaseSign(apiKey, SignType_MD5, bm))
  438. if _, bs, errs = HttpAgent().Post(url).Type("xml").SendString(generateXml(bm)).EndBytes(); len(errs) > 0 {
  439. return nil, errs[0]
  440. }
  441. openIdRsp = new(OpenIdByAuthCodeRsp)
  442. if err = xml.Unmarshal(bs, openIdRsp); err != nil {
  443. return nil, fmt.Errorf("xml.Unmarshal:%v", err.Error())
  444. }
  445. return
  446. }
  447. //微信小程序用户支付完成后,获取该用户的 UnionId,无需用户授权。
  448. // accessToken:接口调用凭据
  449. // openId:用户的OpenID
  450. // transactionId:微信支付订单号
  451. // 文档:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/user-info/auth.getPaidUnionId.html
  452. func GetWeChatAppletPaidUnionId(accessToken, openId, transactionId string) (unionId *PaidUnionId, err error) {
  453. unionId = new(PaidUnionId)
  454. url := "https://api.weixin.qq.com/wxa/getpaidunionid?access_token=" + accessToken + "&openid=" + openId + "&transaction_id=" + transactionId
  455. if _, _, errs := HttpAgent().Get(url).EndStruct(unionId); len(errs) > 0 {
  456. return nil, errs[0]
  457. }
  458. return
  459. }
  460. //获取用户基本信息(UnionID机制)
  461. // accessToken:接口调用凭据
  462. // openId:用户的OpenID
  463. // lang:默认为 zh_CN ,可选填 zh_CN 简体,zh_TW 繁体,en 英语
  464. // 获取用户基本信息(UnionID机制)文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140839
  465. func GetWeChatUserInfo(accessToken, openId string, lang ...string) (userInfo *WeChatUserInfo, err error) {
  466. userInfo = new(WeChatUserInfo)
  467. var url string
  468. if len(lang) > 0 {
  469. url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=" + accessToken + "&openid=" + openId + "&lang=" + lang[0]
  470. } else {
  471. url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=" + accessToken + "&openid=" + openId + "&lang=zh_CN"
  472. }
  473. if _, _, errs := HttpAgent().Get(url).EndStruct(userInfo); len(errs) > 0 {
  474. return nil, errs[0]
  475. }
  476. return
  477. }