IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> PHP知识库 -> 超级简单thinkphp微信小程序服务商分账。以及小程序普通支付,微信特约商户 -> 正文阅读

[PHP知识库]超级简单thinkphp微信小程序服务商分账。以及小程序普通支付,微信特约商户

产品介绍
服务商分账,主要用于服务商帮助特约商户完成订单收单成功后的资金分配。

使用场景举例
1、服务商抽成
在各个行业中,服务商为特约商户提供增值服务,服务商与特约商户协商,可以从特约商户的交易流水中抽取一定的手续费。

2、员工奖励
零售、餐饮等行业中,当销售人员销售完成后,达到可奖励的条件,可以通过分账,将销售奖励分给员工。

3、管理资金到账时间
在酒店行业中,利用分账功能中的“冻结/解冻”能力,当用户预订/入住酒店时,交易资金先冻结在酒店的账户中,当用户确认消费离店后,再利用“分账”功能中的“分账完结”解冻资金到酒店的账户中。这样可以避免用户退款时商户账户资金不足的情况。

4、分润给合作伙伴
在与他方合作的情况下,可以用“分账”功能,将交易资金分给合作伙伴,例如物流合作商。

下面就让我们一起写一套微信小程序支付和服务商分账

首先创建逻辑方法

	/**
     * 会员购买
     * @return \think\response\Json
     */
    public function member_pay()
    {
        $type = input('type');//1微信支付 2激活码
        $id = input('id');
        if (!$type) {
            return json(['code' => 2, 'msg' => '参数错误']);
        }
        $member = Member::where(['id' => $this->uid])->find();
        if ($type == 2) {
            $code = input('code');
            if (!$code) {
                return json(['code' => 2, 'msg' => '参数错误']);
            }
            $list = MemberCode::where(['code' => $code])->find();
            if (!$list) {
                return json(['code' => 2, 'msg' => '激活码错误']);
            }
            if ($list['status'] != 1) {
                return json(['code' => 2, 'msg' => '激活码已使用']);
            }
            $memberM = MemberMember::where(['uid'=>$this->uid])->find();
            if (!$memberM){
                $data = [
                    'uid' => $this->uid,
                    'start_time' => date('Y-m-d H:i:s'),
                    'end_time' => date('Y-m-d H:i:s', strtotime("+" . $list['time'] . "month")),
                    'l_id' => $list['id'],
                    'create_time' => date('Y-m-d H:i:s')
                ];
                $res = MemberMember::insert($data);
            }else{
                $member_level = $list['time'];
                if($memberM['end_time']<date('Y-m-d H:i:s')){
                    $memberData = [
                        'end_time'=>date("Y-m-d H:i:s", strtotime("+$member_level months")),
                        'l_id'=>$list['id']
                    ];
                }else{
                    $memberData = [
                        'end_time'=>date("Y-m-d",strtotime("+$member_level month",strtotime($memberM['end_time']))),
                        'l_id'=>$list['id']
                    ];
                }
                MemberMember::where(['uid'=>$this->uid])->update($memberData);
            }

            if ($res) {
                return json(['code' => 1, 'msg' => '激活成功']);
            } else {
                return json(['code' => 2, 'msg' => '激活失败']);
            }
        } else {
//            if ($member['is_member']!=1){
//                return json(['code'=>2,'msg'=>'首次开通会员必须使用激活码']);
//            }
            $list = MemberLevel::where(['id' => $id])->find();
            $order_number = rand(1111, 9999) . date('Ymd') . $this->uid . $id;
            微信支付统一下单
            //参数 用户openid 订单号 备注 金额 是否分账 是否是商家
            $obj = new Wxpay($member['openid'], $order_number, '用户开通会员', $list['money'] * 100, '', '');
            $pay = $obj->pay();//下单获取返回值
            $order = VideoOrder::where(['vid'=>$id,'type'=>1,'uid'=>$this->uid,'status'=>2])->find();
            if ($order){
                $data = [
                    'order_num'=>$order_number,
                    'create_time'=>date('Y-m-d H:i:s'),
                    'price'=>$list['money'],
                ];
                VideoOrder::where(['id'=>$order['id']])->update($data);
            }else{
                $data = [
                    'uid'=>$this->uid,
                    'vid'=>$id,
                    'order_num'=>$order_number,
                    'create_time'=>date('Y-m-d H:i:s'),
                    'price'=>$list['money'],
                    'status'=>2,
                    'type'=>1
                ];
                VideoOrder::insert($data);
            }
            $info['code'] = 1;
            $info['data'] = $pay;
            return json(['code' => 1, 'data' => $info]);
        }
    }

