golang微信支付服务端

Posted Golang语言社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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后的字串需要手工修改以达到满足微信要求的这种根节点的格式。

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

  2. type UnifyOrderReq struct {

  3.     Appid            string `xml:"appid"`

  4.     Body             string `xml:"body"`

  5.     Mch_id           string `xml:"mch_id"`

  6.     Nonce_str        string `xml:"nonce_str"`

  7.     Notify_url       string `xml:"notify_url"`

  8.     Trade_type       string `xml:"trade_type"`

  9.     Spbill_create_ip string `xml:"spbill_create_ip"`

  10.     Total_fee        int    `xml:"total_fee"`

  11.     Out_trade_no     string `xml:"out_trade_no"`

  12.     Sign             string `xml:"sign"`

  13. }


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

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

  16.     fmt.Println("微信支付签名计算, API KEY:", key)

  17.     //STEP 1, 对key进行升序排序.

  18.     sorted_keys := make([]string, 0)

  19.     for k, _ := range mReq {

  20.         sorted_keys = append(sorted_keys, k)

  21.     }


  22.     sort.Strings(sorted_keys)


  23.     //STEP2, 对key=value的键值对用&连接起来,略过空值

  24.     var signStrings string

  25.     for _, k := range sorted_keys {

  26.         fmt.Printf("k=%v, v=%v\n", k, mReq[k])

  27.         value := fmt.Sprintf("%v", mReq[k])

  28.         if value != "" {

  29.             signStrings = signStrings + k + "=" + value + "&"

  30.         }

  31.     }


  32.     //STEP3, 在键值对的最后加上key=API_KEY

  33.     if key != "" {

  34.         signStrings = signStrings + "key=" + key

  35.     }


  36.     //STEP4, 进行MD5签名并且将所有字符转为大写.

  37.     md5Ctx := md5.New()

  38.     md5Ctx.Write([]byte(signStrings))

  39.     cipherStr := md5Ctx.Sum(nil)

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

  41.     return upperSign

  42. }

复制代码

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

  1. //请求UnifiedOrder的代码

  2.     var yourReq UnifyOrderReq

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

  4.     yourReq.Body = "商品名"

  5.     yourReq.Mch_id = "商户编号"

  6.     yourReq.Nonce_str = "your nonce"

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

  8.     yourReq.Trade_type = "APP"

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

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

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


  12.     var m map[string]interface{}

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

  14.     m["appid"] = yourReq.Appid

  15.     m["body"] = yourReq.Body

  16.     m["mch_id"] = yourReq.Mch_id

  17.     m["notify_url"] = yourReq.Notify_url

  18.     m["trade_type"] = yourReq.Trade_type

  19.     m["spbill_create_ip"] = yourReq.Spbill_create_ip

  20.     m["total_fee"] = yourReq.Total_fee

  21.     m["out_trade_no"] = yourReq.Out_trade_no

  22.     m["nonce_str"] = yourReq.Nonce_str

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


  24.     bytes_req, err := xml.Marshal(yourReq)

  25.     if err != nil {

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

  27.         return

  28.     }


  29.     str_req := string(bytes_req)

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

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

  32.     bytes_req = []byte(str_req)


  33.     //发送unified order请求.

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

  35.     if err != nil {

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

  37.         return

  38.     }

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

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

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


  42.     c := http.Client{}

  43.     resp, _err := c.Do(req)

  44.     if _err != nil {

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

  46.         return

  47.     }


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

复制代码

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

  1. type UnifyOrderResp struct {

  2.         Return_code string `xml:"return_code"`

  3.         Return_msg  string `xml:"return_msg"`

  4.         Appid       string `xml:"appid"`

  5.         Mch_id      string `xml:"mch_id"`

  6.         Nonce_str   string `xml:"nonce_str"`

  7.         Sign        string `xml:"sign"`

  8.         Result_code string `xml:"result_code"`

  9.         Prepay_id   string `xml:"prepay_id"`

  10.         Trade_type  string `xml:"trade_type"`

  11.     }


  12.     xmlResp := UnifyOrderResp{}

  13.     _err = xml.Unmarshal(body, &xmlResp)

  14.     //处理return code.

  15.     if xmlresp.Return_code == "FAIL" {

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

  17.         return

  18.     }


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

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

