소스 검색

Merge pull request #118 from akikistyle/add-refund

添加退款接口,util http增加CA证书
silenceper 6 년 전
부모
커밋
3be94cd80d
2개의 변경된 파일177개의 추가작업 그리고 0개의 파일을 삭제
  1. 108 0
      pay/refund.go
  2. 69 0
      util/http.go

+ 108 - 0
pay/refund.go

@@ -0,0 +1,108 @@
+package pay
+
+import (
+	"encoding/xml"
+	"fmt"
+	"github.com/akikistyle/wechat/util"
+)
+
+var refundGateway = "https://api.mch.weixin.qq.com/secapi/pay/refund"
+
+//RefundParams 调用参数
+type RefundParams struct {
+	TransactionID string
+	OutRefundNo   string
+	TotalFee      string
+	RefundFee     string
+	RefundDesc    string
+	RootCa        string //ca证书
+}
+
+//refundRequest 接口请求参数
+type refundRequest struct {
+	AppID         string `xml:"appid"`
+	MchID         string `xml:"mch_id"`
+	NonceStr      string `xml:"nonce_str"`
+	Sign          string `xml:"sign"`
+	SignType      string `xml:"sign_type,omitempty"`
+	TransactionID string `xml:"transaction_id"`
+	OutRefundNo   string `xml:"out_refund_no"`
+	TotalFee      string `xml:"total_fee"`
+	RefundFee     string `xml:"refund_fee"`
+	RefundDesc    string `xml:"refund_desc,omitempty"`
+	//NotifyUrl     string `xml:"notify_url,omitempty"`
+}
+
+//RefundResponse 接口返回
+type RefundResponse struct {
+	ReturnCode          string `xml:"return_code"`
+	ReturnMsg           string `xml:"return_msg"`
+	AppID               string `xml:"appid,omitempty"`
+	MchID               string `xml:"mch_id,omitempty"`
+	NonceStr            string `xml:"nonce_str,omitempty"`
+	Sign                string `xml:"sign,omitempty"`
+	ResultCode          string `xml:"result_code,omitempty"`
+	ErrCode             string `xml:"err_code,omitempty"`
+	ErrCodeDes          string `xml:"err_code_des,omitempty"`
+	TransactionID       string `xml:"transaction_id,omitempty"`
+	OutTradeNo          string `xml:"out_trade_no,omitempty"`
+	OutRefundNo         string `xml:"out_refund_no,omitempty"`
+	RefundID            string `xml:"refund_id,omitempty"`
+	RefundFee           string `xml:"refund_fee,omitempty"`
+	SettlementRefundFee string `xml:"settlement_refund_fee,omitempty"`
+	TotalFee            string `xml:"total_fee,omitempty"`
+	SettlementTotalFee  string `xml:"settlement_total_fee,omitempty"`
+	FeeType             string `xml:"fee_type,omitempty"`
+	CashFee             string `xml:"cash_fee,omitempty"`
+	CashFeeType         string `xml:"cash_fee_type,omitempty"`
+}
+
+//Refund 退款申请
+func (pcf *Pay) Refund(p *RefundParams) (rsp RefundResponse, err error) {
+	nonceStr := util.RandomStr(32)
+	param := make(map[string]interface{})
+	param["appid"] = pcf.AppID
+	param["mch_id"] = pcf.PayMchID
+	param["nonce_str"] = nonceStr
+	param["out_refund_no"] = p.OutRefundNo
+	param["refund_desc"] = p.RefundDesc
+	param["refund_fee"] = p.RefundFee
+	param["total_fee"] = p.TotalFee
+	param["sign_type"] = "MD5"
+	param["transaction_id"] = p.TransactionID
+
+	bizKey := "&key=" + pcf.PayKey
+	str := orderParam(param, bizKey)
+	sign := util.MD5Sum(str)
+	request := refundRequest{
+		AppID:         pcf.AppID,
+		MchID:         pcf.PayMchID,
+		NonceStr:      nonceStr,
+		Sign:          sign,
+		SignType:      "MD5",
+		TransactionID: p.TransactionID,
+		OutRefundNo:   p.OutRefundNo,
+		TotalFee:      p.TotalFee,
+		RefundFee:     p.RefundFee,
+		RefundDesc:    p.RefundDesc,
+	}
+	rawRet, err := util.PostXMLWithTLS(refundGateway, request, p.RootCa, pcf.PayMchID)
+	if err != nil {
+		return
+	}
+	err = xml.Unmarshal(rawRet, &rsp)
+	if err != nil {
+		return
+	}
+	if rsp.ReturnCode == "SUCCESS" {
+		if rsp.ResultCode == "SUCCESS" {
+			err = nil
+			return
+		}
+		err = fmt.Errorf("refund error, errcode=%s,errmsg=%s", rsp.ErrCode, rsp.ErrCodeDes)
+		return
+	}
+	err = fmt.Errorf("[msg : xmlUnmarshalError] [rawReturn : %s] [params : %s] [sign : %s]",
+		string(rawRet), str, sign)
+	return
+}

