微信商戶平臺【現金紅包】和【企業支付】的一些總結

1、背景介紹php

項目中須要開發一個經過微信紅包提現的功能,調查一下,目前已經簡單實現了功能。如今總結一下開發過程當中遇到的一些問題。html

紅包提現有兩種場景:數據庫

場景一:使用微信的【現金紅包】功能  https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_1小程序

好處:不用本身開發紅包功能,直接調用微信的API就能夠完成紅包的發放,只要用戶關注公衆號具備了惟一可識別的OpenId,而且公衆號與商戶平臺綁定,就能夠發放紅包給用戶,而且在公衆號內會顯示紅包的圖片,點擊便可領取。微信小程序

壞處:必需要關注公衆號,若是是微信小程序或者其餘應用想要發放紅包,並且並不想讓用戶先關注公衆號,這時候這種場景就不適合了。api

 

場景二:使用微信的【企業付款】功能  https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_1數組

好處:不須要用戶關注公衆號,只要用戶登陸微信小程序生成了一個惟一的OpenId,而且小程序和商戶平臺進行了綁定,就能夠經過OpenId付款給用戶,而且直接進入到用戶的零錢當中。安全

壞處:若是想讓用戶有點擊紅包的快感,須要本身在小程序或者APP內實現,有必定的開發成本微信

 

下面我就簡單介紹一下兩種方式是如何實現的。cookie

 

2、實現

1. 準備工做

①須要一個或多個測試用的微信帳號,這個本身註冊或者向別人索要就好了。

②須要一個公衆號或者小程序(換句話說,須要他們的wxAppId)

③須要一個商戶平臺,申請的話比較繁瑣,能夠查看相關的文檔進行。

④商戶平臺須要開通【現金紅包】、【企業支付功能】這個很是重要。

 

2.流程

過程1:首先用戶須要在微信公衆號進行關注或者登陸微信小程序,此時咱們能夠得到到用戶的OpenId,而後將該用戶的OpenId、WeAppId以及咱們本身應用的用戶惟一標識進行保存。

過程2:用戶在咱們本身的應用或者小程序中領取紅包,首先根據上面過程1綁定的關係,可以得到該用戶的OpenId,而後就能夠調用微信商戶平臺的接口發放紅包或者零錢了,具體的實現下面會詳細說。

3、編碼(代碼作過刪減,只保留的一些關鍵方法)

有了基本的流程和微信的接口文檔,咱們就能夠去實現這個紅包功能了,首先以微信【現金紅包-普通紅包】爲例。

①對於微信紅包而言,咱們能夠將一些不變的信息做爲配置信息保存到配置文件當中,其餘的信息例如祝福語、數量、金額等等均可以在做爲請求的參數。例以下面的配置文件:

其中有幾項是在咱們發送紅包構建發放的參數的時候使用的,

I.商戶支付的密鑰PayKey:是在構建參數sign的時候加在字符串末尾的,這個後面會講到。這個的設置是在商戶後臺【key設置路徑:微信商戶平臺(pay.weixin.qq.com)-->帳戶設置-->API安全-->密鑰設置】

II.證書的地址和證書的密碼:這兩個是在構建發放請求參數的時候添加證書時候使用的。此外證書的默認密碼是商戶號。

②構建發放的參數,上面說道構建sign,這個應該是整個調用過程當中的關鍵了,咱們能夠將輸入的參數包裝成類以下:

