service_api.go 18 KB

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