一、准备阶段

配置密钥 、获取证书

官方文档 https://kf.qq.com/faq/180830U...
有部分接口需要用到证书
私钥获取后有三个文件

apiclient_key.p12 
apiclient_cert.pem  
apiclient_key.pem

本次示例程序中,使用的是文件 apiclient_cert.pem apiclient_key.pem内容

二、发起微信支付

常量

const appId = ""                                  // 小程序或者公众号的appid
const mchId = ""                                  // 微信支付的商户id
const md5key = ""                                 // 商户md5key

const md5SignType = "MD5"
const jsApiTradeType = "JSAPI"

会用到的包

import (
    "bytes"
    "crypto/md5"
    "encoding/hex"
    "fmt"
    "math/rand"
    "sort"
    "strings"
    "time"
)
import (
    "bytes"
    "crypto/tls"
    "encoding/xml"
    "errors"
    "fmt"
    "io/ioutil"
    "net/http"
    "time"
)

辅助函数

func getRandomString(length int) string {
    str := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    byts := []byte(str)
    bytesLen := len(byts)
    var result []byte
    r := rand.New(rand.NewSource(time.Now().UnixNano()))
    for i := 0; i < length; i++ {
        result = append(result, byts[r.Intn(bytesLen)])
    }
    return string(result)
}
func randNumber() string {
    return fmt.Sprintf("%05v", rand.New(rand.NewSource(time.Now().UnixNano())).Int31n(100000))
}
// MD5加密
func hashaMd5(str string) string {
    h := md5.New()
    h.Write([]byte(str))
    cipherStr := h.Sum(nil)
    return hex.EncodeToString(cipherStr)
}
func getMd5Sign(paramMap map[string]string, signKey string) string {
    sortString := getSortString(paramMap)
    //fmt.Println("sortString", sortString)
    sign := hashaMd5(sortString + "&key=" + signKey)
    return strings.ToUpper(sign)
}
// 验证签名
func checkMd5Sign(rspMap map[string]string, md5Key, sign string) bool {
    calculateSign := getMd5Sign(rspMap, md5Key)
    //fmt.Println(calculateSign, sign, md5Key)
    return calculateSign == sign
}
// 支付字符串拼接
func getSortString(m map[string]string) string {
    var buf bytes.Buffer
    keys := make([]string, 0, len(m))
    for k := range m {
        keys = append(keys, k)
    }
    sort.Strings(keys)
    for _, k := range keys {
        vs := m[k]
        if vs == "" {
            continue
        }
        if buf.Len() > 0 {
            buf.WriteByte('&')
        }
        buf.WriteString(k)
        buf.WriteByte('=')
        buf.WriteString(vs)
    }
    return buf.String()
}

jsapi 发起支付

调用统一下单接口

type CommonPayParam struct {
    XMLName        xml.Name `xml:"xml" json:"-"`
    AppId          string   `json:"appid" xml:"appid"`
    MchId          string   `json:"mch_id" xml:"mch_id"`
    NonceStr       string   `json:"nonce_str" xml:"nonce_str"`
    Body           string   `json:"body" xml:"body"`
    NotifyUrl      string   `json:"notify_url" xml:"notify_url"`
    Openid         string   `json:"openid" xml:"openid"`
    OutTradeNo     string   `json:"out_trade_no" xml:"out_trade_no"`
    SpBillCreateIp string   `json:"spbill_create_ip" xml:"spbill_create_ip"`
    TotalFee       string   `json:"total_fee" xml:"total_fee"`
    TradeType      string   `json:"trade_type" xml:"trade_type"`
    Sign           string   `json:"sign" xml:"sign"`
    SignType       string   `json:"sign_type" xml:"sign_type"`
}