复制代码

微信异步通知的处理

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

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

  • 1

  • 1

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

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

  1. type WXPayNotifyReq struct {

  2.     Return_code    string `xml:"return_code"`

  3.     Return_msg     string `xml:"return_msg"`

  4.     Appid          string `xml:"appid"`

  5.     Mch_id         string `xml:"mch_id"`

  6.     Nonce          string `xml:"nonce_str"`

  7.     Sign           string `xml:"sign"`

  8.     Result_code    string `xml:"result_code"`

  9.     Openid         string `xml:"openid"`

  10.     Is_subscribe   string `xml:"is_subscribe"`

  11.     Trade_type     string `xml:"trade_type"`

  12.     Bank_type      string `xml:"bank_type"`

  13.     Total_fee      int    `xml:"total_fee"`

  14.     Fee_type       string `xml:"fee_type"`

  15.     Cash_fee       int    `xml:"cash_fee"`

  16.     Cash_fee_Type  string `xml:"cash_fee_type"`

  17.     Transaction_id string `xml:"transaction_id"`

  18.     Out_trade_no   string `xml:"out_trade_no"`

  19.     Attach         string `xml:"attach"`

  20.     Time_end       string `xml:"time_end"`

  21. }


  22. type WXPayNotifyResp struct {

  23.     Return_code string `xml:"return_code"`

  24.     Return_msg  string `xml:"return_msg"`

  25. }


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

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

  28.     // body

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

  30.     if err != nil {

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

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

  33.         return

  34.     }

  35.     defer r.Body.Close()


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

  37.     var mr WXPayNotifyReq

  38.     err = xml.Unmarshal(body, &mr)

  39.     if err != nil {

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

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

  42.         return

  43.     }


  44.     var reqMap map[string]interface{}

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


  46.     reqMap["return_code"] = mr.Return_code

  47.     reqMap["return_msg"] = mr.Return_msg

  48.     reqMap["appid"] = mr.Appid

  49.     reqMap["mch_id"] = mr.Mch_id

  50.     reqMap["nonce_str"] = mr.Nonce

  51.     reqMap["result_code"] = mr.Result_code

  52.     reqMap["openid"] = mr.Openid

  53.     reqMap["is_subscribe"] = mr.Is_subscribe

  54.     reqMap["trade_type"] = mr.Trade_type

  55.     reqMap["bank_type"] = mr.Bank_type

  56.     reqMap["total_fee"] = mr.Total_fee

  57.     reqMap["fee_type"] = mr.Fee_type

  58.     reqMap["cash_fee"] = mr.Cash_fee

  59.     reqMap["cash_fee_type"] = mr.Cash_fee_Type

  60.     reqMap["transaction_id"] = mr.Transaction_id

  61.     reqMap["out_trade_no"] = mr.Out_trade_no

  62.     reqMap["attach"] = mr.Attach

  63.     reqMap["time_end"] = mr.Time_end


  64.     var resp WXPayNotifyResp

  65.     //进行签名校验

  66.     if wxpayVerifySign(reqMap, mr.Sign) {

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

  68.         resp.Return_code = "SUCCESS"

  69.         resp.Return_msg = "OK"

  70.     } else {

  71.         resp.Return_code = "FAIL"

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

  73.     }


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

  75.     bytes, _err := xml.Marshal(resp)

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

  77.     if _err != nil {

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

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

  80.         return

  81.     }


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

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

  84. }

复制代码

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

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

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

  3.     signCalc := wxpayCalcSign(needVerifyM , "API_KEY")


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

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

  6.     if sign == signCalc {

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

  8.         return true

  9.     }


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

  11.     return false

  12. }

复制代码

客户端查询订单请求响应

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

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

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

后记

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



以上是关于golang微信支付服务端的主要内容,如果未能解决你的问题,请参考以下文章

pc端微信扫码支付和支付宝在线支付(还未验证 先留着)

ApiPHP开发APP端微信支付功能,学一下吧

golang微信支付服务端

PHP PC端微信扫码支付模式二详细教程-附带源码(转)

golang实现微信支付v2版本

golang实现微信支付v3版本