diff --git a/weixin/utils.go b/weixin/utils.go new file mode 100644 index 0000000..3563f71 --- /dev/null +++ b/weixin/utils.go @@ -0,0 +1,17 @@ +package weixin + +import ( + "math/rand" + "time" +) + +func randomStr(length int) string { + str := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + bytes := []byte(str) + result := []byte{} + r := rand.New(rand.NewSource(time.Now().UnixNano())) + for i := 0; i < length; i++ { + result = append(result, bytes[r.Intn(len(bytes))]) + } + return string(result) +} diff --git a/weixin/wxpay_params.go b/weixin/wxpay_params.go new file mode 100644 index 0000000..4e8bce2 --- /dev/null +++ b/weixin/wxpay_params.go @@ -0,0 +1,142 @@ +package weixin + +import ( + "bytes" + "crypto/hmac" + "crypto/md5" + "crypto/sha256" + "encoding/hex" + "encoding/xml" + "fmt" + "io" + "sort" + "strconv" + "strings" +) + +type Params map[string]string + +func (p Params) Set(k string, v interface{}) { + p[k] = fmt.Sprintf("%v", v) +} + +func (p Params) GetString(k string) string { + s, _ := p[k] + return s +} + +func (p Params) GetUint64(k string) uint64 { + s, _ := strconv.ParseUint(p[k], 10, 64) + return s +} + +func (p Params) GetInt64(k string) int64 { + i, _ := strconv.ParseInt(p.GetString(k), 10, 64) + return i +} + +func (p Params) GetInt(k string) int64 { + i, _ := strconv.ParseInt(p.GetString(k), 10, 64) + return i +} + +func (p Params) GetFloat64(k string) float64 { + f, _ := strconv.ParseFloat(p.GetString(k), 64) + return f +} +func (p Params) GetBool(k string) bool { + b, _ := strconv.ParseBool(p.GetString(k)) + return b +} + +// XML解码 +func (p Params) Decode(body []byte) (err error) { + defer func() { + if e := recover(); e != nil { + err = fmt.Errorf("%v", e) + } + }() + + var ( + d *xml.Decoder + start *xml.StartElement + ) + buf := bytes.NewBuffer(body) + d = xml.NewDecoder(buf) + for { + tok, err := d.Token() + if err != nil { + break + } + switch t := tok.(type) { + case xml.StartElement: + start = &t + case xml.CharData: + if t = bytes.TrimSpace(t); len(t) > 0 { + p.Set(start.Name.Local, string(t)) + } + } + } + return nil +} + +// XML编码 +func (p Params) Encode() []byte { + var buf bytes.Buffer + buf.WriteString(``) + for k, v := range p { + buf.WriteString(`<`) + buf.WriteString(k) + buf.WriteString(`>`) + } + buf.WriteString(``) + return buf.Bytes() +} + +// 验证签名 +func (p Params) CheckSign(key string) bool { + return p.GetString("sign") == p.SignMd5(key) +} + +// 生成签名 +func (p Params) getSignStr(key string) string { + var keys = make([]string, 0, len(p)) + for k, _ := range p { + if k != "sign" { + keys = append(keys, k) + } + } + sort.Strings(keys) + + var buf bytes.Buffer + for _, k := range keys { + if len(p.GetString(k)) > 0 { + buf.WriteString(k) + buf.WriteString(`=`) + buf.WriteString(p.GetString(k)) + buf.WriteString(`&`) + } + } + buf.WriteString(`key=`) + buf.WriteString(key) + return buf.String() +} + +// 生成签名 +func (p Params) SignMd5(key string) string { + sum := md5.Sum([]byte(p.getSignStr(key))) + str := hex.EncodeToString(sum[:]) + return strings.ToUpper(str) +} + +// 生成签名 +func (p Params) SignHmacSha256(key string) string { + h := hmac.New(sha256.New, []byte(key)) + io.WriteString(h, p.getSignStr(key)) + str := hex.EncodeToString(h.Sum(nil)) + return strings.ToUpper(str) +} diff --git a/weixin/wxpay_partner.go b/weixin/wxpay_partner.go new file mode 100644 index 0000000..0bc50f1 --- /dev/null +++ b/weixin/wxpay_partner.go @@ -0,0 +1,108 @@ +package weixin + +import ( + "bytes" + "errors" + "fmt" + "io" + "net/http" +) + +var ( + ErrUserPaying = errors.New("USERPAYING") +) + +type WxpayPartnerConfig struct { + MchId string + AppId string + ApiKeyV2 string +} + +type WxpayPartner struct { + config *WxpayPartnerConfig + stdClient *http.Client +} + +type MicroPayRequest struct { + SubMchId string + SubAppId string + GoodsName string + OutTradeNo string + TotalFee int64 + AuthCode string + ServerIp string + Attach string +} + +func NewWxpayPartner(cfg *WxpayPartnerConfig) *WxpayPartner { + return &WxpayPartner{ + config: cfg, + stdClient: &http.Client{}, + } +} + +func (p *WxpayPartner) MicroPay(req *MicroPayRequest) error { + params := make(Params) + params.Set("appid", p.config.AppId) + params.Set("mch_id", p.config.MchId) + params.Set("sub_mch_id", req.SubMchId) + params.Set("nonce_str", randomStr(32)) + params.Set("body", req.GoodsName) + params.Set("out_trade_no", req.OutTradeNo) + params.Set("total_fee", req.TotalFee) + params.Set("auth_code", req.AuthCode) + + if req.ServerIp == "" { + params.Set("spbill_create_ip", "113.248.223.215") + } else { + params.Set("spbill_create_ip", req.ServerIp) + } + + if req.Attach != "" { + params.Set("attach", req.Attach) + } + if req.SubAppId != "" { + params.Set("sub_appid", req.SubAppId) + } + + params.Set("sign", params.SignMd5(p.config.ApiKeyV2)) + reqUrl := "https://api.mch.weixin.qq.com/pay/micropay" + body, err := p.post(reqUrl, params) + if err != nil { + return err + } + result := make(Params) + result.Decode(body) + + if result.GetString("return_code") != "SUCCESS" { + return errors.New(string(body)) + } + + if result.GetString("result_code") != "SUCCESS" { + if result.GetString("err_code") == "USERPAYING" { + return ErrUserPaying + } + return fmt.Errorf("%s:%s", + result.GetString("err_code"), result.GetString("err_code_des")) + } + return nil +} + +// 发送请求 +func (c *WxpayPartner) post(url string, params Params) ([]byte, error) { + var httpc *http.Client + + httpc = c.stdClient + + buf := bytes.NewBuffer(params.Encode()) + resp, err := httpc.Post(url, "application/xml; charset=utf-8", buf) + if err != nil { + return nil, err + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + return body, nil +}