一、准备阶段
配置密钥 、获取证书
官方文档 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 发起支付
调用统一下单接口
统一下单接口文档 https://pay.weixin.qq.com/wik...
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 调起支付接口文档 https://pay.weixin.qq.com/wik...
// 微信 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>
三、异步通知
签名校验
文档 https://pay.weixin.qq.com/wik...
// 验证签名
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), ¬ifyResponse); 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
}
四、查询订单
查询订单
文档 https://pay.weixin.qq.com/wik...
// 查询交易
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
}
五、申请退款
申请退款
文档 https://pay.weixin.qq.com/wik...
// 退款
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
}
有疑问加站长微信联系(非本文作者)