企业付款至用户微信支付零钱或银行卡

本文主要讲 微信企业付款到用户零钱的接口:

  • 应用场景
  • 代码
  • 提醒
  • 总结

应用场景

二次开发红包,用户抢到红包金额,需要通过企业付款到用户的零钱包或者直接打到用户的卡。分销提成 提现。以及给用户退款等。

代码

代码块

我这边提取了两个重要的文件,一个是参数封装service.go:

package services
import (
    "encoding/xml"
    "io/ioutil"
    "net/url"
    "strconv"
    "wechat-pay/utils"
)
var transfers = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers"
//付款订单
type WithdrawOrder struct {
    XMLName        xml.Name `xml:"xml"`
    MchAppid       string   `xml:"mch_appid"`
    Mchid          string   `xml:"mchid"`
    DeviceInfo     string   `xml:"device_info"`
    NonceStr       string   `xml:"nonce_str"`
    Sign           string   `xml:"sign"`
    PartnerTradeNo string   `xml:"partner_trade_no"`
    Openid         string   `xml:"openid"`
    CheckName      string   `xml:"check_name"`
    Amount         int      `xml:"amount"`
    Desc           string   `xml:"desc"`
    SpbillCreateIp string   `xml:"spbill_create_ip"`
}
//付款订单结果
type WithdrawResult struct {
    ReturnCode     string `xml:"return_code"`
    ReturnMsg      string `xml:"return_msg"`
    ResultCode     string `xml:"result_code"`
    ErrCodeDes     string `xml:"err_code_des"`
    PaymentNo      string `xml:"payment_no"`
    PartnerTradeNo string `xml:"partner_trade_no"`
}
//付款,成功返回自定义订单号,微信订单号,true,失败返回错误信息,false
func WithdrawMoney(appid, mchid, openid, amount, partnerTradeNo, desc, clientIp string) (string, string, bool) {
    order := WithdrawOrder{}
    order.MchAppid = appid
    order.Mchid = mchid
    order.Openid = openid
    order.Amount, _ = strconv.Atoi(amount)
    order.Desc = desc
    order.PartnerTradeNo = partnerTradeNo
    order.DeviceInfo = "WEB"
    order.CheckName = "NO_CHECK" //NO_CHECK:不校验真实姓名 FORCE_CHECK:强校验真实姓名
    order.SpbillCreateIp = clientIp
    order.NonceStr = utils.GetRandomString(32)
    order.Sign = md5WithdrawOrder(order)
    xmlBody, _ := xml.MarshalIndent(order, " ", " ")
    resp, err := utils.SecurePost(transfers, xmlBody)
    if err != nil {
        return err.Error(), "", false
    }
    defer resp.Body.Close()
    bodyByte, _ := ioutil.ReadAll(resp.Body)
    var res WithdrawResult
    xmlerr := xml.Unmarshal(bodyByte, &res)
    if xmlerr != nil {
        return xmlerr.Error(), "", false
    }
    if res.ReturnCode == "SUCCESS" && res.ResultCode == "SUCCESS" {
        return res.PartnerTradeNo, res.PaymentNo, true
    }
    return res.ReturnMsg, res.ErrCodeDes, false
}
//md5签名
func md5WithdrawOrder(order WithdrawOrder) string {
    o := url.Values{}
    o.Add("mch_appid", order.MchAppid)
    o.Add("mchid", order.Mchid)
    o.Add("device_info", order.DeviceInfo)
    o.Add("partner_trade_no", order.PartnerTradeNo)
    o.Add("check_name", order.CheckName)
    o.Add("amount", strconv.Itoa(order.Amount))
    o.Add("spbill_create_ip", order.SpbillCreateIp)
    o.Add("desc", order.Desc)
    o.Add("nonce_str", order.NonceStr)
    o.Add("openid", order.Openid)
    r, _ := url.QueryUnescape(o.Encode())
    return utils.Md5([]byte(r + "&key=" + KEY))
}

另一个工具安全请求,上面需要用到的,携带ca证书util.go:

package utils
import (
    "bytes"
    "crypto/tls"
    "crypto/x509"
    "io/ioutil"
    "net/http"
)
//ca证书的位置,需要绝对路径
var (
    wechatCertPath = `/home/app/wechatpayserver/cert/cert.pem`
    wechatKeyPath  = `/home/app/wechatpayserver/cert/key.pem`
    wechatCAPath   = `/home/app/wechatpayserver/cert/rootca.pem`
)
var _tlsConfig *tls.Config
//采用单例模式初始化ca
func getTLSConfig() (*tls.Config, error) {
    if _tlsConfig != nil {
        return _tlsConfig, nil
    }
    // load cert
    cert, err := tls.LoadX509KeyPair(wechatCertPath, wechatKeyPath)
    if err != nil {
        return nil, err
    }
    // load root ca
    caData, err := ioutil.ReadFile(wechatCAPath)
    if err != nil {
        return nil, err
    }
    pool := x509.NewCertPool()
    pool.AppendCertsFromPEM(caData)
    _tlsConfig = &tls.Config{
        Certificates: []tls.Certificate{cert},
        RootCAs:      pool,
    }
    return _tlsConfig, nil
}
//携带ca证书的安全请求
func SecurePost(url string, xmlContent []byte) (*http.Response, error) {
    tlsConfig, err := getTLSConfig()
    if err != nil {
        return nil, err
    }
    tr := &http.Transport{TLSClientConfig: tlsConfig}
    client := &http.Client{Transport: tr}
    return client.Post(
        url,
        "application/xml",
        bytes.NewBuffer(xmlContent))
}

提醒

这里的付款的钱,都是需要充值进去的,并不是用户付款的金额。用户的付款的金额,只能用来退款操作,不能用户企业付款用户零钱和用户银行卡等。用个人银行卡充值一般5个自然日,企业需要14个自然日。所以测试的时候需要提前充值金额。不要以为用户付款很多了,直接可以用哪些钱去测,测不了的。

大多数人做的微信支付比较多了,有些方法自然而然的就在了。往往以为同一家公司,同一个值的字段肯定是相同,就是因为这样,方法直接复制来复制去的,典型的搬砖工。结果,砖头砸脚了。本来就是因为直接复制签名代码,也没怎么仔细看,appid已经变成了mch_appid,mch_id 变成了mchid,虽然我很搞不懂,咋要去变呢,但毕竟人家是大佬,跟着走吧。签名报错的时候,多对对字段是否有所改变。

总结

  1. 整体代码不是很难,主要是ca证书需要自己封装一个请求。
  2. 签名需要注意,空格和encode这些操作。
  3. 最好不要encode,因为微信会原样显示出来。