专栏首页Golang语言社区golang微信支付服务端

golang微信支付服务端

一般来说,使用golang主要还是写服务端。所以本文主要讲golang在处理微信移动支付的服务端时的统一下单接口和支付回调接口,以及查询接口。

微信支付流程

下图是微信官网的支付流程描述:

图中红色部分就是微信支付中,我们的系统包括app,后台需要参与的流程。 其中需要后台也就是Server需要参与的流程有三个: 1. 统一下单并返回客户端 2. 异步通知结果回调处理 3. 调用微信支付查询接口

微信所有的接口都是以http RESTFul的API来提供,所以对于server而言其实就是call这些接口并处理返回值。

调用统一下单接口

首先需要呼叫:https://api.mch.weixin.qq.com/pay/unifiedorder 这是微信的api,呼叫之后微信会返回我们一个prepay_id。调用的结果以微信正确的返回给我们prepay id为准。

按照微信文档说明,这个接口的参数没有,我们传入的参数需要以xml的形式来写入http request的body部分传给微信。

需要注意的问题有两点,第一个是sign的计算,另一个是golang中xml包很坑,没有提供DOM方式操作xml的接口,marshal后的字串需要手工修改以达到满足微信要求的这种根节点的格式。

//首先定义一个UnifyOrderReq用于填入我们要传入的参数。

type UnifyOrderReq struct {

    Appid            string `xml:"appid"`

    Body             string `xml:"body"`

    Mch_id           string `xml:"mch_id"`

    Nonce_str        string `xml:"nonce_str"`

    Notify_url       string `xml:"notify_url"`

    Trade_type       string `xml:"trade_type"`

    Spbill_create_ip string `xml:"spbill_create_ip"`

    Total_fee        int    `xml:"total_fee"`

    Out_trade_no     string `xml:"out_trade_no"`

    Sign             string `xml:"sign"`

}



//微信支付计算签名的函数

