Laravel 接入微信服务商分账接口

记录一个用户微信支付后立即分账的功能
开发步骤
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)
    {

        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;
    }

结语

微信分账文档地址
pay.weixin.qq.com/wiki/doc/api/all...

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 3

为什么请求分账的时候子商户id没有传呢,我看文档上说是必传的啊

4年前 评论
liang986 (楼主) 4年前

支付成功的回调中不应该有长时间执行的任务,如果有应将其异步化操作

4年前 评论
liang986 (楼主) 4年前

你这了:$app->order->unify 使用的是easywechat么

2年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!