golang实现微信小程序支付通知

小程序支付的交互图如下:

小程序支付时序图

商户系统和微信支付系统主要交互:
1、小程序内调用登录接口,获取到用户的openid,api参见公共api【小程序登录API】
2、商户server调用支付统一下单,api参见公共api【统一下单API】
3、商户server调用再次签名,api参见公共api【再次签名】
4、商户server接收支付通知,api参见公共api【支付结果通知API】
5、商户server查询支付结果,api参见公共api【查询订单API】

以下是支付结果通知API

type WXPayNotifyReq struct {
    Appid          string `xml:"appid"`
    Bank_type      string `xml:"bank_type"`
    Cash_fee       float64   `xml:"cash_fee"`
    Fee_type       string `xml:"fee_type"`
    Is_subscribe   string `xml:"is_subscribe"`
    Mch_id         string `xml:"mch_id"`
    Nonce_str      string `xml:"nonce_str"`
    Openid         string `xml:"openid"`
    Out_trade_no   string `xml:"out_trade_no"`
    Result_code    string `xml:"result_code"`
    Return_code    string `xml:"return_code"`
    Sign           string `xml:"sign"`
    Time_end       string `xml:"time_end"`
    Total_fee      float64 `xml:"total_fee"`
    Trade_type     string `xml:"trade_type"`
    Transaction_id string `xml:"transaction_id"`
}


