wechat_service_api.go 14 KB

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