美文网首页
PHP小程序支付

PHP小程序支付

作者: 后端PHPer | 来源:发表于2019-11-26 14:24 被阅读0次

    ♦先看本节效果图

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

    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;

        }

    }

    }

    相关文章

      网友评论

          本文标题:PHP小程序支付

          本文链接:https://www.haomeiwen.com/subject/ouiewctx.html