一、准备阶段

配置密钥 、获取证书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

$(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;

}

});

}

})

三、异步通知

签名校验// 验证签名

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

}