php微信支付之APP支付方法


Posted in PHP onMarch 04, 2015

本文实例讲述了微信开放平台移动应用集成微信支付功能。分享给大家供大家参考。具体分析如下:

WechatAppPay文件代码如下:

<?php

namespace common\services\WechatPay;

class WechatAppPay extends WechatPayBase

{

    //package参数

    public $package = [];

    //异步通知参数

    public $notify = [];

    //推送预支付订单参数

    protected $config = [];

    //存储access token和获取时间的文件

    protected $file;

    //access token

    protected $accessToken;

    //取access token的url

    const ACCESS_TOKEN_URL = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s';

    //生成预支付订单提交地址

    const POST_ORDER_URL = 'https://api.weixin.qq.com/pay/genprepay?access_token=%s';

    public function __construct()

    {

        $this->file = __DIR__ . '/payAccessToken.txt';

    }

    /**

     * 创建APP支付最终返回参数

     * @throws \Exception

     * @return multitype:string NULL

     */

    public function createAppPayData()

    {

        $this->generateConfig();

        $prepayid = $this->getPrepayid();

        try{

            $array = [

                'appid' => $this->appid,

                'appkey' => $this->paySignkey,

                'noncestr' => $this->getRandomStr(),

                'package' => 'Sign=WXPay',

                'partnerid' => $this->partnerId,

                'prepayid' => $prepayid,

                'timestamp' => (string)time(),

            ];

            $array['sign'] = $this->sha1Sign($array);

            unset($array['appkey']);

        } catch(\Exception $e) {

            throw new \Exception($e->getMessage());

        }

        return $array;

    }

    /**

     * 验证支付成功后的通知参数

     * 

     * @throws \Exception

     * @return boolean

     */

    public function verifyNotify()

    {

        try{

            $staySignStr = $this->notify;

            unset($staySignStr['sign']);

            $sign = $this->signData($staySignStr);

            return $this->notify['sign'] === $sign;

        } catch(\Exception $e) {

            throw new \Exception($e->getMessage());

        }

    }

    /**

     * 魔术方法,给添加支付参数进来

     * 

     * @param string $name  参数名

     * @param string $value  参数值

     */

    public function __set($name, $value)

    {

        $this->$name = $value;

    }

    /**

     * 设置access token

     * @param string $token

     * @throws \Exception

     * @return boolean

     */

    public function setAccessToken()

    {

        try{

            if(!file_exists($this->file) || !is_file($this->file)) {

                $f = fopen($this->file, 'a');

                fclose($f);

            }

            $content = file_get_contents($this->file);

            if(!empty($content)) {

                $info = json_decode($content, true);

                if( time() - $info['getTime'] < 7150 ) {

                    $this->accessToken = $info['accessToken'];

                    return true;

                }

            }

            //文件内容为空或access token已失效,重新获取

            $this->outputAccessTokenToFile();

        } catch(\Exception $e) {

            throw new \Exception($e->getMessage());

        }

        return true;

    }

    /**

     * 写入access token 到文件

     * @throws \Exception

     * @return boolean

     */

    protected function outputAccessTokenToFile()

    {

        try{

            $f = fopen($this->file, 'wb');

            $token = [

                'accessToken' => $this->getAccessToken(),

                'getTime' => time(),

            ];

            flock($f, LOCK_EX);

            fwrite($f, json_encode($token));

            flock($f, LOCK_UN);

            fclose($f);

            $this->accessToken = $token['accessToken'];

        } catch(\Exception $e) {

            throw new \Exception($e->getMessage());

        }

        return true;

    }

    /**

     * 取access token

     * 

     * @throws \Exception

     * @return string

     */

    protected function getAccessToken()

    {

        $url = sprintf(self::ACCESS_TOKEN_URL, $this->appid, $this->appSecret);

        $result = json_decode( $this->getUrl($url), true );

        if(isset($result['errcode'])) {

            throw new \Exception("get access token failed:{$result['errmsg']}");

        }

        return $result['access_token'];

    }

    /**

     * 取预支付会话标识

     * 

     * @throws \Exception

     * @return string

     */

    protected function getPrepayid()

    {

        $data = json_encode($this->config);

        $url = sprintf(self::POST_ORDER_URL, $this->accessToken);

        $result = json_decode( $this->postUrl($url, $data), true );

        if( isset($result['errcode']) && $result['errcode'] != 0 ) {

            throw new \Exception($result['errmsg']);

        }

        if( !isset($result['prepayid']) ) {

            throw new \Exception('get prepayid failed, url request error.');

        }

        return $result['prepayid'];

    }

    /**

     * 组装预支付参数

     * 

     * @throws \Exception

     */

    protected function generateConfig()

    {

        try{

            $this->config = [

                    'appid' => $this->appid,

                    'traceid' => $this->traceid,

                    'noncestr' => $this->getRandomStr(),

                    'timestamp' => time(),

                    'package' => $this->generatePackage(),

                    'sign_method' => $this->sign_method,

            ];

            $this->config['app_signature'] = $this->generateSign();

        } catch(\Exception $e) {

            throw new \Exception($e->getMessage());

        }

    }

    /**

     * 生成package字段

     * 

     * 生成规则:

     * 1、生成sign的值signValue

     * 2、对package参数再次拼接成查询字符串,值需要进行urlencode

     * 3、将sign=signValue拼接到2生成的字符串后面得到最终的package字符串

     * 

     * 第2步urlencode空格需要编码成%20而不是+

     * 

     * RFC 1738会把 空格编码成+

     * RFC 3986会把空格编码成%20

     * 

     * @return string

     */

    protected function generatePackage()

    {

        $this->package['sign'] = $this->signData($this->package);

        return http_build_query($this->package, '', '&', PHP_QUERY_RFC3986);

    }

