PHP小程序支付

2019-11-26  本文已影响0人  后端PHPer

♦先看本节效果图

详细说明请看微信支付官方开发文档:

1. 用户在小程序内下单,选择微信支付;

2. 商户在小程序中调用小程序登录API,获得参数code;

3. 小程序端向商户后台发起接口调用,并将code及订单相关参数一起发送到商户后台。

4. 商户后台接收小程序发送的code和订单相关参数,并结合appid,secret两个参数,获取openid;

5. 商户后台根据订单信息,调用统一下单接口;

6. 统一下单接口返回预支付信息,商户后台获取预支付信息,并进行再次签名,返回支付参数(5个参数和sign)给小程序;

7. 小程序获得支付参数,发起支付请求;

8. 用户输入支付密码,支付完成;

9. 微信后台向商户后台发出异步通知,同时给小程序回调支付结果;

10.商户后台接收微信发送到异步通知,并进行相关业务处理,并返回SUCCESS或FAIL的标志以告知微信;

11.小程序获取支付回调结果,并向商户后台发起接口请求,获取订单状态;并进行支付成功或失败对应的页面跳转。

微信小程序支付文档:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_3&index=1

业务流程图

♦核心代码就下面这些

♦新建Pay.php文件

//支付接口

class Pay {

    /*

* 小程序微信支付*/

    protected $appid;

    protected $mch_id;

    protected $key;

    protected $openid;

    protected $out_trade_no;

    protected $body;

    protected $total_fee;

    function __construct($appid, $openid, $mch_id, $key,$out_trade_no,$body,$total_fee) {

        $this->appid = $appid;

        $this->openid = $openid;

        $this->mch_id = $mch_id;

        $this->key = $key;

        $this->out_trade_no = $out_trade_no;

        $this->body = $body;

        $this->total_fee = $total_fee;

    }

    public function pay() {

        //统一下单接口

        $return = $this->weixinapp();

        return $return;

    }

    //统一下单接口

    private function unifiedorder() {

        $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';

        $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' => floatval(0.01 * 100), //总金额 单位 分

            'total_fee' => $this->total_fee,

            'spbill_create_ip' => $_SERVER['REMOTE_ADDR'], //终端IP

            'notify_url' => "https://" . $_SERVER['HTTP_HOST'] . "/public/api.php/client_api/pay2/payNotify",//通知地址

            'openid' => $this->openid, //用户id

            'trade_type' => 'JSAPI'//交易类型

        );

        //统一下单签名

        $parameters['sign'] = $this->getSign($parameters);

        $xmlData = $this->arrayToXml($parameters);

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

        return $return;

    }

    private static function postXmlCurl($xml, $url, $second = 30)

{

        $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);

        //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 . ">" . arrayToXml($val) . "</" . $key . ">";

            } else {

                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";

            }

}

        $xml .= "</root>";

        return $xml;

    }

    //xml转换成数组

    private 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();

        $parameters = array(

            'appId' => $this->appid, //小程序ID

            'timeStamp' => '' . time() . '', //时间戳

            'nonceStr' => $this->createNoncestr(), //随机串

            'package' => 'prepay_id=' . $unifiedorder['prepay_id'], //数据包

            '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 = $this->formatBizQueryParaMap($Parameters, false);

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

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

    }

♦新建接口文件Pay2.php

♦在这个文件里面调用Pay.php,开始使用

class Pay2{

//支付费用

    public function payJoinfee(){

        $data = request()->param();

        $appid='小程序的APPID';

        $openid=$data['openid'];

        $mch_id='商户号';

        $key='设置的公众号token';

        $out_trade_no = $mch_id.time();

        $row = Db::table('dp_admin_per')->find($data['per_id']);

        $arr['order_sn'] = $out_trade_no;

        $arr['uid'] = $data['uid'];

        $arr['atime'] = time();

        $arr['name'] = $row['name'];

        $arr['price'] = $row['value'] * 365;

        $arr['openid'] = $openid;

        $arr['per_id'] = $data['per_id'];

        require "Pay.php";

        $weixinpay = new Pay($appid,$openid,$mch_id,$key,$arr['order_sn'],$arr['name'],$arr['price']);

        $return=$weixinpay->pay();

        Db::table('order')->insert($arr);

        return json(['code'=>200,'msg'=>'获取成功','res'=>$return]);

    }