type CommonPayResponse struct {
    XMLName    xml.Name `xml:"xml" json:"-"`
    ReturnCode string   `json:"return_code" xml:"return_code"`
    ReturnMsg  string   `json:"return_msg" xml:"return_msg"`
    AppId      string   `json:"appid" xml:"appid"`
    MchId      string   `json:"mch_id" xml:"mch_id"`
    NonceStr   string   `json:"nonce_str" xml:"nonce_str"`
    Sign       string   `json:"sign" xml:"sign"`
    ResultCode string   `json:"result_code" xml:"result_code"`
    PrepayId   string   `json:"prepay_id" xml:"prepay_id"`
    TradeType  string   `json:"trade_type" xml:"trade_type"`
}

// 统一下单
const CommonPayUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder"
// 统一下单
func commonPay() (commonPayRes CommonPayResponse, err error) {
    amount := 1
    payParam := make(map[string]string)
    payParam["appid"] = appId
    payParam["mch_id"] = mchId
    payParam["nonce_str"] = getRandomString(32)
    payParam["body"] = fmt.Sprintf("微信充值:¥%d", amount)
    payParam["notify_url"] = "https://hy.life23.cn/order/notify"
    payParam["openid"] = ""
    payParam["out_trade_no"] = fmt.Sprintf("test%s%s", time.Now().Format("20060102150405"), randNumber())
    payParam["spbill_create_ip"] = "127.0.0.1"
    payParam["total_fee"] = fmt.Sprintf("%v", amount)
    payParam["trade_type"] = jsApiTradeType
    payParam["sign_type"] = md5SignType
    payParam["sign"] = getMd5Sign(payParam, md5key)
    commonPayParam := CommonPayParam{
        AppId:          payParam["appid"],
        MchId:          payParam["mch_id"],
        NonceStr:       payParam["nonce_str"],
        Body:           payParam["body"],
        NotifyUrl:      payParam["notify_url"],
        Openid:         payParam["openid"],
        OutTradeNo:     payParam["out_trade_no"],
        SpBillCreateIp: payParam["spbill_create_ip"],
        TotalFee:       payParam["total_fee"],
        TradeType:      payParam["trade_type"],
        Sign:           payParam["sign"],
        SignType:       payParam["sign_type"],
    }
    payXmlBytes, err := xml.Marshal(commonPayParam)
    if err != nil {
        return commonPayRes, err
    }
    //fmt.Println(string(payXmlBytes))
    request, err := http.NewRequest(http.MethodPost, CommonPayUrl, bytes.NewReader(payXmlBytes))
    if err != nil {
        return commonPayRes, err
    }
    client := http.DefaultClient
    response, err := client.Do(request)
    if err != nil {
        return commonPayRes, err
    }
    defer response.Body.Close()
    bodyBytes, err := ioutil.ReadAll(response.Body)
    if err != nil {
        return commonPayRes, err
    }
    if err = xml.Unmarshal(bodyBytes, &commonPayRes); err != nil {
        return commonPayRes, err
    }
    commonPayResParam := make(map[string]string)
    commonPayResParam["return_code"] = commonPayRes.ReturnCode
    commonPayResParam["return_msg"] = commonPayRes.ReturnMsg
    commonPayResParam["appid"] = commonPayRes.AppId
    commonPayResParam["mch_id"] = commonPayRes.MchId
    commonPayResParam["nonce_str"] = commonPayRes.NonceStr
    commonPayResParam["result_code"] = commonPayRes.ResultCode
    commonPayResParam["prepay_id"] = commonPayRes.PrepayId
    commonPayResParam["trade_type"] = commonPayRes.TradeType
    if !checkMd5Sign(commonPayResParam, md5key, commonPayRes.Sign) {
        return commonPayRes, errors.New("common pay response sign verify error")
    }
    return commonPayRes, nil
}

生成jsapi发起支付

// 微信 jsApi 支付
func jsApi(commonPayRes CommonPayResponse) (payParam map[string]string) {
    payParam = make(map[string]string)
    payParam["appId"] = appId
    payParam["timeStamp"] = fmt.Sprintf("%v", time.Now().Unix())
    payParam["nonceStr"] = getRandomString(32)
    payParam["package"] = "prepay_id=" + commonPayRes.PrepayId
    payParam["signType"] = md5SignType
    payParam["paySign"] = getMd5Sign(payParam, md5key)
    return payParam
}