    /**

     * 生成签名

     * 

     * @return string

     */

    protected function generateSign()

    {

        $signArray = [

            'appid' => $this->appid,

            'appkey' => $this->paySignkey,

            'noncestr' => $this->config['noncestr'],

            'package' => $this->config['package'],

            'timestamp' => $this->config['timestamp'],

            'traceid' => $this->traceid,

        ];

        return $this->sha1Sign($signArray);

    }

    /**

     * 签名数据

     * 

     * 生成规则:

     * 1、字典排序,拼接成查询字符串格式,不需要urlencode

     * 2、上一步得到的字符串最后拼接上key=paternerKey

     * 3、MD5哈希字符串并转换成大写得到sign的值signValue

     * 

     * @param array $data 待签名数据

     * @return string 最终签名结果

     */

    protected function signData($data)

    {

        ksort($data);

        $str = $this->arrayToString($data);

        $str .= "&key={$this->partnerKey}";

        return strtoupper( $this->signMd5($str) );

    }

    /**

     * sha1签名

     * 签名规则

     * 1、字典排序

     * 2、拼接查询字符串

     * 3、sha1运算

     * 

     * @param array $arr

     * @return string

     */

    protected function sha1Sign($arr)

    {

        ksort($arr);

        return sha1( $this->arrayToString($arr) );

    }

}

希望本文所述对大家的php程序设计有所帮助。

PHP 相关文章推荐
多文件上传的例子
Oct 09 PHP
php中可能用来加密字符串的函数[base64_encode、urlencode、sha1]
Jan 16 PHP
ThinkPHP V2.2说明文档没有说明的那些事实例小结
Jul 01 PHP
详解php设置session(过期、失效、有效期)
Nov 12 PHP
100多行PHP代码实现socks5代理服务器[2]
May 05 PHP
Yii2创建控制器(createController)方法详解
Jul 23 PHP
php中mkdir()函数的权限问题分析
Sep 24 PHP
php中目录操作opendir()、readdir()及scandir()用法示例
Jun 08 PHP
Laravel 5.2 文档 数据库 ―― 起步介绍
Oct 21 PHP
PHP number_format函数原理及实例解析
Jul 14 PHP
PHP实现简单注册登录系统
Dec 28 PHP
用php如何解决大文件分片上传问题
Jul 07 PHP
php支付宝手机网页支付类实例
Mar 04 #PHP
php银联网页支付实现方法
Mar 04 #PHP
php随机抽奖实例分析
Mar 04 #PHP
php二维数组合并及去重复的方法
Mar 04 #PHP
php中get_cfg_var()和ini_get()的用法及区别
Mar 04 #PHP
php用ini_get获取php.ini里变量值的方法
Mar 04 #PHP
浅谈PHP中单引号和双引号到底有啥区别呢?
Mar 04 #PHP
You might like
php采用curl实现伪造IP来源的方法
2014/11/21 PHP
锋利的jQuery jQuery中的DOM操作
2010/03/21 Javascript
学习从实践开始之jQuery插件开发 菜单插件开发
2012/05/03 Javascript
js简单实现用户注册信息的校验代码
2013/11/15 Javascript
jQuery的context属性用法实例
2014/12/27 Javascript
JavaScript中的变量定义与储存介绍
2014/12/31 Javascript
推荐5 个常用的JavaScript调试技巧
2015/01/08 Javascript
javascript结合CSS实现苹果开关按钮特效
2015/04/07 Javascript
使用原生的javascript来实现轮播图
2017/02/24 Javascript
JavaScript实现简单的四则运算计算器完整实例
2017/04/28 Javascript
详解Angular 4.x Injector
2017/05/04 Javascript
基于three.js编写的一个项目类示例代码
2018/01/05 Javascript
jquery的 filter()方法使用教程
2018/03/22 jQuery
Vue CLI 2.x搭建vue(目录最全分析)
2019/02/27 Javascript
微信小程序实现打开并下载服务器上面的pdf文件到手机
2019/09/20 Javascript
在vue中阻止浏览器后退的实例
2019/11/06 Javascript
JS中间件设计模式的深入探讨与实例分析
2020/04/11 Javascript
webpack5 联邦模块介绍详解
2020/07/08 Javascript
vant-ui AddressEdit地址编辑和van-area的用法说明
2020/11/03 Javascript
[01:11:28]DOTA2-DPC中国联赛定级赛 RNG vs Phoenix BO3第一场 1月8日
2021/03/11 DOTA
python生成器的使用方法
2013/11/21 Python
浅谈关于Python3中venv虚拟环境
2018/08/01 Python
pyqt5 实现在别的窗口弹出进度条
2019/06/18 Python
tensorflow通过模型文件,使用tensorboard查看其模型图Graph方式
2020/01/23 Python
pycharm快捷键汇总
2020/02/14 Python
python中提高pip install速度
2020/02/14 Python
CSS3地图动态实例代码(圆圈向外扩散)
2018/06/15 HTML / CSS
简单html5代码获取地理位置
2014/03/31 HTML / CSS
详解Html5 监听拦截Android返回键方法
2018/04/18 HTML / CSS
Holland & Barrett爱尔兰:英国领先的健康零售商
2019/03/31 全球购物
The Athlete’s Foot新西兰:新西兰最大的运动鞋零售商
2019/12/23 全球购物
数学教师个人总结
2015/02/06 职场文书
房屋质量投诉书
2015/07/02 职场文书
月考总结与反思
2015/10/22 职场文书
MySQL中distinct和count(*)的使用方法比较
2021/05/26 MySQL
Java9新特性之Module模块化编程示例演绎
2022/03/16 Java/Android