PHP小程序支付

♦先看本节效果图


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

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;

    }

}

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,976评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,249评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,449评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,433评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,460评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,132评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,721评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,641评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,180评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,267评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,408评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,076评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,767评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,255评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,386评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,764评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,413评论 2 358

推荐阅读更多精彩内容