wechat_service_api.go 16 KB

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