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 }