public class PayRedPack
{
    // 隨機字符串,不長於32位
    public string NonceStr { get; set; }
    // 簽名
    public string Sign { get; set; }
    // 商戶訂單號(每一個訂單號必須惟一)組成:mch_id+yyyymmdd+10位一天內不能重複的數字。
    // 接口根據商戶訂單號支持重入,如出現超時可再調用。
    public string MchBillno { get; set; }
    // 微信支付分配的商戶號
    public string MchId { get; set; }
    // 公衆帳號appid
    public string WxAppid { get; set; }
    // 商戶名稱
    public string SendName { get; set; }
    // 用戶openid
    public string ReOpenId { get; set; }
    // 付款金額 付款金額,單位分
    public int TotalAmount { get; set; }
    //紅包發放總人數
    public int TotalNum { get; set; }
    // 紅包祝福語
    public string Wishing { get; set; }
    // Ip地址
    public string ClientIp { get; set; }
    // 活動名稱
    public string ActName { get; set; }
    // 備註
    public string Remark { get; set; }
    // 場景id
    public string SceneId { get; set; }
    // 活動信息
    public string RiskInfo { get; set; }
    // 資金受權商戶號
    public string ConsumeMchId { get; set; }
}

②構建發放的參數,上面說道構建sign,這個應該是整個調用過程當中的關鍵了,咱們能夠將輸入的參數包裝成類以下:

而後根據必要的信息生成簽名、構建請求的數據

public const string NOTICE_STR = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public const string RANDOM_STR = "0123456789";
//發放紅包待構建的xml數據
public const string SEND_REDPACK_XML_STR = @"<xml>
                                                <mch_billno>{0}</mch_billno>
                                                <mch_id>{1}</mch_id>
                                                <wxappid>{2}</wxappid>
                                                <send_name>{3}</send_name>
                                                <re_openid>{4}</re_openid>
                                                <total_amount>{5}</total_amount>
                                                <total_num>{6}</total_num> 
                                                <wishing>{7}</wishing>
                                                <client_ip>{8}</client_ip>
                                                <act_name>{9}</act_name>
                                                <remark>{10}</remark>
                                                <nonce_str>{11}</nonce_str>
                                                <sign>{12}</sign>
                                            </xml>";

生成請求參數的方法以下:

//生成請求的參數
private string GeneratePostData(SendRedPackInput input, string openId)
{
    string randomNum = ConfigStrHelper.RandomStr(RANDOM_STR, 10);

    //初始化調用微信商戶平臺的輸入參數
    PayRedPack payRedPack = new PayRedPack()
    {
        ActName = input.ActName,
        ClientIp = input.ClientIp,
        MchId = _weChatSettings.MchId,
        Remark = input.Remark,
        WxAppid = input.WxAppid,
        SendName = input.SenderName,
        Wishing = input.Wishing,
        TotalNum = input.TotalNum,
        ReOpenId = input.OpenId,
        TotalAmount = input.TotalAmount,
        MchBillno = _weChatSettings.MchId + System.DateTime.Now.ToString("yyyyMMdd") + ConfigStrHelper.RandomStr(RANDOM_STR, 10),
        NonceStr = ConfigStrHelper.RandomStr(NOTICE_STR, 16)
    };

    //原始傳入參數數組
    string[] signTemp = { "mch_billno=" + payRedPack.MchBillno,
                          "mch_id=" + payRedPack.MchId,
                          "wxappid=" + payRedPack.WxAppid,
                          "send_name=" + payRedPack.SendName,
                          "re_openid=" + payRedPack.ReOpenId,
                          "total_amount=" + payRedPack.TotalAmount,
                          "total_num=" + payRedPack.TotalNum,
                          "wishing=" + payRedPack.Wishing,
                          "client_ip=" + payRedPack.ClientIp,
                          "act_name=" + payRedPack.ActName,
                          "remark=" + payRedPack.Remark,
                          "nonce_str=" + payRedPack.NonceStr };

    //生成具體的簽名
    var sign = RedPackMakeSign(signTemp, _weChatSettings.PayKey);

    //返回待請求的xml數據
    return string.Format(SEND_REDPACK_XML_STR,
            payRedPack.MchBillno,
            payRedPack.MchId,
            payRedPack.WxAppid,
            payRedPack.SendName,
            payRedPack.ReOpenId,
            payRedPack.TotalAmount,
            payRedPack.TotalNum,
            payRedPack.Wishing,
            payRedPack.ClientIp,
            payRedPack.ActName,
            payRedPack.Remark,
            payRedPack.NonceStr,
            sign
         );
}