type WXPayNotifyResp struct {
    Return_code string `xml:"return_code"`
    Return_msg  string `xml:"return_msg"`
}
/** * 微信通知接口 */
func WeixinNoticeHandler(rw http.ResponseWriter,req *http.Request) {
    body,err := ioutil.ReadAll(req.Body)
    if err != nil {
        logger.Error("读取http body失败,原因!",err)
        http.Error(rw.(http.ResponseWriter),http.StatusText(http.StatusBadRequest),http.StatusBadRequest)
        return
    }
    defer req.Body.Close()
    logger.Info("微信支付异步通知,HTTP Body:",string(body))

    var mr WXPayNotifyReq
    err = xml.Unmarshal(body,&mr)
    if err != nil {
        logger.Error("解析HTTP Body格式到xml失败,原因!",http.StatusBadRequest)
        return
    }

    var reqMap map[string]interface{}
    reqMap = make(map[string]interface{},0)

    reqMap["appid"] = mr.Appid
    reqMap["bank_type"] = mr.Bank_type
    reqMap["cash_fee"] = mr.Cash_fee
    reqMap["fee_type"] = mr.Fee_type
    reqMap["is_subscribe"] = mr.Is_subscribe
    reqMap["mch_id"] = mr.Mch_id
    reqMap["nonce_str"] = mr.Nonce_str
    reqMap["openid"] = mr.Openid
    reqMap["out_trade_no"] = mr.Out_trade_no
    reqMap["result_code"] = mr.Result_code
    reqMap["return_code"] = mr.Return_code
    reqMap["time_end"] = mr.Time_end
    reqMap["total_fee"] = mr.Total_fee
    reqMap["trade_type"] = mr.Trade_type
    reqMap["transaction_id"] = mr.Transaction_id

    var resp WXPayNotifyResp
    //进行签名校验
    if wxpayVerifySign(reqMap,mr.Sign) {
        //transactionId := reqMap["transaction_id"]
        orderCode :=  reqMap["out_trade_no"]
        total_fee := reqMap["total_fee"].(float64) //分->元 除以100
        rows,err := mysqlDB.Query("SELECT * FROM canyin_order WHERE dno = ?",orderCode)
        if err!=nil{
            logger.Error("微信查询价格错误",err)
            return
        }
        defer rows.Close()
        orders := RowResult(rows)
        if len(orders) > 0 {
            orderInfo := orders[0].(map[string]interface{})
            //orderId := ToStr(orderInfo["id"])
            allcost,_ := strconv.ParseFloat(ToStr(orderInfo["allcost"]),64)
            logger.Info("价格比对","---",allcost,total_fee)
            //商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现“假通知”,造成资金损失
            if allcost == total_fee {
                logger.Info("订单验证成功")
                //以下是业务处理
            }
            resp.Return_code = "SUCCESS"
            resp.Return_msg = "OK"
        }else{
            resp.Return_code = "FAIL"
            resp.Return_msg = "无此订单"
        }
    }else {
        resp.Return_code = "FAIL"
        resp.Return_msg = "failed to verify sign,please retry!"
    }

    //结果返回,微信要求如果成功需要返回return_code "SUCCESS"
    bytes,_err := xml.Marshal(resp) //string(bytes)
    strResp := strings.Replace(bytes2str(bytes),"WXPayNotifyResp","xml",-1)
    if _err != nil {
        logger.Error("xml编码失败,原因:",_err)
        http.Error(rw.(http.ResponseWriter),http.StatusBadRequest)
        return
    }
    rw.(http.ResponseWriter).WriteHeader(http.StatusOK)
    fmt.Fprint(rw.(http.ResponseWriter),strResp)
}
//微信支付签名验证函数
func wxpayVerifySign(needVerifyM map[string]interface{},sign string) bool {
    //方法名 行数
    pc,_,line,_ := runtime.Caller(0)
    fc := runtime.FuncForPC(pc)

    WECHAT_API_KEY := "" //微信商户key
    signCalc := wxpayCalcSign(needVerifyM,WECHAT_API_KEY)
    logger.Info(fc.Name(),"计算出来的sign: ",signCalc)
    logger.Info(fc.Name(),"微信异步通知sign: ",sign)
    if sign == signCalc {
        logger.Info(fc.Name(),"签名校验通过!")
        return true
    }

    logger.Error(fc.Name(),"签名校验失败!")
    return false
}
//微信支付计算签名的函数
func wxpayCalcSign(mReq map[string]interface{},key string) (sign string) {
    //方法名 行数
    pc,_,line,_ := runtime.Caller(0)
    fc := runtime.FuncForPC(pc)

    logger.Info(fc.Name(),"微信支付签名计算,API KEY:",key)
    //STEP 1,对key进行升序排序.
    sorted_keys := make([]string,0)
    for k,_ := range mReq { sorted_keys = append(sorted_keys,k) }

    sort.Strings(sorted_keys)

    //STEP2,对key=value的键值对用&连接起来,略过空值
    var signStrings string
    for _,k := range sorted_keys {
        logger.Printf("k=%v,v=%v\n",k,mReq[k])
        value := fmt.Sprintf("%v",mReq[k])
        if value != "" {
            signStrings = signStrings + k + "=" + value + "&"
        }
    }

    //STEP3,在键值对的最后加上key=API_KEY
    if key != "" {
        signStrings = signStrings + "key=" + key
    }

    //STEP4,进行MD5签名并且将所有字符转为大写.
    md5Ctx := md5.New()
    md5Ctx.Write(str2bytes(signStrings))
    cipherStr := md5Ctx.Sum(nil)
    upperSign := strings.ToUpper(hex.EncodeToString(cipherStr))
    return upperSign
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


类型转换 1、int转string 2、string转int 3、string转float 4、用户结构类型转换
package main import s "strings" import "fmt" var p = fmt.Println func main() { p("Contains: ", s.Contains("test&quo
类使用:实现一个people中有一个sayhi的方法调用功能,代码如下: 接口使用:实现上面功能,代码如下:
html代码: beego代码:
1、读取文件信息: 2、读取文件夹下的所有文件: 3、写入文件信息 4、删除文件,成功返回true,失败返回false
配置环境:Windows7+推荐IDE:LiteIDEGO下载地址:http://www.golangtc.com/downloadBeego开发文档地址:http://beego.me/docs/intro/ 安装步骤: 一、GO环境安装 二、配置系统变量 三、Beego安装 一、GO环境安装 根
golang获取程序运行路径:
Golang的文档和社区资源:为什么它可以帮助开发人员快速上手?
Golang:AI 开发者的实用工具
Golang的标准库:为什么它可以大幅度提高开发效率?
Golang的部署和运维:如何将应用程序部署到生产环境中?
高性能AI开发:Golang的优势所在
本篇文章和大家了解一下go语言开发优雅得关闭协程的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。1.简介本文将介绍首先为什么需要主...
这篇文章主要介绍了Go关闭goroutine协程的方法,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。1.简介本文将介绍首先为什么需要主动关闭gor...
本篇文章和大家了解一下go关闭GracefulShutdown服务的几种方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。目录Shutdown方法Regi...
这篇文章主要介绍了Go语言如何实现LRU算法的核心思想和实现过程,具有一定借鉴价值,需要的朋友可以参考下。下面就和我一起来看看吧。GO实现Redis的LRU例子常
今天小编给大家分享的是Go简单实现多租户数据库隔离的方法,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。一定会...
这篇“Linux系统中怎么安装NSQ的Go语言客户端”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希
本文小编为大家详细介绍“怎么在Go语言中实现锁机制”,内容详细,步骤清晰,细节处理妥当,希望这篇“怎么在Go语言中实现锁机制”文章能帮助大家解决疑惑,下面...
今天小编给大家分享一下Go语言中interface类型怎么使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考