wechat_client.go 11 KB

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