裏面涉及到了一些字符串操做和簽名操做的主要方法以下:

//生成隨機長度字符串
public string RandomStr(string str, int length)
{
    var result = new StringBuilder();
    var rd = new Random();
    for (int i = 0; i < length; i++)
    {
        result.Append(str[rd.Next(str.Length)]);
    }
    return result.ToString();
}

//生成簽名
public string RedPackMakeSign(string[] signTemp, string key)
{
    List<string> signList = signTemp.ToList();
    //對signList按照ASCII碼從小到大的順序排序
    signList.Sort();
    var signOld = new StringBuilder("");
    foreach (string temp in signList)
    {
        signOld.Append(temp + "&");
    }
    signOld = new StringBuilder(signOld.ToString().Substring(0, signOld.Length - 1));
    //拼接Key
    signOld.Append("&key=" + key);
    //處理支付簽名
    var sign = SignHelper.CreateSign(signOld.ToString(), SignTypeEnum.MD5, Encoding.UTF8).ToUpper(CultureInfo.InvariantCulture);
    return sign;
}

/// <summary>
/// 嘗試32字符長度md5值,小寫. 舉例: 543d9a4a308856458ebd4dac83f4277a 
/// 輸入的中文字符將使用UTF8編碼計算字節
/// </summary>
/// <param name="strSource"></param>
/// <returns></returns>
public static string GetMd5(string strSource)
{
    System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();

    //獲取密文字節數組, 固定使用UTF8
    var bytResult = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(strSource));
    var strResult = BitConverter.ToString(bytResult).ToLower();

    //BitConverter轉換出來的字符串會在每一個字符中間產生一個分隔符,須要去除掉
    strResult = strResult.Replace("-", "");
    return strResult;
}

③ 最後咱們就能夠將生成的字符串,做爲請求的數據,發送到微信商戶平臺,執行紅包的發放,而後將微信返回的信息在進行進一步的處理。

同發放,咱們能夠將發放結果封裝成類,以下:

    public class PayRedPackResult
    {
        public string return_code { get; set; }
        public string return_msg { get; set; }
        public string sign { get; set; }
        public string result_code { get; set; }
        public string err_code { get; set; }
        public string err_code_des { get; set; }
        public string mch_billno { get; set; }
        public string mch_id { get; set; }
        public string wxappid { get; set; }
        public string re_openid { get; set; }
        public string total_amount { get; set; }
        public string send_listid { get; set; }        
    }

下面是執行發放操做的主要方法

/// <summary>
/// 發送紅包
/// </summary>
/// <param name="input"></param>
public void SendRedPackToUser(SendRedPackToUserInput input)
{
    var encoding = Encoding.UTF8;
    var data = encoding.GetBytes(input.PostData);

    //配置文件中取出待請求的url
    var url = _weChatSettings.RedPackUrl;

    var content = RedPackHttpClinetInvoke(data, encoding, url);
    //反序列化成返回的對象
    var payResult = _readXmlService.Deserialize(typeof(PayRedPackResult), content, nameof(PayRedPackResult)) as PayRedPackResult;

    //發放成功、作一些事情
    if (payResult != null && payResult.result_code == "SUCCESS" && payResult.return_code == "SUCCESS")
    {
        //保存發放記錄 記錄到數據庫當中 或者其餘什麼操做
    }
}


