Laravel 接入微信服務商分賬介面

liang986發表於2020-03-02

記錄一個使用者微信支付後立即分賬的功能
開發步驟
Laravel接入微信服務商分賬介面

一.新增分賬接收方

    //    新增分賬接收方
    public function add_sub(User $user)
    {
        header("Content-Type:text/html;charset=utf8");
        $open_id = $user->open_id;
        $request_para = array();
        $request_para['mch_id'] = env('MCH_ID');// 商戶號
        $request_para['appid'] = env('APP_ID');// 公眾賬號ID
        $request_para['nonce_str'] = $this->createNoncestr();// 隨機字串
        $request_para['sign_type'] = "HMAC-SHA256";// 簽名型別
        $receiver = array(
            //                MERCHANT_ID:商戶ID
            //                PERSONAL_WECHATID:個人微訊號
            //                PERSONAL_OPENID:個人openid
            'type' => 'PERSONAL_OPENID',
            'account' => $open_id,
            'relation_type' => 'STAFF'
        );

        $request_para["receiver"] = json_encode($receiver);
        // 簽名
        $request_para["sign"] = $this->appgetSign($request_para, env('MCH_KEY'), false);
        //把陣列轉化成xml格式
        $xmlData = $this->arrayToXml($request_para, true);

        $url = 'https://api.mch.weixin.qq.com/pay/profitsharingaddreceiver';
        //傳送請求
        $get_data = $this->sendPrePayCurl($xmlData, $url);
        \Log::alert('新增分賬接收方返回結果:');
        \Log::alert($get_data);
    }

    //隨機字串
    public function createNoncestr($length = 32)
    {
        $chars = "abcdefghijklmnopqrstuvwxyz0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }

    //格式化引數格式化成url引數  生成簽名sign
    public function appgetSign($Obj, $appwxpay_key, $bool = true)
    {
        foreach ($Obj as $k => $v) {
            $Parameters[$k] = $v;
        }

        //簽名步驟一:按字典序排序引數
        ksort($Parameters); // ksort — 對陣列按照鍵名排序
        $String = $this->formatBizQueryParaMap($Parameters, false);

        //簽名步驟二:在string後加入KEY
        if ($appwxpay_key) {
            $String = $String . "&key=" . $appwxpay_key;
        }

        if ($bool) {
            //簽名步驟三:MD5加密
            $String = md5($String);
        } else {
            //HMAC-SHA256簽名方式
            $String = hash_hmac("sha256", $String, $appwxpay_key);
        }

        //簽名步驟四:所有字元轉為大寫
        $result_ = strtoupper($String);
        return $result_;
    }

    //按字典序排序引數
    public function formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v) {
            if ($urlencode) {
                $v = urlencode($v); //urlencode — 編碼 URL 字串
            }
            //$buff .= strtolower($k) . "=" . $v . "&";
            $buff .= $k . "=" . $v . "&";
        }
        $reqPar = '';
        if (strlen($buff) > 0) {
            $reqPar = substr($buff, 0, strlen($buff) - 1);
        }
        return $reqPar;
    }

    //將陣列轉換為xml格式
    public function arrayToXml($arr, $bool)
    {
        $xml = "<xml>";
        foreach ($arr as $key => $val) {
            if ($bool) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else{
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }

    //傳送請求
    public function sendPrePayCurl($xml, $url = '', $options = array())
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        if (!empty($options)) {
        curl_setopt_array($ch, $options);
        }
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        $data = curl_exec($ch);
        curl_close($ch);

        $data_xml_arr = $this->XMLDataParse($data);
        if ($data_xml_arr) {
            return $data_xml_arr;
        } else {
            return false;
        }
    }
    //xml格式資料解析函式
    public function XMLDataParse($data)
    {
        $xml = simplexml_load_string($data, NULL, LIBXML_NOCDATA);
        $array = json_decode(json_encode($xml), true);
        return $array;
    }

二.在統一下單API新增profit_sharing引數

    $result = $app->order->unify([
        'body'         => '支付人民幣' . $money . '元',
        'out_trade_no' => $order_no,
        'total_fee'    => $total_fee,
        'notify_url'   => env('BACKURL'),
        'trade_type'   => 'JSAPI', 
        'openid'       => $open_id,
        'attach'       => $attach,
        'profit_sharing' => 'Y', 
        // Y-是,需要分賬    N-否,不分賬    字母要求大寫,不傳預設不分賬
    ]);

三.支付完成後,呼叫請求分賬介面,完成分賬

支付成功後,在回撥地址請求單次分賬

    public function sub_account($transaction_id,$order,$type)
    {
        sleep(2);
        header("Content-Type:text/html;charset=utf8");

        $newPara = array();
        // 公眾賬號ID
        $newPara["appid"] = env('APP_ID');
        // 商戶號
        $newPara["mch_id"] = env('MCH_ID');
        // 隨機字串
        $newPara["nonce_str"] = $this->createNoncestr();
        // 商戶分賬單號
        $out_order_no = mt_rand(100000,999999).time().mt_rand(100,999);
        $newPara["out_order_no"] = $out_order_no;

        // 微信訂單號
        $newPara["transaction_id"] = $transaction_id;

        //簽名型別 HMAC-SHA256
        $newPara["sign_type"] = "HMAC-SHA256";

        $user = User::where('is_sub',1)->select(['id','open_id','proportion'])->get();
        // 分賬接收方列表
        $receivers = [];
        foreach ($user as $k => $val) {
            if($order->money >= 1){
                //  扣除0.6%微信手續費
                $money = floor($order->money * 0.994 * $val->proportion); // 捨去法取整
            }else{
                $money = floor($order->money * $val->proportion); // 捨去法取整
            }
            $receivers[$k]['type'] = 'PERSONAL_OPENID';
            $receivers[$k]['account'] = $val->open_id;
            $receivers[$k]['amount'] = $money;
            $receivers[$k]['description'] = '';
        }

        $newPara["receivers"] = json_encode($receivers);
        // 簽名
        $newPara["sign"] = $this->appgetSign($newPara, env('MCH_KEY'), false);
        //把陣列轉化成xml格式
        $xmlData = $this->arrayToXml($newPara, true);

        $options = array(
            CURLOPT_SSLCERT => '/www/wwwroot/supliao/cert/apiclient_cert.pem',// 客戶端證照,用於雙向認證
            CURLOPT_SSLCERTTYPE => 'PEM',// 證照的型別。支援的格式有"PEM" (預設值), "DER"和"ENG"。
            CURLOPT_SSLKEY => '/www/wwwroot/supliao/cert/apiclient_key.pem',// 客戶端私鑰的檔案路徑
            CURLOPT_SSLKEYTYPE => 'PEM',// 客戶端私鑰型別,支援的私鑰型別為"PEM"(預設值)、"DER"和"ENG"。
            CURLOPT_KEYPASSWD => env('MCH_ID'),//客戶端私鑰密碼,私鑰在建立時可以選擇加密。
            //API證照呼叫或安裝需要使用到密碼,該密碼的值為微信商戶號(mch_id)
        );

        $url = "https://api.mch.weixin.qq.com/secapi/pay/profitsharing";
        $get_data = $this->sendPrePayCurl($xmlData, $url, $options);
        \Log::alert('分賬返回結果:');
        \Log::alert($get_data);

        //更新訂單資料
        return true;
    }

四.踩過的坑

公司的要求是使用者支付成功後馬上分賬,於是我選擇在微信支付回撥成功後請求單次分賬,結果發現微信一直在傳送通知,檢視文件發現請求分賬後商戶對微信的通知已經超時,微信會判定本次通知失敗,一直髮送通知

於是我用了個不是方法的方法,在單次請求分賬方法加上sleep(2),讓程式碼沉睡,等到微信第二次通知再執行。
如果小夥伴們有什麼方法能非同步處理,歡迎在評論區討論

結語

微信分賬文件地址
https://pay.weixin.qq.com/wiki/doc/api/all...

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章