Browse Source

https cert/key pem, wechat pay refund

ls 5 years ago
parent
commit
23389de15a
6 changed files with 159 additions and 49 deletions
  1. 35 38
      http.go
  2. 2 2
      logger.go
  3. 1 1
      wechat/client.go
  4. 5 3
      wechat/pay.go
  5. 111 0
      wechat/refund.go
  6. 5 5
      wechat/wechat.go

+ 35 - 38
http.go

@@ -3,6 +3,7 @@ package util
 import (
 	"compress/gzip"
 	"context"
+	"crypto/x509"
 	"encoding/json"
 	"encoding/xml"
 	"fmt"
@@ -15,6 +16,8 @@ import (
 	"os/signal"
 	"syscall"
 	"time"
+
+	"crypto/tls"
 )
 
 const (
@@ -108,10 +111,10 @@ func ResponseJSON(reply *ReplyData, err error, req *http.Request, w http.Respons
 // HTTPListenAndServe new server and start
 func HTTPListenAndServe(addr string, router http.Handler) {
 	server := &http.Server{
-		Addr:    addr,
-		Handler: router,
-		//ReadTimeout:    30 * time.Second,
-		//WriteTimeout:   30 * time.Second,
+		Addr:           addr,
+		Handler:        router,
+		ReadTimeout:    30 * time.Second,
+		WriteTimeout:   30 * time.Second,
 		MaxHeaderBytes: 1 << 20,
 	}
 	// Interrupt handler.
@@ -134,8 +137,8 @@ func HTTPListenAndServe(addr string, router http.Handler) {
 	log.Println("Exit", <-errc)
 }
 
-func newRequest(method, uri string, header map[string]string, body io.Reader) (res *http.Response, err error) {
-	client := &http.Client{Transport: &http.Transport{
+func newRequest(method, uri, certPath, keyPath string, header map[string]string, body io.Reader) (res *http.Response, err error) {
+	t := &http.Transport{
 		Dial: func(netw, addr string) (net.Conn, error) {
 			conn, err := net.DialTimeout(netw, addr, time.Second*RequestTimeOut)
 			if err != nil {
@@ -145,7 +148,18 @@ func newRequest(method, uri string, header map[string]string, body io.Reader) (r
 			return conn, nil
 		},
 		ResponseHeaderTimeout: time.Second * RequestTimeOut,
-	}}
+	}
+
+	if certPath != "" {
+		cert, e := tls.LoadX509KeyPair(certPath, keyPath)
+		if e != nil {
+			t.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
+		} else {
+			pool := x509.NewCertPool()
+			t.TLSClientConfig = &tls.Config{InsecureSkipVerify: true, Certificates: []tls.Certificate{cert}, RootCAs: pool}
+		}
+	}
+	client := &http.Client{Transport: t}
 	var (
 		req *http.Request
 	)
@@ -165,21 +179,14 @@ func newRequest(method, uri string, header map[string]string, body io.Reader) (r
 	}
 
 	res, err = client.Do(req)
-
 	return
 }
 
-// Post HTTP request POST
-func Post(uri string, header map[string]string, data io.Reader) (msg Message, err error) {
-	var res *http.Response
-	if res, err = newRequest("POST", uri, header, data); err != nil {
-		return
-	}
+func readBody(res *http.Response) (msg Message, err error) {
 	var (
 		body   []byte
 		reader io.Reader
 	)
-	defer res.Body.Close()
 	encoding := res.Header.Get("Content-Encoding")
 	switch encoding {
 	case "gzip":
@@ -200,36 +207,26 @@ func Post(uri string, header map[string]string, data io.Reader) (msg Message, er
 	return
 }
 
-// Get HTTP request GET
-func Get(uri string, header map[string]string) (msg Message, err error) {
+// Post HTTP request POST
+func Post(uri, certPath, keyPath string, header map[string]string, data io.Reader) (msg Message, err error) {
 	var res *http.Response
-	if res, err = newRequest("GET", uri, header, nil); err != nil {
+	if res, err = newRequest("POST", uri, certPath, keyPath, header, data); err != nil {
 		return
 	}
-
-	var (
-		body   []byte
-		reader io.Reader
-	)
 	defer res.Body.Close()
-	encoding := res.Header.Get("Content-Encoding")
-	switch encoding {
-	case "gzip":
-		reader, err = gzip.NewReader(res.Body)
-		if err == nil {
-			body, err = ioutil.ReadAll(reader)
-		}
-	default:
-		body, err = ioutil.ReadAll(res.Body)
-	}
-	if err != nil {
+	msg, err = readBody(res)
+	return
+}
+
+// Get HTTP request GET
+func Get(uri, certPath, keyPath string, header map[string]string) (msg Message, err error) {
+	var res *http.Response
+	if res, err = newRequest("GET", uri, certPath, keyPath, header, nil); err != nil {
 		return
 	}
 
-	msg.StatusCode = res.StatusCode
-	msg.Header = res.Header
-	msg.Body = body
-
+	defer res.Body.Close()
+	msg, err = readBody(res)
 	return
 }
 

+ 2 - 2
logger.go

@@ -25,7 +25,7 @@ func NewDefaultLogger(path string) (*Logger, error) {
 
 	deflog = &Logger{}
 	deflog.FP = fp
-	deflog.Log = log.New(fp, "[INFO]", log.Ldate|log.Ltime)
+	deflog.Log = log.New(fp, "[INFO]", log.LstdFlags)
 
 	return deflog, err
 }
@@ -43,7 +43,7 @@ func ReopenDefaultLogger(path string) (*Logger, error) {
 	}
 	fpo := deflog.FP
 	deflog.FP = fp
-	deflog.Log = log.New(fp, "[INFO]", log.Ldate|log.Ltime)
+	deflog.Log = log.New(fp, "[INFO]", log.LstdFlags)
 	fpo.Close()
 
 	return deflog, err

+ 1 - 1
wechat/client.go

@@ -358,7 +358,7 @@ func (wc Client) GetSignPackage(uri, nonceStr string) (sign SignPackage, err err
 	s += `&timestamp=` + fmt.Sprintf("%d", unix)
 	s += `&url=` + uri
 
-	fmt.Println(s)
+	//fmt.Println(s)
 	sign.AppID = wc.AppID
 	sign.NonceStr = nonceStr
 	sign.Signature = util.SHA1(s)

+ 5 - 3
wechat/pay.go

@@ -12,10 +12,12 @@ const (
 	// WePayHost base host
 	WePayHost = `https://api.mch.weixin.qq.com`
 
-	// WePayURLUnifiedOrder pay
+	// WePayURLUnifiedOrder pay 付款
 	WePayURLUnifiedOrder = `/pay/unifiedorder`
-	// WePayURLUnifiedQuery order query
+	// WePayURLUnifiedQuery order query 订单查询
 	WePayURLUnifiedQuery = `/pay/orderquery`
+	// WePayURLPayRefund pay refund 退款
+	WePayURLPayRefund = `/secapi/pay/refund`
 
 	// WePayTradeTypeJS JSAPI 公众号支付
 	WePayTradeTypeJS = `JSAPI`
@@ -273,7 +275,7 @@ func post(url string, data []byte) (reply WePayReply, err error) {
 
 	headers["Accept"] = "application/xml"
 	headers["Content-Type"] = "application/xml; charset=utf-8"
-	msg, err = util.Post(url, headers, bytes.NewReader(data))
+	msg, err = util.Post(url, "", "", headers, bytes.NewReader(data))
 	if err != nil {
 		return
 	}

+ 111 - 0
wechat/refund.go

@@ -0,0 +1,111 @@
+package wechat
+
+import (
+	"bytes"
+	"encoding/xml"
+	"fmt"
+
+	"git.chuangxin1.com/cx/util"
+)
+
+/*
+退款示例:
+
+<xml>
+   <appid>wx2421b1c4370ec43b</appid>
+   <mch_id>10000100</mch_id>
+   <nonce_str>6cefdb308e1e2e8aabd48cf79e546a02</nonce_str>
+   <out_refund_no>1415701182</out_refund_no>
+   <out_trade_no>1415757673</out_trade_no>
+   <refund_fee>1</refund_fee>
+   <total_fee>1</total_fee>
+   <transaction_id>4006252001201705123297353072</transaction_id>
+   <sign>FE56DD4AA85C0EECA82C35595A69E153</sign>
+</xml>
+//*/
+
+// WePayRefund https://api.mch.weixin.qq.com/secapi/pay/refund
+type WePayRefund struct {
+	XMLName  xml.Name `xml:"xml"`
+	AppID    string   `xml:"appid"`     // 公众账号 ID string(32)
+	MchID    string   `xml:"mch_id"`    // 商户号 string(32)
+	NonceStr string   `xml:"nonce_str"` // 随机字符串 string(32)
+	Sign     string   `xml:"sign"`      // 签名 string(32)
+	//SignType      string   `xml:"sign_type"`       // 签名类型, 默认 MD5 string(32), HMAC-SHA256 和 MD5
+	OutTradeNo  string `xml:"out_trade_no"`  // 商户订单号
+	OutRefundNo string `xml:"out_refund_no"` // 商户退款单号
+	TotalFee    int    `xml:"total_fee"`     // 订单金额
+	RefundFee   int    `xml:"refund_fee"`    // 退款金额
+	//RefundFeeType string   `xml:"refund_fee_type"` // 退款货币种类, 款货币类型,需与支付一致,或者不填。符合ISO 4217标准的三位字母代码,默认人民币:CNY
+	NotifyURL string `xml:"notify_url"` // 退款结果通知 url, 异步接收微信支付退款结果通知的回调地址,通知URL必须为外网可访问的url,不允许带参数。如果参数中传了notify_url,则商户平台上配置的回调地址将不会生效
+}
+
+// WePayRefundReply pay refund reply
+type WePayRefundReply struct {
+	XMLName       xml.Name `xml:"xml" json:"_,omitempty"`
+	ReturnCode    string   `xml:"return_code"`
+	ReturnMsg     string   `xml:"return_msg"`
+	ResultCode    string   `xml:"result_code"`
+	ErrCode       string   `xml:"err_code"`
+	ErrCodeDes    string   `xml:"err_code_des"`
+	AppID         string   `xml:"appid"`
+	MchID         string   `xml:"mch_id"`
+	NonceStr      string   `xml:"nonce_str"`
+	Sign          string   `xml:"sign"`
+	TransactionID string   `xml:"transaction_id"`
+	OutTradeNo    string   `xml:"out_trade_no"`
+	OutRefundNo   string   `xml:"out_refund_no"`
+	RefundFee     int      `xml:"refund_fee"` // 退款金额
+	TotalFee      int      `xml:"total_fee"`  // 标价金额, 订单总金额,单位为分,只能为整数
+	FeeType       int      `xml:"fee_type"`   // 退款货币种类, 款货币类型,需与支付一致,或者不填。符合ISO 4217标准的三位字母代码,默认人民币:CNY
+	CashFee       int      `xml:"cash_fee"`   // 现金支付金额
+}
+
+// PayRefund 退款
+func PayRefund(config WePayConfig, order WePayRefund) (reply WePayRefundReply, err error) {
+	var data []byte
+	m := make(map[string]interface{})
+
+	m["appid"] = order.AppID
+	m["mch_id"] = order.MchID
+	m["nonce_str"] = order.NonceStr
+	m["total_fee"] = order.TotalFee
+	m["out_trade_no"] = order.OutTradeNo
+	m["out_refund_no"] = order.OutRefundNo
+	m["total_fee"] = order.TotalFee
+	m["refund_fee"] = order.RefundFee
+	m["notify_url"] = order.NotifyURL
+
+	//order.SignType = `MD5`
+	order.Sign = Sign(m, config.Key)
+	if data, err = xml.Marshal(order); err != nil {
+		return
+	}
+	fmt.Println(string(data))
+
+	url := WePayHost + WePayURLPayRefund
+	reply, err = postRefund(url, config.SSLCert, config.SSLKey, data)
+	bs, _ := xml.Marshal(reply)
+	fmt.Println(string(bs))
+	return
+}
+
+func postRefund(url, cert, key string, data []byte) (reply WePayRefundReply, err error) {
+	var (
+		msg     util.Message
+		headers = make(map[string]string)
+	)
+
+	headers["Accept"] = "application/xml"
+	headers["Content-Type"] = "application/xml; charset=utf-8"
+	msg, err = util.Post(url, cert, key, headers, bytes.NewReader(data))
+	if err != nil {
+		fmt.Println("util.Post", err)
+		return
+	}
+	fmt.Println(string(msg.Body))
+
+	err = xml.Unmarshal(msg.Body, &reply)
+
+	return
+}

+ 5 - 5
wechat/wechat.go

@@ -159,7 +159,7 @@ func checkError(msg util.Message) (res Response, err error) {
 
 func getJSON(uri string) (res Response, err error) {
 	var msg util.Message
-	if msg, err = util.Get(uri, map[string]string{}); err != nil {
+	if msg, err = util.Get(uri, "", "", map[string]string{}); err != nil {
 		return
 	}
 	res, err = checkError(msg)
@@ -168,7 +168,7 @@ func getJSON(uri string) (res Response, err error) {
 
 func getBody(uri string) (body []byte, err error) {
 	var msg util.Message
-	if msg, err = util.Get(uri, map[string]string{}); err != nil {
+	if msg, err = util.Get(uri, "", "", map[string]string{}); err != nil {
 		return
 	}
 	body, err = checkErrorWithBody(msg)
@@ -183,7 +183,7 @@ func postXML(url string, data []byte) (res Response, err error) {
 
 	headers["Accept"] = "application/json"
 	headers["Content-Type"] = "application/xml; charset=utf-8"
-	msg, err = util.Post(url, headers, bytes.NewReader(data))
+	msg, err = util.Post(url, "", "", headers, bytes.NewReader(data))
 	if err != nil {
 		return
 	}
@@ -199,7 +199,7 @@ func postJSON(url string, data []byte) (res Response, err error) {
 
 	headers["Accept"] = "application/json"
 	headers["Content-Type"] = "application/json; charset=utf-8"
-	msg, err = util.Post(url, headers, bytes.NewReader(data))
+	msg, err = util.Post(url, "", "", headers, bytes.NewReader(data))
 	if err != nil {
 		return
 	}
@@ -215,7 +215,7 @@ func postBody(uri string, data []byte) (body []byte, err error) {
 
 	headers["Accept"] = "application/json"
 	headers["Content-Type"] = "application/json; charset=utf-8"
-	msg, err = util.Post(uri, headers, bytes.NewReader(data))
+	msg, err = util.Post(uri, "", "", headers, bytes.NewReader(data))
 	if err != nil {
 		return
 	}