    /**

    * 支付回调

    * @author:大脸猫脸大

    */

    public function payNotify()

{

        //接收微信返回的数据数据,返回的xml格式

        $xmlData = file_get_contents('php://input');

        //将xml格式转换为数组

        $data = $this->FromXml($xmlData);

        //用日志记录检查数据是否接受成功,验证成功一次之后,可删除。

        $path_dir='./log.txt';

        if (!is_dir(dirname($path_dir))) {

            mkdir($path_dir, 0777, true);

        }

        $file = fopen('./log.txt', 'a+');

        fwrite($file,var_export($data,true));

        //为了防止假数据,验证签名是否和返回的一样。

        //记录一下,返回回来的签名,生成签名的时候,必须剔除sign字段。

        $sign = $data['sign'];

        unset($data['sign']);

        if($sign == $this->getSign($data)){

            //签名验证成功后,判断返回微信返回的

            if ($data['result_code'] == 'SUCCESS') {

                //根据返回的订单号做业务逻辑

                $re = Db::table('order')->where(['order_sn'=>$data['out_trade_no']])->setField(['order_status'=>1,'pay_time'=>time()]);

                $sql = Db::table('order')->getLastSql();

                $file = fopen('./log.txt', 'a+');

                fwrite($file,"订单状态修改成功:".$sql."\r\n");

                //处理完成之后,告诉微信成功结果!

                //增加会员到期时间

                $row = Db::table('order')->where('order_sn','15642106611574737298')->find();

                $vip_end_time = Db::table('dp_admin_user')->where('id',$row['uid'])->value('vip_end_time');

                if ($vip_end_time || $vip_end_time != 0){

                    $vip_end_time = (time() +(365 * 86400)) + ($vip_end_time - time());

                }else{

                    $vip_end_time = time() +(365 * 86400);

                }

                Db::table('dp_admin_user')->where('id',$row['uid'])->setInc(['vip_end_time'=>$vip_end_time]);

                Db::table('dp_admin_user')->where('id',$row['uid'])->setField(['is_vip'=>1]);

                if($re){

                    echo '

                              </xml>';exit();

                }

}

            //支付失败,输出错误信息

            else{

                $file = fopen('./log.txt', 'a+');

                fwrite($file,"错误信息:".$data['return_msg'].date("Y-m-d H:i:s"),time()."\r\n");

            }

}

        else{

            $file = fopen('./log.txt', 'a+');

            fwrite($file,"错误信息:签名验证失败".date("Y-m-d H:i:s"),time()."\r\n");

        }

}

    private function getSign($params) {

        ksort($params);        //将参数数组按照参数名ASCII码从小到大排序

        foreach ($params as $key => $item) {

            if (!empty($item)) {        //剔除参数值为空的参数

                $newArr[] = $key.'='.$item;    // 整合新的参数数组

            }

}

        $stringA = implode("&", $newArr);        //使用 & 符号连接参数

        $stringSignTemp = $stringA."&key=设置的公众号token♥";        //拼接key

        // key是在商户平台API安全里自己设置的

        $stringSignTemp = MD5($stringSignTemp);      //将字符串进行MD5加密

        $sign = strtoupper($stringSignTemp);      //将所有字符转换为大写

        return $sign;

    }

    //将xml数据转换为数组,接收微信返回数据时用到.

    public function FromXml($xml)

{

        if(!$xml){

            echo "xml数据异常!";

        }

        //将XML转为array

        //禁止引用外部xml实体

        libxml_disable_entity_loader(true);

        $data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);

        return $data;

    }

}

}

上一篇下一篇

猜你喜欢

热点阅读