123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- package wx
- import (
- "bytes"
- "crypto/tls"
- "encoding/json"
- "encoding/xml"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "net/http"
- "net/url"
- "time"
- )
- type AccessToken struct {
- TokenStr string `json:"access_token"`
- ExpiresAt int64 `json:"expires_at"`
- }
- func (token *AccessToken) IsExpired() bool {
- return time.Now().Unix()+10 > token.ExpiresAt
- }
- type AccessTokenResponse struct {
- ErrCode int `json:"errcode"`
- ErrMsg string `json:"errmsg"`
- AccessToken string `json:"access_token"`
- ExpiresIn int64 `json:"expires_in"`
- }
- type Message map[string]interface{}
- type Weixin interface {
- GetAccessToken() (*AccessTokenResponse, error)
- PrepareOrder(reqInfo UnionPayRequest) (*UnionPayResponse, error)
- OrderQuery(reqInfo UnionPayRequest) (map[string]string, error)
- Refund(param UnionPayRequest, certFile, keyFile string) (map[string]string, error)
- Send(msg Message, token string) error
- SendTpl(msg string, token, wxType string) error
- SendRedpack(param UnionPayRequest, certFile, keyFile string) (map[string]string, error)
- GetHBInfo(param UnionPayRequest, certFile, keyFile string) (map[string]string, error)
- }
- var appInfo struct {
- appid, secret string
- isInit bool
- }
- func Init(appid, secret string) Weixin {
- appInfo.appid = appid
- appInfo.secret = secret
- appInfo.isInit = true
- return NewWeixin(appid, secret)
- }
- func NewWeixin(appid, secret string) Weixin {
- if appid == "" && appInfo.isInit {
- appid = appInfo.appid
- }
- if secret == "" && appInfo.isInit {
- secret = appInfo.secret
- }
- return &defaultWeixin{
- AppId: appid,
- Secret: secret,
- }
- }
- type defaultWeixin struct {
- AppId string
- Secret string
- }
- func (weixin *defaultWeixin) GetAccessToken() (*AccessTokenResponse, error) {
- var serviceUrl string = "https://api.weixin.qq.com/cgi-bin/token"
- reqParams := url.Values{}
- reqParams.Add("grant_type", "client_credential")
- reqParams.Add("appid", weixin.AppId)
- reqParams.Add("secret", weixin.Secret)
- serviceUrl += "?" + reqParams.Encode()
- res, err := http.Get(serviceUrl)
- if err != nil {
- return nil, err
- }
- defer res.Body.Close()
- resBuf, err := ioutil.ReadAll(res.Body)
- if err != nil {
- return nil, err
- }
- tokenRes := &AccessTokenResponse{}
- err = json.Unmarshal(resBuf, tokenRes)
- if err != nil {
- //return nil, err
- return nil, fmt.Errorf("%s", string(resBuf))
- }
- if tokenRes.ErrCode != 0 {
- return nil, fmt.Errorf("%d::%s", tokenRes.ErrCode, tokenRes.ErrMsg)
- }
- return tokenRes, nil
- }
- func (weixin *defaultWeixin) PrepareOrder(reqInfo UnionPayRequest) (*UnionPayResponse, error) {
- var serviceUrl string = "https://api.mch.weixin.qq.com/pay/unifiedorder"
- reqBytes, err := xml.Marshal(reqInfo)
- if err != nil {
- return nil, err
- }
- reader := bytes.NewReader(reqBytes)
- resp, err := http.Post(serviceUrl, "application/xml", reader)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- resBuf, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- return nil, err
- }
- payRes := &UnionPayResponse{}
- err = xml.Unmarshal(resBuf, payRes)
- return payRes, err
- }
- func (weixin *defaultWeixin) OrderQuery(reqInfo UnionPayRequest) (map[string]string, error) {
- var serviceUrl string = "https://api.mch.weixin.qq.com/pay/orderquery"
- reqBytes, err := xml.Marshal(reqInfo)
- if err != nil {
- return nil, err
- }
- reader := bytes.NewReader(reqBytes)
- resp, err := http.Post(serviceUrl, "application/xml", reader)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- /*resBuf, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- return nil, err
- }
- //qRes := &UnionPayResult{}
- //err = xml.Unmarshal(resBuf, qRes)
- fmt.Println(string(resBuf))
- */
- qRes := xmlToMap(resp.Body)
- return qRes, nil
- }
- func (weixin *defaultWeixin) Refund(params UnionPayRequest, certFile, keyFile string) (map[string]string, error) {
- var serviceUrl string = "https://api.mch.weixin.qq.com/secapi/pay/refund"
- return secureRequest(serviceUrl, params, certFile, keyFile)
- }
- func (weixin *defaultWeixin) SendRedpack(params UnionPayRequest, certFile, keyFile string) (map[string]string, error) {
- var serviceUrl string = "https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack"
- //var serviceUrl string = "https://api.mch.weixin.qq.com/sandboxnew/mmpaymkttransfers/sendredpack"
- return secureRequest(serviceUrl, params, certFile, keyFile)
- }
- func (weixin *defaultWeixin) GetHBInfo(params UnionPayRequest, certFile, keyFile string) (map[string]string, error) {
- var serviceUrl string = "https://api.mch.weixin.qq.com/mmpaymkttransfers/gethbinfo"
- return secureRequest(serviceUrl, params, certFile, keyFile)
- }
- func secureRequest(serviceUrl string, params UnionPayRequest, certFile, keyFile string) (map[string]string, error) {
- reqBytes, err := xml.Marshal(params)
- if err != nil {
- return nil, err
- }
- reader := bytes.NewReader(reqBytes)
- client, err := getSecureClient(certFile, keyFile)
- if err != nil {
- return nil, err
- }
- resp, err := client.Post(serviceUrl, "application/xml", reader)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- ret := xmlToMap(resp.Body)
- return ret, nil
- }
- func (weixin *defaultWeixin) Send(msg Message, token string) error {
- var serviceUrl string = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=" + token
- var buffer bytes.Buffer
- enc := json.NewEncoder(&buffer)
- enc.SetEscapeHTML(false)
- err := enc.Encode(msg)
- if err != nil {
- return err
- }
- reader := bytes.NewReader(buffer.Bytes())
- resp, err := http.Post(serviceUrl, "application/json", reader)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
- buf, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- return err
- }
- var httpRes = struct {
- ErrCode uint16 `json:"errcode"`
- ErrMsg string `json:"errmsg"`
- }{}
- err = json.Unmarshal(buf, &httpRes)
- if err != nil {
- return err
- }
- if httpRes.ErrCode == 0 {
- return nil
- } else {
- return errors.New(httpRes.ErrMsg)
- }
- }
- func (weixin *defaultWeixin) SendTpl(msg string, token, wxType string) error {
- var serviceUrl string = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token="
- if wxType == "mp" {
- serviceUrl = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token="
- }
- serviceUrl += token
- reader := bytes.NewReader([]byte(msg))
- resp, err := http.Post(serviceUrl, "application/json", reader)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
- buf, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- return err
- }
- var httpRes = struct {
- ErrCode uint16 `json:"errcode"`
- ErrMsg string `json:"errmsg"`
- MsgId uint64 `json:"msgid"`
- }{}
- err = json.Unmarshal(buf, &httpRes)
- if err != nil {
- //return err
- return fmt.Errorf("%s", string(buf))
- }
- if httpRes.ErrCode == 0 {
- return nil
- } else {
- //return errors.New(httpRes.ErrMsg)
- return fmt.Errorf("%d::%s", httpRes.ErrCode, httpRes.ErrMsg)
- }
- }
- func getSecureClient(certFile, keyFile string) (*http.Client, error) {
- cert, err := tls.LoadX509KeyPair(certFile, keyFile)
- if err != nil {
- return nil, err
- }
- ssl := &tls.Config{
- Certificates: []tls.Certificate{cert},
- }
- return &http.Client{
- Transport: &http.Transport{
- TLSClientConfig: ssl,
- },
- }, nil
- }
- func xmlToMap(r io.Reader) map[string]string {
- m := make(map[string]string)
- // the current value stack
- values := make([]string, 0)
- p := xml.NewDecoder(r)
- for token, err := p.Token(); err == nil; token, err = p.Token() {
- switch t := token.(type) {
- case xml.CharData:
- // push
- values = append(values, string([]byte(t)))
- case xml.EndElement:
- if t.Name.Local == "xml" {
- continue
- }
- m[t.Name.Local] = values[len(values)-1]
- // pop
- values = values[:len(values)]
- }
- }
- return m
- }
|