weixin.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. package wx
  2. import (
  3. "bytes"
  4. "crypto/tls"
  5. "encoding/json"
  6. "encoding/xml"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "io/ioutil"
  11. "net/http"
  12. "net/url"
  13. "time"
  14. )
  15. type AccessToken struct {
  16. TokenStr string `json:"access_token"`
  17. ExpiresAt int64 `json:"expires_at"`
  18. }
  19. func (token *AccessToken) IsExpired() bool {
  20. return time.Now().Unix()+10 > token.ExpiresAt
  21. }
  22. type AccessTokenResponse struct {
  23. ErrCode int `json:"errcode"`
  24. ErrMsg string `json:"errmsg"`
  25. AccessToken string `json:"access_token"`
  26. ExpiresIn int64 `json:"expires_in"`
  27. }
  28. type Message map[string]interface{}
  29. type Weixin interface {
  30. GetAccessToken() (*AccessTokenResponse, error)
  31. PrepareOrder(reqInfo UnionPayRequest) (*UnionPayResponse, error)
  32. OrderQuery(reqInfo UnionPayRequest) (map[string]string, error)
  33. Refund(param UnionPayRequest, certFile, keyFile string) (map[string]string, error)
  34. Send(msg Message, token string) error
  35. SendTpl(msg string, token, wxType string) error
  36. SendRedpack(param UnionPayRequest, certFile, keyFile string) (map[string]string, error)
  37. GetHBInfo(param UnionPayRequest, certFile, keyFile string) (map[string]string, error)
  38. }
  39. var appInfo struct {
  40. appid, secret string
  41. isInit bool
  42. }
  43. func Init(appid, secret string) Weixin {
  44. appInfo.appid = appid
  45. appInfo.secret = secret
  46. appInfo.isInit = true
  47. return NewWeixin(appid, secret)
  48. }
  49. func NewWeixin(appid, secret string) Weixin {
  50. if appid == "" && appInfo.isInit {
  51. appid = appInfo.appid
  52. }
  53. if secret == "" && appInfo.isInit {
  54. secret = appInfo.secret
  55. }
  56. return &defaultWeixin{
  57. AppId: appid,
  58. Secret: secret,
  59. }
  60. }
  61. type defaultWeixin struct {
  62. AppId string
  63. Secret string
  64. }
  65. func (weixin *defaultWeixin) GetAccessToken() (*AccessTokenResponse, error) {
  66. var serviceUrl string = "https://api.weixin.qq.com/cgi-bin/token"
  67. reqParams := url.Values{}
  68. reqParams.Add("grant_type", "client_credential")
  69. reqParams.Add("appid", weixin.AppId)
  70. reqParams.Add("secret", weixin.Secret)
  71. serviceUrl += "?" + reqParams.Encode()
  72. res, err := http.Get(serviceUrl)
  73. if err != nil {
  74. return nil, err
  75. }
  76. defer res.Body.Close()
  77. resBuf, err := ioutil.ReadAll(res.Body)
  78. if err != nil {
  79. return nil, err
  80. }
  81. tokenRes := &AccessTokenResponse{}
  82. err = json.Unmarshal(resBuf, tokenRes)
  83. if err != nil {
  84. //return nil, err
  85. return nil, fmt.Errorf("%s", string(resBuf))
  86. }
  87. if tokenRes.ErrCode != 0 {
  88. return nil, fmt.Errorf("%d::%s", tokenRes.ErrCode, tokenRes.ErrMsg)
  89. }
  90. return tokenRes, nil
  91. }
  92. func (weixin *defaultWeixin) PrepareOrder(reqInfo UnionPayRequest) (*UnionPayResponse, error) {
  93. var serviceUrl string = "https://api.mch.weixin.qq.com/pay/unifiedorder"
  94. reqBytes, err := xml.Marshal(reqInfo)
  95. if err != nil {
  96. return nil, err
  97. }
  98. reader := bytes.NewReader(reqBytes)
  99. resp, err := http.Post(serviceUrl, "application/xml", reader)
  100. if err != nil {
  101. return nil, err
  102. }
  103. defer resp.Body.Close()
  104. resBuf, err := ioutil.ReadAll(resp.Body)
  105. if err != nil {
  106. return nil, err
  107. }
  108. payRes := &UnionPayResponse{}
  109. err = xml.Unmarshal(resBuf, payRes)
  110. return payRes, err
  111. }
  112. func (weixin *defaultWeixin) OrderQuery(reqInfo UnionPayRequest) (map[string]string, error) {
  113. var serviceUrl string = "https://api.mch.weixin.qq.com/pay/orderquery"
  114. reqBytes, err := xml.Marshal(reqInfo)
  115. if err != nil {
  116. return nil, err
  117. }
  118. reader := bytes.NewReader(reqBytes)
  119. resp, err := http.Post(serviceUrl, "application/xml", reader)
  120. if err != nil {
  121. return nil, err
  122. }
  123. defer resp.Body.Close()
  124. /*resBuf, err := ioutil.ReadAll(resp.Body)
  125. if err != nil {
  126. return nil, err
  127. }
  128. //qRes := &UnionPayResult{}
  129. //err = xml.Unmarshal(resBuf, qRes)
  130. fmt.Println(string(resBuf))
  131. */
  132. qRes := xmlToMap(resp.Body)
  133. return qRes, nil
  134. }
  135. func (weixin *defaultWeixin) Refund(params UnionPayRequest, certFile, keyFile string) (map[string]string, error) {
  136. var serviceUrl string = "https://api.mch.weixin.qq.com/secapi/pay/refund"
  137. return secureRequest(serviceUrl, params, certFile, keyFile)
  138. }
  139. func (weixin *defaultWeixin) SendRedpack(params UnionPayRequest, certFile, keyFile string) (map[string]string, error) {
  140. var serviceUrl string = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack"
  141. //var serviceUrl string = "https://api.mch.weixin.qq.com/sandboxnew/mmpaymkttransfers/sendredpack"
  142. return secureRequest(serviceUrl, params, certFile, keyFile)
  143. }
  144. func (weixin *defaultWeixin) GetHBInfo(params UnionPayRequest, certFile, keyFile string) (map[string]string, error) {
  145. var serviceUrl string = "https://api.mch.weixin.qq.com/mmpaymkttransfers/gethbinfo"
  146. return secureRequest(serviceUrl, params, certFile, keyFile)
  147. }
  148. func secureRequest(serviceUrl string, params UnionPayRequest, certFile, keyFile string) (map[string]string, error) {
  149. reqBytes, err := xml.Marshal(params)
  150. if err != nil {
  151. return nil, err
  152. }
  153. reader := bytes.NewReader(reqBytes)
  154. client, err := getSecureClient(certFile, keyFile)
  155. if err != nil {
  156. return nil, err
  157. }
  158. resp, err := client.Post(serviceUrl, "application/xml", reader)
  159. if err != nil {
  160. return nil, err
  161. }
  162. defer resp.Body.Close()
  163. ret := xmlToMap(resp.Body)
  164. return ret, nil
  165. }
  166. func (weixin *defaultWeixin) Send(msg Message, token string) error {
  167. var serviceUrl string = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=" + token
  168. var buffer bytes.Buffer
  169. enc := json.NewEncoder(&buffer)
  170. enc.SetEscapeHTML(false)
  171. err := enc.Encode(msg)
  172. if err != nil {
  173. return err
  174. }
  175. reader := bytes.NewReader(buffer.Bytes())
  176. resp, err := http.Post(serviceUrl, "application/json", reader)
  177. if err != nil {
  178. return err
  179. }
  180. defer resp.Body.Close()
  181. buf, err := ioutil.ReadAll(resp.Body)
  182. if err != nil {
  183. return err
  184. }
  185. var httpRes = struct {
  186. ErrCode uint16 `json:"errcode"`
  187. ErrMsg string `json:"errmsg"`
  188. }{}
  189. err = json.Unmarshal(buf, &httpRes)
  190. if err != nil {
  191. return err
  192. }
  193. if httpRes.ErrCode == 0 {
  194. return nil
  195. } else {
  196. return errors.New(httpRes.ErrMsg)
  197. }
  198. }
  199. func (weixin *defaultWeixin) SendTpl(msg string, token, wxType string) error {
  200. var serviceUrl string = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token="
  201. if wxType == "mp" {
  202. serviceUrl = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token="
  203. }
  204. serviceUrl += token
  205. reader := bytes.NewReader([]byte(msg))
  206. resp, err := http.Post(serviceUrl, "application/json", reader)
  207. if err != nil {
  208. return err
  209. }
  210. defer resp.Body.Close()
  211. buf, err := ioutil.ReadAll(resp.Body)
  212. if err != nil {
  213. return err
  214. }
  215. var httpRes = struct {
  216. ErrCode uint16 `json:"errcode"`
  217. ErrMsg string `json:"errmsg"`
  218. MsgId uint64 `json:"msgid"`
  219. }{}
  220. err = json.Unmarshal(buf, &httpRes)
  221. if err != nil {
  222. //return err
  223. return fmt.Errorf("%s", string(buf))
  224. }
  225. if httpRes.ErrCode == 0 {
  226. return nil
  227. } else {
  228. //return errors.New(httpRes.ErrMsg)
  229. return fmt.Errorf("%d::%s", httpRes.ErrCode, httpRes.ErrMsg)
  230. }
  231. }
  232. func getSecureClient(certFile, keyFile string) (*http.Client, error) {
  233. cert, err := tls.LoadX509KeyPair(certFile, keyFile)
  234. if err != nil {
  235. return nil, err
  236. }
  237. ssl := &tls.Config{
  238. Certificates: []tls.Certificate{cert},
  239. }
  240. return &http.Client{
  241. Transport: &http.Transport{
  242. TLSClientConfig: ssl,
  243. },
  244. }, nil
  245. }
  246. func xmlToMap(r io.Reader) map[string]string {
  247. m := make(map[string]string)
  248. // the current value stack
  249. values := make([]string, 0)
  250. p := xml.NewDecoder(r)
  251. for token, err := p.Token(); err == nil; token, err = p.Token() {
  252. switch t := token.(type) {
  253. case xml.CharData:
  254. // push
  255. values = append(values, string([]byte(t)))
  256. case xml.EndElement:
  257. if t.Name.Local == "xml" {
  258. continue
  259. }
  260. m[t.Name.Local] = values[len(values)-1]
  261. // pop
  262. values = values[:len(values)]
  263. }
  264. }
  265. return m
  266. }