func wxpayCalcSign(mReq map[string]interface{}, key string) (sign string) {

    fmt.Println("微信支付签名计算, 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 {

        fmt.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([]byte(signStrings))

    cipherStr := md5Ctx.Sum(nil)

    upperSign := strings.ToUpper(hex.EncodeToString(cipherStr))

    return upperSign

}

统一下单接口调用的范例:

//请求UnifiedOrder的代码

    var yourReq UnifyOrderReq

    yourReq.Appid = "app_id" //微信开放平台我们创建出来的app的app id

    yourReq.Body = "商品名"

    yourReq.Mch_id = "商户编号"

    yourReq.Nonce_str = "your nonce"

    yourReq.Notify_url = "www.yourserver.com/wxpayNotify"

    yourReq.Trade_type = "APP"

    yourReq.Spbill_create_ip = "xxx.xxx.xxx.xxx"

    yourReq.Total_fee = 10 //单位是分,这里是1毛钱

    yourReq.Out_trade_no = "后台系统单号"



    var m map[string]interface{}

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

    m["appid"] = yourReq.Appid

    m["body"] = yourReq.Body

    m["mch_id"] = yourReq.Mch_id

    m["notify_url"] = yourReq.Notify_url

    m["trade_type"] = yourReq.Trade_type

    m["spbill_create_ip"] = yourReq.Spbill_create_ip

    m["total_fee"] = yourReq.Total_fee

    m["out_trade_no"] = yourReq.Out_trade_no

    m["nonce_str"] = yourReq.Nonce_str

    yourReq.Sign = wxpayCalcSign(m, "wxpay_api_key") //这个是计算wxpay签名的函数上面已贴出



    bytes_req, err := xml.Marshal(yourReq)

    if err != nil {

        fmt.Println("以xml形式编码发送错误, 原因:", err)

        return

    }



    str_req := string(bytes_req)

    //wxpay的unifiedorder接口需要http body中xmldoc的根节点是<xml></xml>这种,所以这里需要replace一下

    str_req = strings.Replace(str_req, "UnifyOrderReq", "xml", -1)

    bytes_req = []byte(str_req)



    //发送unified order请求.

    req, err := http.NewRequest("POST", unify_order_req, bytes.NewReader(bytes_req))

    if err != nil {

        fmt.Println("New Http Request发生错误,原因:", err)

        return

    }

    req.Header.Set("Accept", "application/xml")

    //这里的http header的设置是必须设置的.

    req.Header.Set("Content-Type", "application/xml;charset=utf-8")



    c := http.Client{}

    resp, _err := c.Do(req)

    if _err != nil {

        fmt.Println("请求微信支付统一下单接口发送错误, 原因:", _err)

        return

    }



    //到这里统一下单接口就已经执行完成了

接下来就是微信统一下单接口的响应,首先定义解析微信返回的response的数据结构。然后就是标准的http response的处理流程。其中我们需要使用的主要还是他的prepay id,拿到prepay id,服务端需完成的支付流程就基本完毕,将prepay id给客户端继续支付流程。

type UnifyOrderResp struct {

        Return_code string `xml:"return_code"`

        Return_msg  string `xml:"return_msg"`

        Appid       string `xml:"appid"`

        Mch_id      string `xml:"mch_id"`

        Nonce_str   string `xml:"nonce_str"`

        Sign        string `xml:"sign"`

        Result_code string `xml:"result_code"`

        Prepay_id   string `xml:"prepay_id"`

        Trade_type  string `xml:"trade_type"`

    }



    xmlResp := UnifyOrderResp{}

    _err = xml.Unmarshal(body, &xmlResp)

    //处理return code.

    if xmlresp.Return_code == "FAIL" {

        fmt.Println("微信支付统一下单不成功,原因:", xmlresp.Return_msg)

        return

    }



    //这里已经得到微信支付的prepay id,需要返给客户端,由客户端继续完成支付流程

    fmt.Println("微信支付统一下单成功,预支付单号:", xmlResp.Prepay_id)

微信异步通知的处理

在微信支付的流程图中,当客户端支付完成以后,微信会异步的来通知商户后台系统对支付结果进行一次更新,或更新数据库,或通知客户端,根据你的业务来定。 回调函数实际上就是我们在第一步统一下单接口里设置的回调函数。

yourReq.Notify_url = "www.yourserver.com/wxpayNotify"

就是这里设置的这个地址,这个地址指向我们后台的一个接口(其他语言就是页面),当支付的结果变化时,微信会异步来透过这个接口通知我们支付的结果。

在处理上,主要是针对他的签名的一个检查。在有了第一步计算签名函数wxpayCalcSign的基础上这个签名检查就很简单了,直接针对微信异步通知的请求,计算一次签名(不含他请求的签名,不含空串),然后比对微信返回的签名和他的异步通知的签名是否是一致的就可以。

微信异步通知的数据结构,他也是以xml形式包含在请求的body中,解出来即可。

type WXPayNotifyReq struct {

    Return_code    string `xml:"return_code"`

    Return_msg     string `xml:"return_msg"`

    Appid          string `xml:"appid"`

    Mch_id         string `xml:"mch_id"`

    Nonce          string `xml:"nonce_str"`

    Sign           string `xml:"sign"`

    Result_code    string `xml:"result_code"`

    Openid         string `xml:"openid"`

    Is_subscribe   string `xml:"is_subscribe"`

    Trade_type     string `xml:"trade_type"`

    Bank_type      string `xml:"bank_type"`

    Total_fee      int    `xml:"total_fee"`

    Fee_type       string `xml:"fee_type"`

    Cash_fee       int    `xml:"cash_fee"`

    Cash_fee_Type  string `xml:"cash_fee_type"`

    Transaction_id string `xml:"transaction_id"`

    Out_trade_no   string `xml:"out_trade_no"`

    Attach         string `xml:"attach"`

    Time_end       string `xml:"time_end"`

}



type WXPayNotifyResp struct {

    Return_code string `xml:"return_code"`

    Return_msg  string `xml:"return_msg"`

}



//具体的微信支付回调函数的范例

func WxpayCallback(w http.ResponseWriter, r *http.Request) {

    // body

    body, err := ioutil.ReadAll(r.Body)

    if err != nil {

        fmt.Println("读取http body失败,原因!", err)

        http.Error(w.(http.ResponseWriter), http.StatusText(http.StatusBadRequest), http.StatusBadRequest)

        return

    }

    defer r.Body.Close()



    fmt.Println("微信支付异步通知,HTTP Body:", string(body))

    var mr WXPayNotifyReq

    err = xml.Unmarshal(body, &mr)

    if err != nil {

        fmt.Println("解析HTTP Body格式到xml失败,原因!", err)

        http.Error(w.(http.ResponseWriter), http.StatusText(http.StatusBadRequest), http.StatusBadRequest)

        return

    }



    var reqMap map[string]interface{}

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



    reqMap["return_code"] = mr.Return_code

    reqMap["return_msg"] = mr.Return_msg

    reqMap["appid"] = mr.Appid

    reqMap["mch_id"] = mr.Mch_id

    reqMap["nonce_str"] = mr.Nonce

    reqMap["result_code"] = mr.Result_code

    reqMap["openid"] = mr.Openid

    reqMap["is_subscribe"] = mr.Is_subscribe

    reqMap["trade_type"] = mr.Trade_type

    reqMap["bank_type"] = mr.Bank_type

    reqMap["total_fee"] = mr.Total_fee

    reqMap["fee_type"] = mr.Fee_type

    reqMap["cash_fee"] = mr.Cash_fee

    reqMap["cash_fee_type"] = mr.Cash_fee_Type

    reqMap["transaction_id"] = mr.Transaction_id

    reqMap["out_trade_no"] = mr.Out_trade_no

    reqMap["attach"] = mr.Attach

    reqMap["time_end"] = mr.Time_end



    var resp WXPayNotifyResp

    //进行签名校验

    if wxpayVerifySign(reqMap, mr.Sign) {

        //这里就可以更新我们的后台数据库了,其他业务逻辑同理。

        resp.Return_code = "SUCCESS"

        resp.Return_msg = "OK"

    } else {

        resp.Return_code = "FAIL"

        resp.Return_msg = "failed to verify sign, please retry!"

    }



    //结果返回,微信要求如果成功需要返回return_code "SUCCESS"

    bytes, _err := xml.Marshal(resp)

    strResp := strings.Replace(string(bytes), "WXPayNotifyResp", "xml", -1)

    if _err != nil {

        fmt.Println("xml编码失败,原因:", _err)

        http.Error(w.(http.ResponseWriter), http.StatusText(http.StatusBadRequest), http.StatusBadRequest)

        return

    }



    w.(http.ResponseWriter).WriteHeader(http.StatusOK)

    fmt.Fprint(w.(http.ResponseWriter), strResp)

}

微信签名验证函数,先针对微信回调的参数不含sign,做一次签名,api_key就是商户平台的api key。然后再比对通过我们的签名计算函数wxpayCalcSign和微信异步通知的签名是否是一致的就可以了。

//微信支付签名验证函数

func wxpayVerifySign(needVerifyM map[string]interface{}, sign string) bool {

    signCalc := wxpayCalcSign(needVerifyM , "API_KEY")



    slog.Debug("计算出来的sign: %v", signCalc)

    slog.Debug("微信异步通知sign: %v", sign)

    if sign == signCalc {

        fmt.Println("签名校验通过!")

        return true

    }



    fmt.Println("签名校验失败!")

    return false

}

客户端查询订单请求响应

因微信端并不能保证异步通知是一定送达商户服务端,因此这里需要进行主动查询订单状态。 https://api.mch.weixin.qq.com/pay/orderquery 这里是微信的查询接口。 当然访问这个接口也很简单,将我们的系统单号,第一步的out_trade_no用作查询条件传入即可查到订单的当前状态。

签名依旧使用我们之前的签名计算函数来完成即可。

代码此处略过,没啥好讲的。

后记

这里只是一个golang的例子,不过其他语言和平台应该是类似的。 例子中基本上传入的参数,需要替换为您对应的正确的参数就可以。 范例中只包含于微信支付服务端沟通的API调用部分,商户平台因为各自不同业务逻辑我就省略了。

本文分享自微信公众号 - Golang语言社区(Golangweb)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2017-06-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Go和HTTPS--1

    近期在构思一个产品,考虑到安全性的原因,可能需要使用到HTTPS协议以及双向数字证书校验。之前只是粗浅接触过HTTP(使用Golang开 发微信系列)。对HTT...

    李海彬
  • 游戏思维开发社区问答系统的感受

    大家好,最近在开发社区的问答系统,公众账号文章由于志愿者相继有事情,近期就更新的比较少,请大家见谅。本篇给大家讲讲社区系统的部分功能说明,后面必然开源,。

    李海彬
  • 配置文件热加载的go语言实现

    通常我们更新应用程序的配置文件,都需要手动重启程序或手动重新加载配置。假设一组服务部署在10台机器上,你需要借助批量运维工具执行重启命令,而且10台同时重启可能...

    李海彬
  • AS1.0(2.0)中的XML示例

    虽然Flash早就升级为AS3.0,但是FMS的服务端编程依然仅支持AS1.0(2.0),服务端与.net通讯的最简单方式莫过于请求一个RESTful的webS...

    菩提树下的杨过
  • mapbox GL台风路径的播放实现

    前面的文章中写了基于openlayers4的台风路径播放,最近用到mapbox GL,也要实现相似的功能,网上找了好久都没有找到,于是就放弃了“拿来主义”的想法...

    lzugis
  • .NET Core的文件系统[5]:扩展文件系统构建一个简易版“云盘”

    FileProvider构建了一个抽象文件系统,作为它的两个具体实现,PhysicalFileProvider和EmbeddedFileProvider则分别为...

    蒋金楠
  • 从0.5到1写个rpc框架 - 2:远程服务调用(grpc)

    gRPC是Google开源的跨语言远程服务调用(RPC)框架,通信协议用的HTTP/2,数据传输默认用的protocol buffers(一种轻便高效的结构化数...

    acupt
  • 这算是ASP.NET MVC的一个大BUG吗?

    这是昨天一个同事遇到的问题,我觉得这是一个蛮大的问题,而且不像是ASP.NET MVC的设计者有意为之,换言之,这可能是ASP.NET MVC的一个Bug(不过...

    蒋金楠
  • C# 基础知识系列- 14 IO篇 文件的操作(2)

    除了上文提到的 GetDirectories 方法可以直接返回目录下所有子目录以外,还有一组方法也可以枚举出当前目录下的子目录:

    程序员小高
  • 细说Golang的JSON解析

    之前一直写一些动态语言,觉得解析JOSN还是很简单的,往往只需要几行代码就能拿到解析好的JSON对象。Go语言自带的json包可以让您在程序中方便的读取和写入 ...

    mojocn

扫码关注云+社区

领取腾讯云代金券