前台发起支付js

需要加载微信js http://res.wx.qq.com/open/js/...
调用微信js需要在微信支付平台,设置支付目录
指引文档
https://pay.weixin.qq.com/wik...
https://pay.weixin.qq.com/wik...

<script type="text/javascript" src="__STATIC__/frontend/js/jquery.min.js"></script>
<script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
<script>  
    $(function () {
        $(".am-btn").click(function () {
            var score = $(".score div input:checked").val();
            $.post("发起微信支付后端接口URL", {"score": score}, function (res) {
                if (res.status === 500) {
                    alert(res.message);
                    return;
                }
                if (typeof WeixinJSBridge == "undefined") {
                    if (document.addEventListener) {
                        document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
                    } else if (document.attachEvent) {
                        document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
                        document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
                    }
                } else {
                    onBridgeReady(res);
                }
            })
        })
        function onBridgeReady(param) {
            var orderId = param.data.orderId;
            WeixinJSBridge.invoke('getBrandWCPayRequest', {
                    "appId": param.data.appId,
                    "timeStamp": param.data.timeStamp,
                    "nonceStr": param.data.nonceStr,
                    "package": param.data.package,
                    "signType": param.data.signType,
                    "paySign": param.data.paySign
                },
                function (res) {
                    if (res.err_msg === "get_brand_wcpay_request:ok") {
                        window.location.href = "{:url('index/order/successful')}?order_id=" + orderId;
                    }
                });
        }
    })
  </script>

三、异步通知

签名校验

// 验证签名
type NotifyResponse struct {
    XMLName       xml.Name `xml:"xml" json:"-"`
    AppId         string   `json:"appid" xml:"appid"`
    BankType      string   `json:"bank_type" xml:"bank_type"`
    CashFee       string   `json:"cash_fee" xml:"cash_fee"`
    FeeType       string   `json:"fee_type" xml:"fee_type"`
    IsSubscribe   string   `json:"is_subscribe" xml:"is_subscribe"`
    MchId         string   `json:"mch_id" xml:"mch_id"`
    NonceStr      string   `json:"nonce_str" xml:"nonce_str"`
    Openid        string   `json:"openid" xml:"openid"`
    OutTradeNo    string   `json:"out_trade_no" xml:"out_trade_no"`
    ResultCode    string   `json:"result_code" xml:"result_code"`
    ReturnCode    string   `json:"return_code" xml:"return_code"`
    Sign          string   `json:"sign" xml:"sign"`
    TimeEnd       string   `json:"time_end" xml:"time_end"`
    TotalFee      string   `json:"total_fee" xml:"total_fee"`
    TradeType     string   `json:"trade_type" xml:"trade_type"`
    TransactionId string   `json:"transaction_id" xml:"transaction_id"`
}
// 异步通知验证
func notifyValidate(rowPost string) (bool, error) {
    var notifyResponse NotifyResponse
    if err := xml.Unmarshal([]byte(rowPost), &notifyResponse); err != nil {
        return false, err
    }
    notifyMap := make(map[string]string)
    notifyMap["appid"] = notifyResponse.AppId
    notifyMap["bank_type"] = notifyResponse.BankType
    notifyMap["cash_fee"] = notifyResponse.CashFee
    notifyMap["fee_type"] = notifyResponse.FeeType
    notifyMap["is_subscribe"] = notifyResponse.IsSubscribe
    notifyMap["mch_id"] = notifyResponse.MchId
    notifyMap["nonce_str"] = notifyResponse.NonceStr
    notifyMap["openid"] = notifyResponse.Openid
    notifyMap["out_trade_no"] = notifyResponse.OutTradeNo
    notifyMap["result_code"] = notifyResponse.ResultCode
    notifyMap["return_code"] = notifyResponse.ReturnCode
    notifyMap["time_end"] = notifyResponse.TimeEnd
    notifyMap["total_fee"] = notifyResponse.TotalFee
    notifyMap["trade_type"] = notifyResponse.TradeType
    notifyMap["transaction_id"] = notifyResponse.TransactionId
    
    // 签名校验
    if !checkMd5Sign(notifyMap, md5key, notifyResponse.Sign) {
        return false, errors.New("notify trade result sign verify error")
    }
    
    return true, nil
}