+ 69 - 0
util/http.go

@@ -2,11 +2,15 @@ package util
 
 import (
 	"bytes"
+	"crypto/tls"
 	"encoding/json"
+	"encoding/pem"
 	"encoding/xml"
 	"fmt"
+	"golang.org/x/crypto/pkcs12"
 	"io"
 	"io/ioutil"
+	"log"
 	"mime/multipart"
 	"net/http"
 	"os"
@@ -167,3 +171,68 @@ func PostXML(uri string, obj interface{}) ([]byte, error) {
 	}
 	return ioutil.ReadAll(response.Body)
 }
+
+//httpWithTLS CA证书
+func httpWithTLS(rootCa, key string) (*http.Client, error) {
+	var client *http.Client
+	certData, err := ioutil.ReadFile(rootCa)
+	if err != nil {
+		return nil, fmt.Errorf("unable to find cert path=%s, error=%v", rootCa, err)
+	}
+	cert := pkcs12ToPem(certData, key)
+	config := &tls.Config{
+		Certificates: []tls.Certificate{cert},
+	}
+	tr := &http.Transport{
+		TLSClientConfig:    config,
+		DisableCompression: true,
+	}
+	client = &http.Client{Transport: tr}
+	return client, nil
+}
+
+//pkcs12ToPem 将Pkcs12转成Pem
+func pkcs12ToPem(p12 []byte, password string) tls.Certificate {
+	blocks, err := pkcs12.ToPEM(p12, password)
+	defer func() {
+		if x := recover(); x != nil {
+			log.Print(x)
+		}
+	}()
+	if err != nil {
+		panic(err)
+	}
+	var pemData []byte
+	for _, b := range blocks {
+		pemData = append(pemData, pem.EncodeToMemory(b)...)
+	}
+	cert, err := tls.X509KeyPair(pemData, pemData)
+	if err != nil {
+		panic(err)
+	}
+	return cert
+}
+
+//PostXMLWithTLS perform a HTTP/POST request with XML body and TLS
+func PostXMLWithTLS(uri string, obj interface{}, ca, key string) ([]byte, error) {
+	xmlData, err := xml.Marshal(obj)
+	if err != nil {
+		return nil, err
+	}
+
+	body := bytes.NewBuffer(xmlData)
+	client, err := httpWithTLS(ca, key)
+	if err != nil {
+		return nil, err
+	}
+	response, err := client.Post(uri, "application/xml;charset=utf-8", body)
+	if err != nil {
+		return nil, err
+	}
+	defer response.Body.Close()
+
+	if response.StatusCode != http.StatusOK {
+		return nil, fmt.Errorf("http code error : uri=%v , statusCode=%v", uri, response.StatusCode)
+	}
+	return ioutil.ReadAll(response.Body)
+}