新建微信统一下单类

<?php

namespace app\api\home;
//命名空间 方便在API中调用

/*
 * 小程序微信支付
 */

class Wxpay
{
    protected $appid;
    protected $mch_id;
    protected $key;
    protected $openid;
    protected $out_trade_no;
    protected $body;
    protected $total_fee;

    function __construct($openid, $out_trade_no, $body, $total_fee, $profit_sharing = 'N', $shops = [])
    {

        $this->appid = config('appid');//小程序appid
        $this->openid = $openid; //用户的openid
        // 商户号
        $this->mch_id = config('mch_id'); //商户号  微信商户平台中查找
        // 支付秘钥
        $this->key = config('pay_key'); //商户号的密钥, 微信商户平台设置
        //订单号
        $this->out_trade_no = $out_trade_no; //自己生成的订单号
        // 内容
        $this->body = $body; //支付时显示的提示
        //金额
        $this->total_fee = $total_fee;//使用分为单位的所有要乘100
        $this->profit_sharing = $profit_sharing;//是否分账
        $this->shops = $shops;//是否商家
    }

    public function pay()
    {

        //统一下单接口
        $return = $this->weixinapp();
        return $return;
    }

    //统一下单接口
    private function unifiedorder()
    {

        $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder'; //应该是同步回调地址(不需要更改)
        $shops = $this->shops;
        if ($this->profit_sharing == 'Y') {
          	参数是Y的时候为服务商模式
            $this->key = $shops['sub_key'];
            $parameters = array(
                'appid' => 'wx04060xxxxxx', //服务号的appid
                'mch_id' => '1604xxxxxx', //商户号
                'sub_appid' => $shops['appid'],//特约商户的appid
                'sub_mch_id' => $shops['sub_mch_id'],//特约商户商户号
                'nonce_str' => $this->createNoncestr(), //随机字符串
                'body' => $this->body, //商品描述
                'out_trade_no' => $this->out_trade_no, //商户订单号
                'total_fee' => $this->total_fee, //总金额 单位 分
                'spbill_create_ip' => $_SERVER['REMOTE_ADDR'], //终端IP
                'notify_url' => '你的url/api/index/pay_success', //支付成功回调地址,注意1. 确保外网能正常访问 2.https
                'openid' => $this->openid, //用户id
                'trade_type' => 'JSAPI',//交易类型       //要是返回该产品权限未开通请在产品中心开通jsAPi他包含的小程序支付,
                'profit_sharing' => 'Y'此参数为Y的时候为微信服务商支付
            );
        }else {
        //微信小程序普通支付
            $parameters = array(
                'appid' => $this->appid, //小程序ID
                'mch_id' => $this->mch_id, //商户号
                'nonce_str' => $this->createNoncestr(), //随机字符串
                'body' => $this->body, //商品描述
                'out_trade_no' => $this->out_trade_no, //商户订单号
                'total_fee' => $this->total_fee, //总金额 单位 分
                'spbill_create_ip' => $_SERVER['REMOTE_ADDR'], //终端IP
                'notify_url' => 'https://shoptemplate.rztnhy.com/api/index/pay_success', //支付成功回调地址,注意1. 确保外网能正常访问 2.https
                'openid' => $this->openid, //用户id
                'trade_type' => 'JSAPI'//交易类型       //要是返回该产品权限未开通请在产品中心开通jsAPi他包含的小程序支付
            );

        }

        //统一下单签名
        $parameters['sign'] = $this->getSign($parameters);

        $xmlData = $this->arrayToXml($parameters);
        $return = $this->xmlToArray($this->postXmlCurl($xmlData, $url, 60));
        return $return;
    }


