wechat_client.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. package gopay
  2. import (
  3. "crypto/tls"
  4. "crypto/x509"
  5. "encoding/xml"
  6. "errors"
  7. "fmt"
  8. "io/ioutil"
  9. "strings"
  10. )
  11. type weChatClient struct {
  12. AppId string
  13. MchId string
  14. apiKey string
  15. baseURL string
  16. isProd bool
  17. }
  18. //初始化微信客户端 ok
  19. // appId:应用ID
  20. // mchId:商户ID
  21. // apiKey:API秘钥值
  22. // isProd:是否是正式环境
  23. func NewWeChatClient(appId, mchId, apiKey string, isProd bool) (client *weChatClient) {
  24. client = new(weChatClient)
  25. client.AppId = appId
  26. client.MchId = mchId
  27. client.apiKey = apiKey
  28. client.isProd = isProd
  29. return client
  30. }
  31. //提交付款码支付 ok
  32. // 文档地址:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10&index=1
  33. func (this *weChatClient) Micropay(body BodyMap) (wxRsp *WeChatMicropayResponse, err error) {
  34. var bytes []byte
  35. if this.isProd {
  36. //正式环境
  37. bytes, err = this.doWeChat(body, wx_Micropay)
  38. if err != nil {
  39. return nil, err
  40. }
  41. } else {
  42. bytes, err = this.doWeChat(body, wx_SanBox_Micropay)
  43. if err != nil {
  44. return nil, err
  45. }
  46. }
  47. wxRsp = new(WeChatMicropayResponse)
  48. err = xml.Unmarshal(bytes, wxRsp)
  49. if err != nil {
  50. return nil, fmt.Errorf("xml.Unmarshal:%v", err.Error())
  51. }
  52. return wxRsp, nil
  53. }
  54. //统一下单 ok
  55. // 文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1
  56. func (this *weChatClient) UnifiedOrder(body BodyMap) (wxRsp *WeChatUnifiedOrderResponse, err error) {
  57. var bytes []byte
  58. if this.isProd {
  59. //正式环境
  60. bytes, err = this.doWeChat(body, wx_UnifiedOrder)
  61. if err != nil {
  62. return nil, err
  63. }
  64. } else {
  65. body.Set("total_fee", 101)
  66. bytes, err = this.doWeChat(body, wx_SanBox_UnifiedOrder)
  67. if err != nil {
  68. return nil, err
  69. }
  70. }
  71. wxRsp = new(WeChatUnifiedOrderResponse)
  72. //fmt.Println("bytes:", string(bytes))
  73. err = xml.Unmarshal(bytes, wxRsp)
  74. if err != nil {
  75. //fmt.Println("xml.Unmarshal.Err:", err)
  76. return nil, fmt.Errorf("xml.Unmarshal:%v", err.Error())
  77. }
  78. return wxRsp, nil
  79. }
  80. //查询订单 ok
  81. // 文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2
  82. func (this *weChatClient) QueryOrder(body BodyMap) (wxRsp *WeChatQueryOrderResponse, err error) {
  83. var bytes []byte
  84. if this.isProd {
  85. //正式环境
  86. bytes, err = this.doWeChat(body, wx_OrderQuery)
  87. if err != nil {
  88. return nil, err
  89. }
  90. } else {
  91. bytes, err = this.doWeChat(body, wx_SanBox_OrderQuery)
  92. if err != nil {
  93. return nil, err
  94. }
  95. }
  96. wxRsp = new(WeChatQueryOrderResponse)
  97. err = xml.Unmarshal(bytes, wxRsp)
  98. if err != nil {
  99. return nil, fmt.Errorf("xml.Unmarshal:%v", err.Error())
  100. }
  101. return wxRsp, nil
  102. }
  103. //关闭订单 ok
  104. // 文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_3
  105. func (this *weChatClient) CloseOrder(body BodyMap) (wxRsp *WeChatCloseOrderResponse, err error) {
  106. var bytes []byte
  107. if this.isProd {
  108. //正式环境
  109. bytes, err = this.doWeChat(body, wx_CloseOrder)
  110. if err != nil {
  111. return nil, err
  112. }
  113. } else {
  114. bytes, err = this.doWeChat(body, wx_SanBox_CloseOrder)
  115. if err != nil {
  116. return nil, err
  117. }
  118. }
  119. wxRsp = new(WeChatCloseOrderResponse)
  120. err = xml.Unmarshal(bytes, wxRsp)
  121. if err != nil {
  122. return nil, fmt.Errorf("xml.Unmarshal:%v", err.Error())
  123. }
  124. return wxRsp, nil
  125. }
  126. //撤销订单 ok
  127. // 文档地址:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_11&index=3
  128. func (this *weChatClient) Reverse(body BodyMap, certFilePath, keyFilePath, pkcs12FilePath string) (wxRsp *WeChatReverseResponse, err error) {
  129. var bytes []byte
  130. if this.isProd {
  131. //正式环境
  132. pkcsPool := x509.NewCertPool()
  133. pkcs, err := ioutil.ReadFile(pkcs12FilePath)
  134. if err != nil {
  135. return nil, fmt.Errorf("ioutil.ReadFile:%v", err.Error())
  136. }
  137. pkcsPool.AppendCertsFromPEM(pkcs)
  138. certificate, err := tls.LoadX509KeyPair(certFilePath, keyFilePath)
  139. if err != nil {
  140. return nil, fmt.Errorf("tls.LoadX509KeyPair:%v", err.Error())
  141. }
  142. tlsConfig := new(tls.Config)
  143. tlsConfig.Certificates = []tls.Certificate{certificate}
  144. tlsConfig.RootCAs = pkcsPool
  145. tlsConfig.InsecureSkipVerify = true
  146. bytes, err = this.doWeChat(body, wx_Reverse, tlsConfig)
  147. if err != nil {
  148. return nil, err
  149. }
  150. } else {
  151. bytes, err = this.doWeChat(body, wx_SanBox_Reverse)
  152. if err != nil {
  153. return nil, err
  154. }
  155. }
  156. wxRsp = new(WeChatReverseResponse)
  157. err = xml.Unmarshal(bytes, wxRsp)
  158. if err != nil {
  159. return nil, fmt.Errorf("xml.Unmarshal:%v", err.Error())
  160. }
  161. return wxRsp, nil
  162. }
  163. //申请退款 ok
  164. // 文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4
  165. func (this *weChatClient) Refund(body BodyMap, certFilePath, keyFilePath, pkcs12FilePath string) (wxRsp *WeChatRefundResponse, err error) {
  166. var bytes []byte
  167. if this.isProd {
  168. //正式环境
  169. pkcsPool := x509.NewCertPool()
  170. pkcs, err := ioutil.ReadFile(pkcs12FilePath)
  171. if err != nil {
  172. return nil, fmt.Errorf("ioutil.ReadFile:%v", err.Error())
  173. }
  174. pkcsPool.AppendCertsFromPEM(pkcs)
  175. certificate, err := tls.LoadX509KeyPair(certFilePath, keyFilePath)
  176. if err != nil {
  177. return nil, fmt.Errorf("tls.LoadX509KeyPair:%v", err.Error())
  178. }
  179. tlsConfig := new(tls.Config)
  180. tlsConfig.Certificates = []tls.Certificate{certificate}
  181. tlsConfig.RootCAs = pkcsPool
  182. tlsConfig.InsecureSkipVerify = true
  183. bytes, err = this.doWeChat(body, wx_Refund, tlsConfig)
  184. if err != nil {
  185. return nil, err
  186. }
  187. } else {
  188. bytes, err = this.doWeChat(body, wx_SanBox_Refund)
  189. if err != nil {
  190. return nil, err
  191. }
  192. }
  193. wxRsp = new(WeChatRefundResponse)
  194. err = xml.Unmarshal(bytes, wxRsp)
  195. if err != nil {
  196. return nil, fmt.Errorf("xml.Unmarshal:%v", err.Error())
  197. }
  198. return wxRsp, nil
  199. }
  200. //查询退款 ok
  201. // 文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_5
  202. func (this *weChatClient) QueryRefund(body BodyMap) (wxRsp *WeChatQueryRefundResponse, err error) {
  203. var bytes []byte
  204. if this.isProd {
  205. //正式环境
  206. bytes, err = this.doWeChat(body, wx_RefundQuery)
  207. if err != nil {
  208. return nil, err
  209. }
  210. } else {
  211. bytes, err = this.doWeChat(body, wx_SanBox_RefundQuery)
  212. if err != nil {
  213. return nil, err
  214. }
  215. }
  216. wxRsp = new(WeChatQueryRefundResponse)
  217. err = xml.Unmarshal(bytes, wxRsp)
  218. if err != nil {
  219. return nil, fmt.Errorf("xml.Unmarshal:%v", err.Error())
  220. }
  221. return wxRsp, nil
  222. }
  223. //下载对账单 ok
  224. // 文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_6
  225. func (this *weChatClient) DownloadBill(body BodyMap) (wxRsp string, err error) {
  226. var bytes []byte
  227. if this.isProd {
  228. //正式环境
  229. bytes, err = this.doWeChat(body, wx_DownloadBill)
  230. } else {
  231. bytes, err = this.doWeChat(body, wx_SanBox_DownloadBill)
  232. }
  233. wxRsp = string(bytes)
  234. if err != nil {
  235. return wxRsp, err
  236. }
  237. return wxRsp, nil
  238. }
  239. //下载资金账单 ok
  240. // 文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_18&index=7
  241. // 好像不支持沙箱环境,因为沙箱环境默认需要用MD5签名,但是此接口仅支持HMAC-SHA256签名
  242. func (this *weChatClient) DownloadFundFlow(body BodyMap, certFilePath, keyFilePath, pkcs12FilePath string) (wxRsp string, err error) {
  243. var bytes []byte
  244. if this.isProd {
  245. //正式环境
  246. pkcsPool := x509.NewCertPool()
  247. pkcs, err := ioutil.ReadFile(pkcs12FilePath)
  248. if err != nil {
  249. return null, fmt.Errorf("ioutil.ReadFile:%v", err.Error())
  250. }
  251. pkcsPool.AppendCertsFromPEM(pkcs)
  252. certificate, err := tls.LoadX509KeyPair(certFilePath, keyFilePath)
  253. if err != nil {
  254. return null, fmt.Errorf("tls.LoadX509KeyPair:%v", err.Error())
  255. }
  256. tlsConfig := new(tls.Config)
  257. tlsConfig.Certificates = []tls.Certificate{certificate}
  258. tlsConfig.RootCAs = pkcsPool
  259. tlsConfig.InsecureSkipVerify = true
  260. bytes, err = this.doWeChat(body, wx_DownloadFundFlow, tlsConfig)
  261. } else {
  262. bytes, err = this.doWeChat(body, wx_SanBox_DownloadFundFlow)
  263. }
  264. if err != nil {
  265. return null, err
  266. }
  267. wxRsp = string(bytes)
  268. return wxRsp, nil
  269. }
  270. //拉取订单评价数据
  271. // 文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_17&index=11
  272. // 好像不支持沙箱环境,因为沙箱环境默认需要用MD5签名,但是此接口仅支持HMAC-SHA256签名
  273. func (this *weChatClient) BatchQueryComment(body BodyMap, certFilePath, keyFilePath, pkcs12FilePath string) (wxRsp string, err error) {
  274. var bytes []byte
  275. if this.isProd {
  276. //正式环境
  277. body.Set("sign_type", SignType_HMAC_SHA256)
  278. pkcsPool := x509.NewCertPool()
  279. pkcs, err := ioutil.ReadFile(pkcs12FilePath)
  280. if err != nil {
  281. return null, fmt.Errorf("ioutil.ReadFile:%v", err.Error())
  282. }
  283. pkcsPool.AppendCertsFromPEM(pkcs)
  284. certificate, err := tls.LoadX509KeyPair(certFilePath, keyFilePath)
  285. if err != nil {
  286. return null, fmt.Errorf("tls.LoadX509KeyPair:%v", err.Error())
  287. }
  288. tlsConfig := new(tls.Config)
  289. tlsConfig.Certificates = []tls.Certificate{certificate}
  290. tlsConfig.RootCAs = pkcsPool
  291. tlsConfig.InsecureSkipVerify = true
  292. bytes, err = this.doWeChat(body, wx_BatchQueryComment, tlsConfig)
  293. } else {
  294. bytes, err = this.doWeChat(body, wx_SanBox_BatchQueryComment)
  295. }
  296. if err != nil {
  297. return null, err
  298. }
  299. wxRsp = string(bytes)
  300. return wxRsp, nil
  301. }
  302. //企业向微信用户个人付款
  303. // 文档地址:https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_1
  304. // 注意:此方法未支持沙箱环境,默认正式环境,转账请慎重
  305. func (this *weChatClient) Transfer(body BodyMap, certFilePath, keyFilePath, pkcs12FilePath string) (wxRsp *WeChatTransfersResponse, err error) {
  306. var bytes []byte
  307. var sign string
  308. body.Set("mch_appid", this.AppId)
  309. body.Set("mchid", this.MchId)
  310. agent := HttpAgent()
  311. //正式环境
  312. pkcsPool := x509.NewCertPool()
  313. pkcs, err := ioutil.ReadFile(pkcs12FilePath)
  314. if err != nil {
  315. return nil, fmt.Errorf("ioutil.ReadFile:%v", err.Error())
  316. }
  317. pkcsPool.AppendCertsFromPEM(pkcs)
  318. certificate, err := tls.LoadX509KeyPair(certFilePath, keyFilePath)
  319. if err != nil {
  320. return nil, fmt.Errorf("tls.LoadX509KeyPair:%v", err.Error())
  321. }
  322. tlsConfig := new(tls.Config)
  323. tlsConfig.Certificates = []tls.Certificate{certificate}
  324. tlsConfig.RootCAs = pkcsPool
  325. tlsConfig.InsecureSkipVerify = true
  326. agent.TLSClientConfig(tlsConfig)
  327. //本地计算Sign
  328. sign = getWeChatReleaseSign(this.apiKey, SignType_MD5, body)
  329. body.Set("sign", sign)
  330. reqXML := generateXml(body)
  331. if this.baseURL != null {
  332. agent.Post(this.baseURL + wx_Transfers)
  333. } else {
  334. agent.Post(wx_base_url_ch + wx_Transfers)
  335. }
  336. agent.Type("xml")
  337. agent.SendString(reqXML)
  338. _, bytes, errs := agent.EndBytes()
  339. if len(errs) > 0 {
  340. return nil, errs[0]
  341. }
  342. wxRsp = new(WeChatTransfersResponse)
  343. err = xml.Unmarshal(bytes, wxRsp)
  344. if err != nil {
  345. return nil, fmt.Errorf("xml.Unmarshal:%v", err.Error())
  346. }
  347. return wxRsp, nil
  348. }
  349. //向微信发送请求 ok
  350. func (this *weChatClient) doWeChat(body BodyMap, path string, tlsConfig ...*tls.Config) (bytes []byte, err error) {
  351. var sign string
  352. body.Set("appid", this.AppId)
  353. body.Set("mch_id", this.MchId)
  354. //===============生成参数===================
  355. if body.Get("sign") != null {
  356. goto GoRequest
  357. }
  358. //计算Sign值
  359. if !this.isProd {
  360. //沙箱环境
  361. body.Set("sign_type", SignType_MD5)
  362. //沙箱环境Sign值
  363. sign, err = getWeChatSignBoxSign(this.MchId, this.apiKey, body)
  364. if err != nil {
  365. //fmt.Println("getWeChatSignBoxSign:", err)
  366. return nil, err
  367. }
  368. } else {
  369. //正式环境
  370. sign = getWeChatReleaseSign(this.apiKey, body.Get("sign_type"), body)
  371. }
  372. body.Set("sign", sign)
  373. GoRequest:
  374. reqXML := generateXml(body)
  375. //fmt.Println("reqXML:",reqXML)
  376. //===============发起请求===================
  377. agent := HttpAgent()
  378. if this.isProd && tlsConfig != nil {
  379. agent.TLSClientConfig(tlsConfig[0])
  380. }
  381. if this.baseURL != null {
  382. agent.Post(this.baseURL + path)
  383. } else {
  384. agent.Post(wx_base_url_ch + path)
  385. }
  386. agent.Type("xml")
  387. agent.SendString(reqXML)
  388. res, bytes, errs := agent.EndBytes()
  389. if len(errs) > 0 {
  390. //fmt.Println("errs[0]:", errs[0])
  391. return nil, errs[0]
  392. }
  393. //fmt.Println("res:", res.StatusCode)
  394. if res.StatusCode != 200 {
  395. return nil, fmt.Errorf("HTTP Request Error, StatusCode = %v", res.StatusCode)
  396. }
  397. if strings.Contains(string(bytes), "HTML") {
  398. return nil, errors.New(string(bytes))
  399. }
  400. //fmt.Println("bytes:", string(bytes))
  401. return bytes, nil
  402. }