四、查询订单

查询订单

// 查询交易
const searchTradeUrl = "https://api.mch.weixin.qq.com/pay/orderquery"
type SearchTradeParam struct {
    XMLName    xml.Name `xml:"xml" json:"-"`
    AppId      string   `json:"appid" xml:"appid"`
    MchId      string   `json:"mch_id" xml:"mch_id"`
    OutTradeNo string   `json:"out_trade_no" xml:"out_trade_no"`
    NonceStr   string   `json:"nonce_str" xml:"nonce_str"`
    SignType   string   `json:"sign_type" xml:"sign_type"`
    Sign       string   `json:"sign" xml:"sign"`
}
type SearchTradeResponse struct {
    ReturnCode     string `json:"return_code" xml:"return_code"`
    ReturnMsg      string `json:"return_msg" xml:"return_msg"`
    AppId          string `json:"appid" xml:"appid"`
    MchId          string `json:"mch_id" xml:"mch_id"`
    DeviceInfo     string `json:"device_info" xml:"device_info"`
    NonceStr       string `json:"nonce_str" xml:"nonce_str"`
    Sign           string `json:"sign" xml:"sign"`
    ResultCode     string `json:"result_code" xml:"result_code"`
    Openid         string `json:"openid" xml:"openid"`
    IsSubscribe    string `json:"is_subscribe" xml:"is_subscribe"`
    TradeType      string `json:"trade_type" xml:"trade_type"`
    BankType       string `json:"bank_type" xml:"bank_type"`
    TotalFee       string `json:"total_fee" xml:"total_fee"`
    FeeType        string `json:"fee_type" xml:"fee_type"`
    TransactionId  string `json:"transaction_id" xml:"transaction_id"`
    OutTradeNo     string `json:"out_trade_no" xml:"out_trade_no"`
    Attach         string `json:"attach" xml:"attach"`
    TimeEnd        string `json:"time_end" xml:"time_end"`
    TradeState     string `json:"trade_state" xml:"trade_state"`
    CashFee        string `json:"cash_fee" xml:"cash_fee"`
    TradeStateDesc string `json:"trade_state_desc" xml:"trade_state_desc"`
    CashFeeType    string `json:"cash_fee_type" xml:"cash_fee_type"`
}
// 查询交易
func searchTrade(orderId string) (trade SearchTradeResponse, err error) {
    paramMap := make(map[string]string)
    paramMap["appid"] = appId
    paramMap["mch_id"] = mchId
    paramMap["out_trade_no"] = orderId
    paramMap["nonce_str"] = getRandomString(32)
    paramMap["sign_type"] = md5SignType
    paramMap["sign"] = getMd5Sign(paramMap, md5key)
    searchTradeParam := SearchTradeParam{
        AppId:      paramMap["appid"],
        MchId:      paramMap["mch_id"],
        OutTradeNo: paramMap["out_trade_no"],
        NonceStr:   paramMap["nonce_str"],
        SignType:   paramMap["sign_type"],
        Sign:       paramMap["sign"],
    }
    searchTradeParamBytes, err := xml.Marshal(searchTradeParam)
    if err != nil {
        return trade, err
    }
    request, err := http.NewRequest(http.MethodPost, searchTradeUrl, bytes.NewReader(searchTradeParamBytes))
    if err != nil {
        return trade, err
    }
    client := http.DefaultClient
    response, err := client.Do(request)
    if err != nil {
        return trade, err
    }
    defer response.Body.Close()
    bodyBytes, err := ioutil.ReadAll(response.Body)
    if err != nil {
        return trade, err
    }
    if err := xml.Unmarshal(bodyBytes, &trade); err != nil {
        return trade, err
    }
    searchTradeMap := make(map[string]string)
    searchTradeMap["return_code"] = trade.ReturnCode
    searchTradeMap["return_msg"] = trade.ReturnMsg
    searchTradeMap["appid"] = trade.AppId
    searchTradeMap["mch_id"] = trade.MchId
    searchTradeMap["device_info"] = trade.DeviceInfo
    searchTradeMap["nonce_str"] = trade.NonceStr
    searchTradeMap["result_code"] = trade.ResultCode
    searchTradeMap["openid"] = trade.Openid
    searchTradeMap["is_subscribe"] = trade.IsSubscribe
    searchTradeMap["trade_type"] = trade.TradeType
    searchTradeMap["bank_type"] = trade.BankType
    searchTradeMap["total_fee"] = trade.TotalFee
    searchTradeMap["fee_type"] = trade.FeeType
    searchTradeMap["transaction_id"] = trade.TransactionId
    searchTradeMap["out_trade_no"] = trade.OutTradeNo
    searchTradeMap["attach"] = trade.Attach
    searchTradeMap["time_end"] = trade.TimeEnd
    searchTradeMap["trade_state"] = trade.TradeState
    searchTradeMap["cash_fee"] = trade.CashFee
    searchTradeMap["trade_state_desc"] = trade.TradeStateDesc
    searchTradeMap["cash_fee_type"] = trade.CashFeeType
    
    // 签名校验
    if !checkMd5Sign(searchTradeMap, md5key, trade.Sign) {
        return trade, errors.New("search trade result sign verify error")
    }
    
    return trade, nil
}