/// <summary>
/// 調用微信接口發送紅包
/// </summary>
/// <param name="data"></param>
/// <param name="encoding"></param>
/// <param name="url"></param>
/// <returns></returns>
private string RedPackHttpClinetInvoke(byte[] data, Encoding encoding, string url)
{
    //CerPath證書路徑
    string certPath = _weChatSettings.CertPath;
    //證書密碼
    string password = _weChatSettings.Password;
    X509Certificate2 cert = new X509Certificate2(certPath, password, X509KeyStorageFlags.MachineKeySet);

    // 設置參數  
    HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
    CookieContainer cookieContainer = new CookieContainer();
    request.CookieContainer = cookieContainer;
    request.AllowAutoRedirect = true;
    request.Method = "POST";
    request.ContentType = "text/xml";
    request.ContentLength = data.Length;
    request.ClientCertificates.Add(cert);
    Stream outstream = request.GetRequestStream();
    outstream.Write(data, 0, data.Length);
    outstream.Close();
    //發送請求並獲取相應迴應數據  
    HttpWebResponse response = request.GetResponse() as HttpWebResponse;
    //直到request.GetResponse()程序纔開始向目標網頁發送Post請求  
    Stream instream = response.GetResponseStream();
    StreamReader sr = new StreamReader(instream, encoding);
    //返回結果網頁(html)代碼  
    string content = sr.ReadToEnd();
    return content;
}

/// <summary>
/// 反序列化
/// </summary>
/// <param name="type">類型</param>
/// <param name="xml">XML字符串</param>
/// <param name="className">類名</param>
/// <returns></returns>
public object Deserialize(Type type, string xml, string className)
{
    xml = xml.Replace("xml", className);
    try
    {
        using (StringReader sr = new StringReader(xml))
        {
            XmlSerializer xmldes = new XmlSerializer(type);
            return xmldes.Deserialize(sr);
        }
    }
    catch (Exception ex)
    {
        throw;
    }
}

以上就是普通紅包的流程,發放成功以後,應該會在公衆號上面收到一個紅包,點擊便可領取對應的零錢。

 

④發放失敗和異常

咱們在調用微信的時候,有些用戶會反饋紅包收到不,此時就要根據微信返回的信息,進行判斷了,有些是咱們的配置沒有配好,有些則是用戶微信帳號的問題。下面總結了幾個常見的問題:

I.發放失敗,此請求可能存在風險,已被微信攔截 (NO_AUTH) : 出現這個問題,多是用戶沒有實名認證,或者剛剛實名不久,不是一個活躍帳號。

II.商戶API證書校驗出錯(CA_ERROR) : 出現這個問題,是因爲你發放的時候使用的Ca文件過時或者不是該商戶的Ca,此時你能夠在商戶平臺上面下載一個最新的替換掉。

下載:微信商戶平臺(pay.weixin.qq.com)-->帳戶中心-->帳戶設置-->API安全 

III.輸入xml參數格式錯誤(XML_ERROR) : 這是你post過去的xml對,我在作企業支付的過程當中發現,騰訊官方提供的文檔上面,也存在錯誤,以下圖mch_id多了一個空格,致使我在請求的時候報這個錯誤

 

 IV: 在作【企業支付】的時候出現了 NO_AUTH,返回的消息是【產品權限驗證失敗,請查看您當前是否具備該產品的權限】: 這個問題是沒有開通企業支付這個功能,開通一下就能夠了。

 V: 有時候咱們【企業支付】發放的金額想要發放小於1元的金額,這個能夠在商戶平臺上面進行設置以下圖所示:產品中心 -> 企業付款到零錢 -> 產品設置

 

 

總結:

1.對於微信的紅包咱們須要一個公衆號去接收紅包,因此上面的AppId必須是一個公衆號的,若是你拿了一個小程序的可能沒有地方用於展現紅包。

這也是關鍵的地方吧,流程上面就是先讓用戶關注,而後綁定本身應用的惟一標識,創建起本身應用用戶的Id和微信公衆號下用戶OpenId的綁定關係,而後經過該openId以及公衆號的微信AppId,發放獎勵。

此外默認的普通紅包金額最低爲1元,若是須要發放1元如下的,能夠經過配置場景來實現。調用的方法和上面無異,只是更加複雜了一些。

 

2.對於咱們本身設計紅包,而後直接給用戶發零錢的方式,咱們能夠採用【企業支付】的方式,這種方式不須要一個接收紅包的地方,對於AppId也不必必須是一個公衆號的。調用方法和上述無異,只是參數和返回值有一些區別。

相關文章
相關標籤/搜索