    public function refund($transaction_id, $out_refund_no, $total_fee, $refund_fee)
    {

        //退款参数
        $refundorder = array(
            'appid' => $this->appid,
            'mch_id' => $this->mch_id,
            'nonce_str' => $this->createNoncestr(),
            'transaction_id' => $transaction_id,
            'out_refund_no' => $out_refund_no,
            'total_fee' => $total_fee * 100,
            'refund_fee' => $refund_fee * 100
        );
        $refundorder['sign'] = $this->getSign($refundorder);

        //请求数据,进行退款
        $xmldata = $this->arrayToXml($refundorder);
        $url = 'https://api.mch.weixin.qq.com/secapi/pay/refund';
        $res = $this->postXmlCurl($xmldata, $url, 30, true);
        if (!$res) {
            return array('status' => 0, 'msg' => "Can't connect the server");
        }

        $content = $this->xmlToArray($res);

        if (strval($content['result_code']) == FAIL') {
            return array('status' => 0, 'msg' => strval($content['err_code']) . ':' . strval($content['err_code_des']));
        }
        if (strval($content['return_code']) == 'FAIL') {
            return array('status' => 0, 'msg' => strval($content['return_msg']));
        }


        return array('status' => 1, 'transaction_id' => strval($content['transaction_id']));
    }

    private static function postXmlCurl($xml, $url, $second = 60, $useCert = false)
    {
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); //严格校验
        //设置header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        //要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);

        if ($useCert == true) {
            //设置证书
            //使用证书:cert 与 key 分别属于两个.pem文件
            //证书文件请放入服务器的非web目录下
            $sslCertPath = "./static/cert/apiclient_cert.pem";
            $sslKeyPath = "./static/cert/apiclient_key.pem";
            curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
            curl_setopt($ch, CURLOPT_SSLCERT, $sslCertPath);
            curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
            curl_setopt($ch, CURLOPT_SSLKEY, $sslKeyPath);
        }
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);
        curl_setopt($ch, CURLOPT_TIMEOUT, 40);
        set_time_limit(0);

        //运行curl
        $data = curl_exec($ch);

        //返回结果
        if ($data) {
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            curl_close($ch);
            throw new WxPayException("curl出错,错误码:$error");
        }
    }

    //数组转换成xml
    private function arrayToXml($arr)
    {
        $xml = "<root>";
        foreach ($arr as $key => $val) {
            if (is_array($val)) {
                $xml .= "<" . $key . ">" . $this->arrayToXml($val) . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            }
        }
        $xml .= "</root>";
        return $xml;
    }


    //xml转换成数组
    public function xmlToArray($xml)
    {
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $xmlstring = simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA);
        $val = json_decode(json_encode($xmlstring), true);
        return $val;
    }


    //微信小程序接口
    private function weixinapp()
    {

        //统一下单接口
        $unifiedorder = $this->unifiedorder();


        // halt($unifiedorder);
        // if($unifiedorder['err_code_des'] == '201 商户订单号重复'){
        //   return '
        // }
        $parameters = array(
            'appId' => $this->appid, //小程序ID
            'timeStamp' => '' . time() . '', //时间戳
            'nonceStr' => $this->createNoncestr(), //随机串
            'package' => 'prepay_id=' . $unifiedorder['prepay_id'], //数据包         要是返回201则要修改订单号,这个问题测试中容易出现,上线了基本不出现问题
            'signType' => 'MD5'//签名方式
        );
        //签名
        $parameters['paySign'] = $this->getSign($parameters);

        return $parameters;
    }


    //作用:产生随机字符串,不长于32位
    private 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;
    }


    //作用:生成签名
    private function getSign($Obj)
    {

        foreach ($Obj as $k => $v) {
            $Parameters[$k] = $v;
        }
        //签名步骤一:按字典序排序参数
        ksort($Parameters);

        //签名步骤二:在string后加入KEY

        $String = $this->formatBizQueryParaMap($Parameters, false);

        $String = $String . "&key=" . $this->key;

        //签名步骤三:MD5加密
        $String = md5($String);
        //签名步骤四:所有字符转为大写
        $result_ = strtoupper($String);
        return $result_;
    }


    ///作用:格式化参数,签名过程需要使用
    private function formatBizQueryParaMap($paraMap, $urlencode)
    {
        $buff = "";
        ksort($paraMap);
        foreach ($paraMap as $k => $v) {
            if ($urlencode) {
                $v = urlencode($v);
            }
            $buff .= $k . "=" . $v . "&";
        }
        $reqPar = '';
        if (strlen($buff) > 0) {
            $reqPar = substr($buff, 0, strlen($buff) - 1);
        }
        return $reqPar;
    }
}

