wechat_servier_api.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. package gopay
  2. import (
  3. "bytes"
  4. "crypto/aes"
  5. "crypto/cipher"
  6. "crypto/hmac"
  7. "crypto/md5"
  8. "crypto/sha256"
  9. "encoding/base64"
  10. "encoding/hex"
  11. "encoding/json"
  12. "encoding/xml"
  13. "errors"
  14. "github.com/parnurzeal/gorequest"
  15. "io/ioutil"
  16. "net/http"
  17. "reflect"
  18. "strings"
  19. )
  20. //解析微信支付异步通知的结果到BodyMap
  21. // req:*http.Request
  22. // 返回参数bm:Notify请求的参数
  23. // 返回参数err:错误信息
  24. func ParseWeChatNotifyResultToBodyMap(req *http.Request) (bm BodyMap, err error) {
  25. bs, err := ioutil.ReadAll(req.Body)
  26. defer req.Body.Close()
  27. if err != nil {
  28. return nil, err
  29. }
  30. //获取Notify请求参数
  31. bm = make(BodyMap)
  32. err = xml.Unmarshal(bs, &bm)
  33. if err != nil {
  34. return nil, err
  35. }
  36. return
  37. }
  38. //通过BodyMap验证微信支付异步通知的Sign值
  39. // apiKey:API秘钥值
  40. // signType:签名类型 MD5 或 HMAC-SHA256(默认请填写 MD5)
  41. // bm:通过 gopay.ParseWeChatNotifyResult() 得到的BodyMap
  42. // 返回参数ok:是否验证通过
  43. // 返回参数sign:计算出的sign值,非微信返回参数中的Sign
  44. func VerifyWeChatResultSignByBodyMap(apiKey string, signType string, bm BodyMap) (ok bool, sign string) {
  45. //验证Sign值的BodyMap
  46. bmNew := make(BodyMap)
  47. for key := range bm {
  48. if key != "sign" {
  49. vStr := bm.Get(key)
  50. bmNew.Set(key, vStr)
  51. }
  52. }
  53. //验证Sign值
  54. sign = getLocalSign(apiKey, signType, bmNew)
  55. ok = sign == bm.Get("sign")
  56. return
  57. }
  58. //解析微信支付异步通知的参数
  59. // req:*http.Request
  60. // 返回参数notifyReq:Notify请求的参数
  61. // 返回参数err:错误信息
  62. func ParseWeChatNotifyResult(req *http.Request) (notifyReq *WeChatNotifyRequest, err error) {
  63. notifyReq = new(WeChatNotifyRequest)
  64. defer req.Body.Close()
  65. err = xml.NewDecoder(req.Body).Decode(notifyReq)
  66. if err != nil {
  67. return nil, err
  68. }
  69. return
  70. }
  71. //验证微信支付异步通知的Sign值
  72. // apiKey:API秘钥值
  73. // signType:签名类型 MD5 或 HMAC-SHA256(默认请填写 MD5)
  74. // notifyReq:利用 gopay.ParseWeChatNotifyResult() 得到的结构体
  75. // 返回参数ok:是否验证通过
  76. // 返回参数sign:根据参数计算的sign值,非微信返回参数中的Sign
  77. func VerifyWeChatResultSign(apiKey string, signType string, notifyReq *WeChatNotifyRequest) (ok bool, sign string) {
  78. body := make(BodyMap)
  79. body.Set("return_code", notifyReq.ReturnCode)
  80. body.Set("return_msg", notifyReq.ReturnMsg)
  81. body.Set("appid", notifyReq.Appid)
  82. body.Set("mch_id", notifyReq.MchId)
  83. body.Set("device_info", notifyReq.DeviceInfo)
  84. body.Set("nonce_str", notifyReq.NonceStr)
  85. body.Set("sign_type", notifyReq.SignType)
  86. body.Set("result_code", notifyReq.ResultCode)
  87. body.Set("err_code", notifyReq.ErrCode)
  88. body.Set("err_code_des", notifyReq.ErrCodeDes)
  89. body.Set("openid", notifyReq.Openid)
  90. body.Set("is_subscribe", notifyReq.IsSubscribe)
  91. body.Set("trade_type", notifyReq.TradeType)
  92. body.Set("bank_type", notifyReq.BankType)
  93. body.Set("total_fee", notifyReq.TotalFee)
  94. body.Set("settlement_total_fee", notifyReq.SettlementTotalFee)
  95. body.Set("fee_type", notifyReq.FeeType)
  96. body.Set("cash_fee", notifyReq.CashFee)
  97. body.Set("cash_fee_type", notifyReq.CashFeeType)
  98. body.Set("coupon_fee", notifyReq.CouponFee)
  99. body.Set("coupon_count", notifyReq.CouponCount)
  100. body.Set("coupon_type_0", notifyReq.CouponType0)
  101. body.Set("coupon_type_1", notifyReq.CouponType1)
  102. body.Set("coupon_type_2", notifyReq.CouponType2)
  103. body.Set("coupon_id_0", notifyReq.CouponId0)
  104. body.Set("coupon_id_1", notifyReq.CouponId1)
  105. body.Set("coupon_id_2", notifyReq.CouponId2)
  106. body.Set("coupon_fee_0", notifyReq.CouponFee0)
  107. body.Set("coupon_fee_1", notifyReq.CouponFee1)
  108. body.Set("coupon_fee_2", notifyReq.CouponFee2)
  109. body.Set("transaction_id", notifyReq.TransactionId)
  110. body.Set("out_trade_no", notifyReq.OutTradeNo)
  111. body.Set("attach", notifyReq.Attach)
  112. body.Set("time_end", notifyReq.TimeEnd)
  113. newBody := make(BodyMap)
  114. for key := range body {
  115. vStr := body.Get(key)
  116. if vStr != null && vStr != "0" {
  117. newBody.Set(key, vStr)
  118. }
  119. }
  120. sign = getLocalSign(apiKey, signType, newBody)
  121. ok = sign == notifyReq.Sign
  122. return
  123. }
  124. type WeChatNotifyResponse struct {
  125. ReturnCode string `xml:"return_code"`
  126. ReturnMsg string `xml:"return_msg"`
  127. }
  128. //返回数据给微信
  129. func (this *WeChatNotifyResponse) ToXmlString() (xmlStr string) {
  130. buffer := new(bytes.Buffer)
  131. buffer.WriteString("<xml><return_code><![CDATA[")
  132. buffer.WriteString(this.ReturnCode)
  133. buffer.WriteString("]]></return_code>")
  134. buffer.WriteString("<return_msg><![CDATA[")
  135. buffer.WriteString(this.ReturnMsg)
  136. buffer.WriteString("]]></return_msg></xml>")
  137. xmlStr = buffer.String()
  138. return
  139. }
  140. //JSAPI支付,统一下单获取支付参数后,再次计算出小程序用的paySign
  141. // appId:APPID
  142. // nonceStr:随即字符串
  143. // prepayId:统一下单成功后得到的值
  144. // signType:签名类型
  145. // timeStamp:时间
  146. // apiKey:API秘钥值
  147. func GetMiniPaySign(appId, nonceStr, prepayId, signType, timeStamp, apiKey string) (paySign string) {
  148. buffer := new(bytes.Buffer)
  149. buffer.WriteString("appId=")
  150. buffer.WriteString(appId)
  151. buffer.WriteString("&nonceStr=")
  152. buffer.WriteString(nonceStr)
  153. buffer.WriteString("&package=")
  154. buffer.WriteString(prepayId)
  155. buffer.WriteString("&signType=")
  156. buffer.WriteString(signType)
  157. buffer.WriteString("&timeStamp=")
  158. buffer.WriteString(timeStamp)
  159. buffer.WriteString("&key=")
  160. buffer.WriteString(apiKey)
  161. signStr := buffer.String()
  162. var hashSign []byte
  163. if signType == SignType_HMAC_SHA256 {
  164. hash := hmac.New(sha256.New, []byte(apiKey))
  165. hash.Write([]byte(signStr))
  166. hashSign = hash.Sum(nil)
  167. } else {
  168. hash := md5.New()
  169. hash.Write([]byte(signStr))
  170. hashSign = hash.Sum(nil)
  171. }
  172. paySign = strings.ToUpper(hex.EncodeToString(hashSign))
  173. return
  174. }
  175. //JSAPI支付,统一下单获取支付参数后,再次计算出微信内H5支付需要用的paySign
  176. // appId:APPID
  177. // nonceStr:随即字符串
  178. // prepayId:统一下单成功后得到的值
  179. // signType:签名类型
  180. // timeStamp:时间
  181. // apiKey:API秘钥值
  182. func GetH5PaySign(appId, nonceStr, prepayId, signType, timeStamp, apiKey string) (paySign string) {
  183. buffer := new(bytes.Buffer)
  184. buffer.WriteString("appId=")
  185. buffer.WriteString(appId)
  186. buffer.WriteString("&nonceStr=")
  187. buffer.WriteString(nonceStr)
  188. buffer.WriteString("&package=")
  189. buffer.WriteString(prepayId)
  190. buffer.WriteString("&signType=")
  191. buffer.WriteString(signType)
  192. buffer.WriteString("&timeStamp=")
  193. buffer.WriteString(timeStamp)
  194. buffer.WriteString("&key=")
  195. buffer.WriteString(apiKey)
  196. signStr := buffer.String()
  197. var hashSign []byte
  198. if signType == SignType_HMAC_SHA256 {
  199. hash := hmac.New(sha256.New, []byte(apiKey))
  200. hash.Write([]byte(signStr))
  201. hashSign = hash.Sum(nil)
  202. } else {
  203. hash := md5.New()
  204. hash.Write([]byte(signStr))
  205. hashSign = hash.Sum(nil)
  206. }
  207. paySign = strings.ToUpper(hex.EncodeToString(hashSign))
  208. return
  209. }
  210. //APP支付,统一下单获取支付参数后,再次计算APP支付所需要的的sign
  211. // appId:APPID
  212. // partnerid:partnerid
  213. // nonceStr:随即字符串
  214. // prepayId:统一下单成功后得到的值
  215. // signType:此处签名方式,务必与统一下单时用的签名方式一致
  216. // timeStamp:时间
  217. // apiKey:API秘钥值
  218. func GetAppPaySign(appid, partnerid, noncestr, prepayid, signType, timestamp, apiKey string) (paySign string) {
  219. buffer := new(bytes.Buffer)
  220. buffer.WriteString("appid=")
  221. buffer.WriteString(appid)
  222. buffer.WriteString("&noncestr=")
  223. buffer.WriteString(noncestr)
  224. buffer.WriteString("&package=Sign=WXPay")
  225. buffer.WriteString("&partnerid=")
  226. buffer.WriteString(partnerid)
  227. buffer.WriteString("&prepayid=")
  228. buffer.WriteString(prepayid)
  229. buffer.WriteString("&timestamp=")
  230. buffer.WriteString(timestamp)
  231. buffer.WriteString("&key=")
  232. buffer.WriteString(apiKey)
  233. signStr := buffer.String()
  234. var hashSign []byte
  235. if signType == SignType_HMAC_SHA256 {
  236. hash := hmac.New(sha256.New, []byte(apiKey))
  237. hash.Write([]byte(signStr))
  238. hashSign = hash.Sum(nil)
  239. } else {
  240. hash := md5.New()
  241. hash.Write([]byte(signStr))
  242. hashSign = hash.Sum(nil)
  243. }
  244. paySign = strings.ToUpper(hex.EncodeToString(hashSign))
  245. return
  246. }
  247. //解密开放数据
  248. // encryptedData:包括敏感数据在内的完整用户信息的加密数据
  249. // iv:加密算法的初始向量
  250. // sessionKey:会话密钥
  251. // beanPtr:需要解析到的结构体指针
  252. func DecryptOpenDataToStruct(encryptedData, iv, sessionKey string, beanPtr interface{}) (err error) {
  253. //验证参数类型
  254. beanValue := reflect.ValueOf(beanPtr)
  255. if beanValue.Kind() != reflect.Ptr {
  256. return errors.New("传入beanPtr类型必须是以指针形式")
  257. }
  258. //验证interface{}类型
  259. if beanValue.Elem().Kind() != reflect.Struct {
  260. return errors.New("传入interface{}必须是结构体")
  261. }
  262. aesKey, _ := base64.StdEncoding.DecodeString(sessionKey)
  263. ivKey, _ := base64.StdEncoding.DecodeString(iv)
  264. cipherText, _ := base64.StdEncoding.DecodeString(encryptedData)
  265. if len(cipherText)%len(aesKey) != 0 {
  266. return errors.New("encryptedData is error")
  267. }
  268. //fmt.Println("cipherText:", cipherText)
  269. block, err := aes.NewCipher(aesKey)
  270. if err != nil {
  271. return err
  272. }
  273. //解密
  274. blockMode := cipher.NewCBCDecrypter(block, ivKey)
  275. plainText := make([]byte, len(cipherText))
  276. blockMode.CryptBlocks(plainText, cipherText)
  277. //fmt.Println("plainText1:", plainText)
  278. plainText = PKCS7UnPadding(plainText)
  279. //fmt.Println("plainText:", plainText)
  280. //解析
  281. err = json.Unmarshal(plainText, beanPtr)
  282. if err != nil {
  283. return err
  284. }
  285. return nil
  286. }
  287. //获取微信用户的OpenId、SessionKey、UnionId
  288. // appId:APPID
  289. // appSecret:AppSecret
  290. // wxCode:小程序调用wx.login 获取的code
  291. func Code2Session(appId, appSecret, wxCode string) (sessionRsp *Code2SessionRsp, err error) {
  292. sessionRsp = new(Code2SessionRsp)
  293. url := "https://api.weixin.qq.com/sns/jscode2session?appid=" + appId + "&secret=" + appSecret + "&js_code=" + wxCode + "&grant_type=authorization_code"
  294. agent := HttpAgent()
  295. _, _, errs := agent.Get(url).EndStruct(sessionRsp)
  296. if len(errs) > 0 {
  297. return nil, errs[0]
  298. } else {
  299. return sessionRsp, nil
  300. }
  301. }
  302. //获取小程序全局唯一后台接口调用凭据(AccessToken:157字符)
  303. // appId:APPID
  304. // appSecret:AppSecret
  305. func GetAccessToken(appId, appSecret string) (accessToken *AccessToken, err error) {
  306. accessToken = new(AccessToken)
  307. url := "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + appSecret
  308. agent := HttpAgent()
  309. _, _, errs := agent.Get(url).EndStruct(accessToken)
  310. if len(errs) > 0 {
  311. return nil, errs[0]
  312. } else {
  313. return accessToken, nil
  314. }
  315. }
  316. //授权码查询openid(AccessToken:157字符)
  317. // appId:APPID
  318. // mchId:商户号
  319. // apiKey:ApiKey
  320. // authCode:用户授权码
  321. // nonceStr:随即字符串
  322. func GetOpenIdByAuthCode(appId, mchId, apiKey, authCode, nonceStr string) (openIdRsp *OpenIdByAuthCodeRsp, err error) {
  323. url := "https://api.mch.weixin.qq.com/tools/authcodetoopenid"
  324. body := make(BodyMap)
  325. body.Set("appid", appId)
  326. body.Set("mch_id", mchId)
  327. body.Set("auth_code", authCode)
  328. body.Set("nonce_str", nonceStr)
  329. sign := getLocalSign(apiKey, SignType_MD5, body)
  330. body.Set("sign", sign)
  331. reqXML := generateXml(body)
  332. //===============发起请求===================
  333. agent := gorequest.New()
  334. agent.Post(url)
  335. agent.Type("xml")
  336. agent.SendString(reqXML)
  337. _, bs, errs := agent.EndBytes()
  338. if len(errs) > 0 {
  339. return nil, errs[0]
  340. }
  341. openIdRsp = new(OpenIdByAuthCodeRsp)
  342. err = xml.Unmarshal(bs, openIdRsp)
  343. if err != nil {
  344. return nil, err
  345. }
  346. return openIdRsp, nil
  347. }
  348. //用户支付完成后,获取该用户的 UnionId,无需用户授权。
  349. // accessToken:接口调用凭据
  350. // openId:用户的OpenID
  351. // transactionId:微信支付订单号
  352. func GetPaidUnionId(accessToken, openId, transactionId string) (unionId *PaidUnionId, err error) {
  353. unionId = new(PaidUnionId)
  354. url := "https://api.weixin.qq.com/wxa/getpaidunionid?access_token=" + accessToken + "&openid=" + openId + "&transaction_id=" + transactionId
  355. agent := HttpAgent()
  356. _, _, errs := agent.Get(url).EndStruct(unionId)
  357. if len(errs) > 0 {
  358. return nil, errs[0]
  359. } else {
  360. return unionId, nil
  361. }
  362. }
  363. //获取用户基本信息(UnionID机制)
  364. // accessToken:接口调用凭据
  365. // openId:用户的OpenID
  366. // lang:默认为 zh_CN ,可选填 zh_CN 简体,zh_TW 繁体,en 英语
  367. func GetWeChatUserInfo(accessToken, openId string, lang ...string) (userInfo *WeChatUserInfo, err error) {
  368. userInfo = new(WeChatUserInfo)
  369. var url string
  370. if len(lang) > 0 {
  371. url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=" + accessToken + "&openid=" + openId + "&lang=" + lang[0]
  372. } else {
  373. url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=" + accessToken + "&openid=" + openId + "&lang=zh_CN"
  374. }
  375. agent := HttpAgent()
  376. _, _, errs := agent.Get(url).EndStruct(userInfo)
  377. if len(errs) > 0 {
  378. return nil, errs[0]
  379. } else {
  380. return userInfo, nil
  381. }
  382. }