五、申请退款

申请退款

// 退款
const refundTradeUrl = "https://api.mch.weixin.qq.com/secapi/pay/refund"
type RefundTradeParam struct {
    XMLName     xml.Name `xml:"xml" json:"-"`
    AppId       string   `json:"appid" xml:"appid"`
    MchId       string   `json:"mch_id" xml:"mch_id"`
    NonceStr    string   `json:"nonce_str" xml:"nonce_str"`
    OutTradeNo  string   `json:"out_trade_no" xml:"out_trade_no"`
    OutRefundNo string   `json:"out_refund_no" xml:"out_refund_no"`
    TotalFee    string   `json:"total_fee" xml:"total_fee"`
    RefundFee   string   `json:"refund_fee" xml:"refund_fee"`
    Sign        string   `json:"sign" xml:"sign"`
}
type RefundTradeResponse struct {
    XMLName           xml.Name `xml:"xml" json:"-"`
    ReturnCode        string   `json:"return_code" xml:"return_code"`
    ReturnMsg         string   `json:"return_msg" xml:"return_msg"`
    AppId             string   `json:"appid" xml:"appid"`
    MchId             string   `json:"mch_id" xml:"mch_id"`
    NonceStr          string   `json:"nonce_str" xml:"nonce_str"`
    Sign              string   `json:"sign" xml:"sign"`
    ResultCode        string   `json:"result_code" xml:"result_code"`
    TransactionId     string   `json:"transaction_id" xml:"transaction_id"`
    OutTradeNo        string   `json:"out_trade_no" xml:"out_trade_no"`
    OutRefundNo       string   `json:"out_refund_no" xml:"out_refund_no"`
    RefundId          string   `json:"refund_id" xml:"refund_id"`
    RefundChannel     string   `json:"refund_channel" xml:"refund_channel"`
    RefundFee         string   `json:"refund_fee" xml:"refund_fee"`
    CouponRefundFee   string   `json:"coupon_refund_fee" xml:"coupon_refund_fee"`
    TotalFee          string   `json:"total_fee" xml:"total_fee"`
    CashFee           string   `json:"cash_fee" xml:"cash_fee"`
    CouponRefundCount string   `json:"coupon_refund_count" xml:"coupon_refund_count"`
    CashRefundFee     string   `json:"cash_refund_fee" xml:"cash_refund_fee"`
}
// 申请退款
func refundTrade(orderId string, amount float64) (trade RefundTradeResponse, err error) {
    refundTradeMap := make(map[string]string)
    refundTradeMap["appid"] = appId
    refundTradeMap["mch_id"] = mchId
    refundTradeMap["nonce_str"] = getRandomString(32)
    refundTradeMap["out_trade_no"] = orderId
    refundTradeMap["out_refund_no"] = orderId + "-1"
    refundTradeMap["total_fee"] = fmt.Sprintf("%v", amount)
    refundTradeMap["refund_fee"] = fmt.Sprintf("%v", amount)
    refundTradeMap["sign"] = getMd5Sign(refundTradeMap, md5key)
    refundTradeParam := RefundTradeParam{
        AppId:       refundTradeMap["appid"],
        MchId:       refundTradeMap["mch_id"],
        NonceStr:    refundTradeMap["nonce_str"],
        OutTradeNo:  refundTradeMap["out_trade_no"],
        OutRefundNo: refundTradeMap["out_refund_no"],
        TotalFee:    refundTradeMap["total_fee"],
        RefundFee:   refundTradeMap["refund_fee"],
        Sign:        refundTradeMap["sign"],
    }
    marshal, err := xml.Marshal(refundTradeParam)
    if err != nil {
        return trade, err
    }
    request, err := http.NewRequest(http.MethodPost, refundTradeUrl, bytes.NewReader(marshal))
    if err != nil {
        return trade, err
    }
    certFile := "./apiclient_cert.pem" // 商户证书
    keyFile := "./apiclient_key.pem"   // 商户证书
    var cliCrt tls.Certificate // 具体的证书加载对象
    cliCrt, err = tls.LoadX509KeyPair(certFile, keyFile)
    if err != nil {
        return trade, err
    }
    client := http.Client{
        Transport: &http.Transport{
            TLSClientConfig: &tls.Config{Certificates: []tls.Certificate{cliCrt}},
        },
    }
    response, err := client.Do(request)
    if err != nil {
        return trade, err
    }
    defer response.Body.Close()
    bodyBytes, err := ioutil.ReadAll(response.Body)
    if err != nil {
        return trade, err
    }
    if err = xml.Unmarshal(bodyBytes, &trade); err != nil {
        return trade, err
    }
    RefundTradeResMap := make(map[string]string)
    RefundTradeResMap["return_code"] = trade.ReturnCode
    RefundTradeResMap["return_msg"] = trade.ReturnMsg
    RefundTradeResMap["appid"] = trade.AppId
    RefundTradeResMap["mch_id"] = trade.MchId
    RefundTradeResMap["nonce_str"] = trade.NonceStr
    RefundTradeResMap["result_code"] = trade.ResultCode
    RefundTradeResMap["transaction_id"] = trade.TransactionId
    RefundTradeResMap["out_trade_no"] = trade.OutTradeNo
    RefundTradeResMap["out_refund_no"] = trade.OutRefundNo
    RefundTradeResMap["refund_id"] = trade.RefundId
    RefundTradeResMap["refund_channel"] = trade.RefundChannel
    RefundTradeResMap["refund_fee"] = trade.RefundFee
    RefundTradeResMap["coupon_refund_fee"] = trade.CouponRefundFee
    RefundTradeResMap["total_fee"] = trade.TotalFee
    RefundTradeResMap["cash_fee"] = trade.CashFee
    RefundTradeResMap["coupon_refund_count"] = trade.CouponRefundCount
    RefundTradeResMap["cash_refund_fee"] = trade.CashRefundFee
    
    // 签名校验
    if !checkMd5Sign(RefundTradeResMap, md5key, trade.Sign) {
        return trade, errors.New("refund trade sign verify error")
    }
    return trade, nil
}