支付成功后在回调方法中进行分账

每个项目的回调逻辑不一样。在这我就不写回调逻辑了
一般项目客户在确定收货的时候才触发分账 所以直接在确定收货的接口里使用分账方法。

/**
     * @title 更改订单状态
     * @description 更改订单状态
     * @param name:order_id type:int require:1 default:0 other: desc:订单id
     * @param name:job type:string require:1 default: other: desc:confirm确认接单,arrived送达(也可用于核销),refund退款,cancel取消
     *
     * @return data:true/false
     * @author QQ910297279
     * @url /api/seller/change_state
     * @method GET
     *
     * @header name:Cookie require:1 default: desc:phpsession
     *
     */
public function change_state(){
	$this->profitsharing($shops['appid'],$shops['sub_mch_id'],$order['transaction_id']);
}
public function profitsharing($sub_appid,$sub_mch_id,$transaction_id,$total_price){
        //整理生成签名的参数
        $tmp = array(
                'type'=>'MERCHANT_ID',
                'account'=>'16046XXXXX',//商户号
                'amount'=>$total_price*30,//分账金额*分账百分比30就是百分之三十
                'description'=>'测试使用',
            );
           
            $nonce_str = '3a93c91ff4fd56a610ba9c47146ed8ec';
            $receivers = json_encode($tmp,JSON_UNESCAPED_UNICODE);
        $postArr = array(
                'appid' => 'wx5acb4562dxxxxx', //服务号的appid
                'mch_id' => '16046xxxxx', //商户号 
                'sub_appid'=>$sub_appid,//特约商户appid
                'sub_mch_id' => $sub_mch_id,//特约商户商户号
                'nonce_str'=>$nonce_str,
                'transaction_id'=>$transaction_id,//微信订单号
                'out_order_no'=>rand(1,100).rand(1000,9999),
                'receivers'=>$receivers
            );
            
            $sign = $this->getSign($postArr, 'HMAC-SHA256','密钥');
           
            $postArr['sign'] = $sign;
            $url = 'https://api.mch.weixin.qq.com/secapi/pay/profitsharing';///分账url
           
            $postXML = $this->toXml($postArr);
            
            $curl_res = $this->post($url,$postXML);
           
            return $curl_res;
    }

	
	
    public function getSign(array $param, $signType = 'MD5', $md5Key)
    {
      
        $values = $this->paraFilter($param);
        
       
        $values = $this->arraySort($values);
        
       
        $signStr = $this->createLinkstring($values);
        
        
        $signStr .= '&key=' . $md5Key;
        // var_dump($signStr);
        // die;
        switch ($signType)
        {
            case 'MD5':
                $sign = md5($signStr);
                break;
            case 'HMAC-SHA256':
                $sign = hash_hmac('sha256', $signStr, $md5Key);
                break;
            default:
                $sign = '';
        }
        
        return strtoupper($sign);
    }
    
    public function paraFilter($para)
    {
        $paraFilter = array();
        while (list($key, $val) = each($para))
        {
            if ($val == "") {
                continue;

            } else {
                if (! is_array($para[$key])) {
                    $para[$key] = is_bool($para[$key]) ? $para[$key] : trim($para[$key]);
                }

                $paraFilter[$key] = $para[$key];
            }
        }
        return $paraFilter;
    }


    /**
     * @function 对输入的数组进行字典排序
     * @param array $param 需要排序的数组
     * @return array
     * @author helei
     */
    public function arraySort(array $param)
    {
        ksort($param);
        reset($param);
        return $param;
    }


    /**
     * @function 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
     * @param array $para 需要拼接的数组
     * @return string
     * @throws \Exception
     */
    public function createLinkString($para)
    {
        if (! is_array($para)) {
            throw new \Exception('必须传入数组参数');
        }

        reset($para);
        $arg  = "";
        while (list($key, $val) = each($para)) {
            if (is_array($val)) {
                continue;
            }

            $arg.=$key."=".urldecode($val)."&";
        }
       
        //去掉最后一个&字符
        $arg = substr($arg, 0, strlen($arg) - 2);

        //如果存在转义字符,那么去掉转义
        if (get_magic_quotes_gpc()) {
            $arg = stripslashes($arg);
        }

        return $arg;
    }
    
    
    /**
     * @function 将array转为xml
     * @param array $values
     * @return string|bool
     * @author xiewg
     **/
    public function toXml($values)
    {
        if (!is_array($values) || count($values) <= 0) {
            return false;
        }

        $xml = "<xml>";
        foreach ($values as $key => $val) {
            if (is_numeric($val)) {
                $xml.="<".$key.">".$val."</".$key.">";
            } else {
                $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
            }
        }
        $xml.="</xml>";
        return $xml;
    }
    
    
    public function setOption($option) {
        $this->option = $option + $this->option;
      
        return $this;
    }
    
    public function post($url, $data, $timeoutMs = 3000) {
        return $this->request( $data, $url,$timeoutMs);
    }

    
    
    public function request($xml, $url, $useCert = false, $second = 30)
    {
        
    
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);

        //如果有配置代理这里就设置代理
