service_api.go 19 KB

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