//        if(WxPayConfig::CURL_PROXY_HOST != "0.0.0.0"
//            && WxPayConfig::CURL_PROXY_PORT != 0){
//            curl_setopt($ch,CURLOPT_PROXY, WxPayConfig::CURL_PROXY_HOST);
//            curl_setopt($ch,CURLOPT_PROXYPORT, WxPayConfig::CURL_PROXY_PORT);
//        }
        curl_setopt($ch,CURLOPT_URL, $url);
        curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
        curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验
        //设置header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        //要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        if (defined('CURLOPT_IPRESOLVE') && defined('CURL_IPRESOLVE_V4')) {
            curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
        }
        if($useCert == true){
            $cert_dir = '服务商证书目录';
          
            if(
                !file_exists($cert_dir."apiclient_cert.pem") ||
                !file_exists($cert_dir."apiclient_key.pem")
            ){
                return "2";
            }
           
            //设置证书 证书自己参考支付设置
            //使用证书:cert 与 key 分别属于两个.pem文件
            curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLCERT, $cert_dir."apiclient_cert.pem");
            curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLKEY, $cert_dir."apiclient_key.pem");
        }
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行curl
        
        $data = curl_exec($ch);
        var_dump($data);
       die;
        //返回结果
        if($data){
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            
            curl_close($ch);
            
            return "";
        }
    }

注意事项

1,需要在微信服务商后台绑定特约商户 和绑定小程序 特约商户申请可以在小程序内部实现。可以自己写接口 后期咱们在写
2,服务商有单独的appid和secret和小程序的appid和secret不一致
3,特约商户有自己的商户号和密钥 与服务商的商户号和密钥不一致
4,在统一下单的时候只有参数’profit_sharing’=>'Y’的时候才会是服务商模式支付。否则就是普通小程序支付
5,服务商需要设置分账比例最低百分之30
6,上述代码已上线,能够正常使用。如果在使用过程中报错,或者返回值错误请检查服务商appid、secret、特约商户商户号和密钥、小程序appid、secret等参数是否正确没有用错
7,服务商分账时使用的证书为服务商证书和微信支付的证书不一致

  PHP知识库 最新文章
Laravel 下实现 Google 2fa 验证
UUCTF WP
DASCTF10月 web
XAMPP任意命令执行提升权限漏洞(CVE-2020-
[GYCTF2020]Easyphp
iwebsec靶场 代码执行关卡通关笔记
多个线程同步执行,多个线程依次执行,多个
php 没事记录下常用方法 (TP5.1)
php之jwt
2021-09-18
上一篇文章      下一篇文章      查看所有文章
加:2021-07-24 11:13:11  更:2021-07-24 11:14:56 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 2:14:35-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码