短信网站代码备份

main
甜甜圈 2023-12-05 09:46:27 +08:00
commit 078a41ac3c
1436 changed files with 271146 additions and 0 deletions

View File

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectDictionaryState">
<dictionary name="lock" />
</component>
</project>

View File

@ -0,0 +1,5 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" />
</settings>
</component>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/application" isTestSource="false" packagePrefix="app\" />
<sourceFolder url="file://$MODULE_DIR$/application/common/WeChatPay/src" isTestSource="false" packagePrefix="WeChatPay\" />
<sourceFolder url="file://$MODULE_DIR$/application/common/WeChatPay/tests" isTestSource="true" packagePrefix="WeChatPay\Tests\" />
<sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/thinkphp/library/think" isTestSource="false" packagePrefix="think\" />
<sourceFolder url="file://$MODULE_DIR$/thinkphp/tests" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/topthink/think-installer" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/message-admin.iml" filepath="$PROJECT_DIR$/.idea/message-admin.iml" />
</modules>
</component>
</project>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MessDetectorOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCSFixerOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCodeSnifferOptionsConfiguration">
<option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" />
</component>
<component name="PhpIncludePathManager">
<include_path>
<path value="$PROJECT_DIR$/vendor/composer" />
<path value="$PROJECT_DIR$/vendor/topthink/think-installer" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="5.4.0">
<option name="suggestChangeDefaultLanguageLevel" value="false" />
</component>
<component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PsalmOptionsConfiguration">
<option name="transferred" value="true" />
</component>
</project>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PHPUnit">
<option name="directories">
<list>
<option value="$PROJECT_DIR$/thinkphp/tests" />
<option value="$PROJECT_DIR$/application/common/WeChatPay/tests" />
<option value="$PROJECT_DIR$/application/common/WeChatPay/tests" />
<option value="$PROJECT_DIR$/thinkphp/tests" />
<option value="$PROJECT_DIR$/application/common/WeChatPay/tests" />
<option value="$PROJECT_DIR$/thinkphp/tests" />
<option value="$PROJECT_DIR$/application/common/WeChatPay/tests" />
<option value="$PROJECT_DIR$/thinkphp/tests" />
<option value="$PROJECT_DIR$/application/common/WeChatPay/tests" />
<option value="$PROJECT_DIR$/thinkphp/tests" />
<option value="$PROJECT_DIR$/application/common/WeChatPay/tests" />
<option value="$PROJECT_DIR$/thinkphp/tests" />
<option value="$PROJECT_DIR$/application/common/WeChatPay/tests" />
<option value="$PROJECT_DIR$/thinkphp/tests" />
<option value="$PROJECT_DIR$/thinkphp/tests" />
<option value="$PROJECT_DIR$/application/common/WeChatPay/tests" />
<option value="$PROJECT_DIR$/thinkphp/tests" />
<option value="$PROJECT_DIR$/application/common/WeChatPay/tests" />
<option value="$PROJECT_DIR$/thinkphp/tests" />
<option value="$PROJECT_DIR$/application/common/WeChatPay/tests" />
</list>
</option>
</component>
</project>

View File

@ -0,0 +1,32 @@
ThinkPHP遵循Apache2开源协议发布并提供免费使用。
版权所有Copyright © 2006-2017 by ThinkPHP (http://thinkphp.cn)
All rights reserved。
ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
Apache Licence是著名的非盈利开源组织Apache采用的协议。
该协议和BSD类似鼓励代码共享和尊重原作者的著作权
允许代码修改,再作为开源或商业软件发布。需要满足
的条件:
1 需要给代码的用户一份Apache Licence
2 如果你修改了代码,需要在被修改的文件中说明;
3 在延伸的代码中(修改和有源代码衍生的代码中)需要
带有原来代码中的协议,商标,专利声明和其他原来作者规
定需要包含的说明;
4 如果再发布的产品中包含一个Notice文件则在Notice文
件中需要带有本协议内容。你可以在Notice中增加自己的
许可但不可以表现为对Apache Licence构成更改。
具体的协议参考http://www.apache.org/licenses/LICENSE-2.0
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,133 @@
ThinkPHP 5.0
===============
[![Total Downloads](https://poser.pugx.org/topthink/think/downloads)](https://packagist.org/packages/topthink/think)
[![Latest Stable Version](https://poser.pugx.org/topthink/think/v/stable)](https://packagist.org/packages/topthink/think)
[![Latest Unstable Version](https://poser.pugx.org/topthink/think/v/unstable)](https://packagist.org/packages/topthink/think)
[![License](https://poser.pugx.org/topthink/think/license)](https://packagist.org/packages/topthink/think)
ThinkPHP5在保持快速开发和大道至简的核心理念不变的同时PHP版本要求提升到5.4对已有的CBD模式做了更深的强化优化核心减少依赖基于全新的架构思想和命名空间实现是ThinkPHP突破原有框架思路的颠覆之作其主要特性包括
+ 基于命名空间和众多PHP新特性
+ 核心功能组件化
+ 强化路由功能
+ 更灵活的控制器
+ 重构的模型和数据库类
+ 配置文件可分离
+ 重写的自动验证和完成
+ 简化扩展机制
+ API支持完善
+ 改进的Log类
+ 命令行访问支持
+ REST支持
+ 引导文件支持
+ 方便的自动生成定义
+ 真正惰性加载
+ 分布式环境支持
+ 更多的社交类库
> ThinkPHP5的运行环境要求PHP5.4以上。
详细开发文档参考 [ThinkPHP5完全开发手册](http://www.kancloud.cn/manual/thinkphp5)
## 目录结构
初始的目录结构如下:
~~~
www WEB部署目录或者子目录
├─application 应用目录
│ ├─common 公共模块目录(可以更改)
│ ├─module_name 模块目录
│ │ ├─config.php 模块配置文件
│ │ ├─common.php 模块函数文件
│ │ ├─controller 控制器目录
│ │ ├─model 模型目录
│ │ ├─view 视图目录
│ │ └─ ... 更多类库目录
│ │
│ ├─command.php 命令行工具配置文件
│ ├─common.php 公共函数文件
│ ├─config.php 公共配置文件
│ ├─route.php 路由配置文件
│ ├─tags.php 应用行为扩展定义文件
│ └─database.php 数据库配置文件
├─public WEB目录对外访问目录
│ ├─index.php 入口文件
│ ├─router.php 快速测试文件
│ └─.htaccess 用于apache的重写
├─thinkphp 框架系统目录
│ ├─lang 语言文件目录
│ ├─library 框架类库目录
│ │ ├─think Think类库包目录
│ │ └─traits 系统Trait目录
│ │
│ ├─tpl 系统模板目录
│ ├─base.php 基础定义文件
│ ├─console.php 控制台入口文件
│ ├─convention.php 框架惯例配置文件
│ ├─helper.php 助手函数文件
│ ├─phpunit.xml phpunit配置文件
│ └─start.php 框架入口文件
├─extend 扩展类库目录
├─runtime 应用的运行时目录(可写,可定制)
├─vendor 第三方类库目录Composer依赖库
├─build.php 自动生成定义文件(参考)
├─composer.json composer 定义文件
├─LICENSE.txt 授权说明文件
├─README.md README 文件
├─think 命令行入口文件
~~~
> router.php用于php自带webserver支持可用于快速测试
> 切换到public目录后启动命令php -S localhost:8888 router.php
> 上面的目录结构和名称是可以改变的,这取决于你的入口文件和配置参数。
## 命名规范
`ThinkPHP5`遵循PSR-2命名规范和PSR-4自动加载规范并且注意如下规范
### 目录和文件
* 目录不强制规范,驼峰和小写+下划线模式均支持;
* 类库、函数文件统一以`.php`为后缀;
* 类的文件名均以命名空间定义,并且命名空间的路径和类库文件所在路径一致;
* 类名和类文件名保持一致,统一采用驼峰法命名(首字母大写);
### 函数和类、属性命名
* 类的命名采用驼峰法,并且首字母大写,例如 `User`、`UserType`,默认不需要添加后缀,例如`UserController`应该直接命名为`User`
* 函数的命名使用小写字母和下划线(小写字母开头)的方式,例如 `get_client_ip`
* 方法的命名使用驼峰法,并且首字母小写,例如 `getUserName`
* 属性的命名使用驼峰法,并且首字母小写,例如 `tableName`、`instance`
* 以双下划线“__”打头的函数或方法作为魔法方法例如 `__call``__autoload`
### 常量和配置
* 常量以大写字母和下划线命名,例如 `APP_PATH``THINK_PATH`
* 配置参数以小写字母和下划线命名,例如 `url_route_on` 和`url_convert`
### 数据表和字段
* 数据表和字段采用小写加下划线方式命名,并注意字段名不要以下划线开头,例如 `think_user` 表和 `user_name`字段,不建议使用驼峰和中文作为数据表字段命名。
## 参与开发
请参阅 [ThinkPHP5 核心框架包](https://github.com/top-think/framework)。
## 版权信息
ThinkPHP遵循Apache2开源协议发布并提供免费使用。
本项目包含的第三方源码和二进制文件之版权信息另行标注。
版权所有Copyright © 2006-2018 by ThinkPHP (http://thinkphp.cn)
All rights reserved。
ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。
更多细节参阅 [LICENSE.txt](LICENSE.txt)

View File

@ -0,0 +1,12 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
return [];

View File

@ -0,0 +1,633 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: 流年 <liu21st@gmail.com>
// +----------------------------------------------------------------------
// 应用公共文件
/**
* 是否是邮件
* @param $str
* @return bool
*/
function isEmail($str){
return preg_match('/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/', $str) === 1;
}
/**
* 是否是url
* @param $str
* @return bool
*/
function isUrl($str){
return preg_match('/^http(s?):\/\/(?:[A-za-z0-9-]+\.)+[A-za-z]{2,4}(:\d+)?(?:[\/\?#][\/=\?%\-&~`@[\]\':+!\.#\w]*)?$/', $str) === 1;
}
/**
* 是否是金钱
* @param $str
* @return bool
*/
function isCurrency($str){
return preg_match('/^\d+(\.\d+)?$/', $str) === 1;
}
/**
* 是否是邮编
* @param $str
* @return bool
*/
function isPostcode($str){
return preg_match('/^\d{6}$/', $str) === 1;
}
/**
* 是否是英文
* @param $str
* @return bool
*/
function isEnglish($str){
return preg_match('/^[A-Za-z]+$/', $str) === 1;
}
/**
* 是否是手机
* @param $str
* @return bool
*/
function isMobile($str){
return preg_match('/^1(3|4|5|8)\d{9}$/', $str) === 1;
}
/**
* 是否是固话
* @param $str
* @return bool
*/
function isFixedPhone($str){
return preg_match('/\d{3}(-?)\d{8}|\d{4}(-?)\d{7}$/', $str) === 1;
}
/**
* 是否是身份证号
* @param $str
* @return bool
*/
function isIdNumber($str){
return preg_match('/\d{15}|\d{18}/', $str) === 1;
}
/**
* 是否是正确的密码格式以字母开头长度在6~18之间,只能包含字符、数字和下划线
* @param $str
* @param int $length_min 最小长度
* @param int $length_max 最大长度
* @return bool
*/
function isPassword($str, $length_min = 6, $length_max = 18){
return preg_match('/^[a-zA-Z]\w{'.$length_min.','.$length_max.'}$/', $str) === 1;
}
/**
* 判断字符串是否是json数据
* @param $str
* @return bool
*/
function isJson($str){
$str = json_decode($str);
return !empty($str);
}
/**
* 判断字符串是否是int型
* @param $str
* @return bool
*/
function isInt($str){
return strlen(intval($str)) === strlen($str);
}
/**
* crc32和md5加密
* @param $str
* @return string
*/
function encryption($str)
{
return crc32($str).md5($str);
}
/**
* 数组拼接
* @param $arr
* @return string
*/
function access_toke_enc($arr)
{
return encryption(implode(",",$arr));
}
/**
* 判断token是否过期
* @param $token
* @return bool
*/
function checkToken($token)
{
$tmp_time = getRedisValue($token);
if($tmp_time)
{
if (time() - $tmp_time <= 7200)
{
return true;
}
}
return false;
}
/**
* getIp
* @return array|false|string
*/
function getIp()
{
global $ip;
if (getenv("HTTP_CLIENT_IP"))
$ip = getenv("HTTP_CLIENT_IP");
else if(getenv("HTTP_X_FORWARDED_FOR"))
$ip = getenv("HTTP_X_FORWARDED_FOR");
else if(getenv("REMOTE_ADDR"))
$ip = getenv("REMOTE_ADDR");
else $ip = "Unknow";
return $ip;
}
/**
* getBarberType
* @return array
*/
function getBarberType()
{
$arr = array(
'1' => '发型师',
'2' => '助理',
'3' => '技师'
);
return $arr;
}
/**
* getSex
* @param int $type
* @return array|mixed
*/
function getSex($type = 0)
{
$arr = array(
'1' => '男',
'2' => '女'
);
if($type != 0)
{
return $arr[$type];
}
return $arr;
}
/**
* getConstellation
* @param int $type
* @return array|mixed
*/
function getConstellation($type = 0)
{
$arr = array(
'1' => '白羊座',
'2' => '金牛座',
'3' => '双子座',
'4' => '巨蟹座',
'5' => '狮子座',
'6' => '处女座',
'7' => '天秤座',
'8' => '天蝎座',
'9' => '射手座',
'10' => '摩羯座',
'11' => '水瓶座',
'12' => '双鱼座'
);
if($type != 0)
{
return $arr[$type];
}
return $arr;
}
/**
* getServiceType
* @return array
*/
function getServiceType()
{
$arr = array(
"error_code" => 0,
"lists" => array(
array(
'id' => 1,
'name' => '服务项目'
),
array(
'id' => 2,
'name' => '套餐'
),
array(
'id' => 3,
'name' => '会员卡'
),
array(
'id' => 4,
'name' => '外卖产品'
)
));
return $arr;
}
/**
* getPayMode
* @return array
*/
function getPayMode()
{
$arr = array(
'1' => '支付宝',
'2' => '微信',
'3' => '现金',
'4' => '会员卡',
'5' => 'POS机',
'6' => '美团(大众点评)',
'7' => '饿了么',
'8' => '口碑'
);
return $arr;
}
/**
* setRedisValue
* @param $name
* @param $value
* @param null $time
* @return bool
*/
function setRedisValue($name,$value,$time = null)
{
$result = cache($name,$value,$time);
return $result;
}
/**
* getRedisValue
* @param $name
* @return mixed|string
*/
function getRedisValue($name)
{
$data = '';
$data = cache($name);
return $data;
}
/**
* deleteRedisValue
* @param $name
* @return bool
*/
function deleteRedisValue($name)
{
$result = cache($name,null);
return $result;
}
/**
* 加密方法
* @param string $str
* @return string
*/
function encrypt($str,$screct_key){
//AES, 128 模式加密数据 CBC
$screct_key = base64_decode($screct_key);
$str = trim($str);
$str = addPKCS7Padding($str);
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128,MCRYPT_MODE_CBC),1);
$encrypt_str = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $screct_key, $str, MCRYPT_MODE_CBC);
return base64_encode($encrypt_str);
}
/**
* 解密方法
* @param string $str
* @return string
*/
function decrypt($str,$screct_key){
//AES, 128 模式加密数据 CBC
$str = base64_decode($str);
$screct_key = base64_decode($screct_key);
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128,MCRYPT_MODE_CBC),1);
$encrypt_str = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $screct_key, $str, MCRYPT_MODE_CBC);
$encrypt_str = trim($encrypt_str);
$encrypt_str = stripPKSC7Padding($encrypt_str);
return $encrypt_str;
}
/**
* 填充算法
* @param string $source
* @return string
*/
function addPKCS7Padding($source){
$source = trim($source);
$block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$pad = $block - (strlen($source) % $block);
if ($pad <= $block) {
$char = chr($pad);
$source .= str_repeat($char, $pad);
}
return $source;
}
/**
* 移去填充算法
* @param string $source
* @return string
*/
function stripPKSC7Padding($source){
$source = trim($source);
$char = substr($source, -1);
$num = ord($char);
if($num==62)return $source;
$source = substr($source,0,-$num);
return $source;
}
/**随机获取字符串
* @param $length 字符串个数
* @return string 返回的字符串
*/
function createRandomStr($length){
$str = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';//62个字符
$strlen = 62;
while($length > $strlen){
$str .= $str;
$strlen += 62;
}
$str = str_shuffle($str);
return substr($str,0,$length);
}
/**
* @param $arr
* @return string
*/
function importArr($arr,$tag)
{
$return_str = '';
$tmp_i = 0;
foreach ($arr as $each_v)
{
if($tmp_i == 0)
{
$return_str = $each_v;
}else{
$return_str = $return_str.$tag.$each_v;
}
$tmp_i++;
}
return $return_str;
}
/**
* qiniu function start
*/
/**
* 计算文件的crc32检验码:
*
* @param $file string 待计算校验码的文件路径
*
* @return string 文件内容的crc32校验码
*/
function crc32_file($file)
{
$hash = hash_file('crc32b', $file);
$array = unpack('N', pack('H*', $hash));
return sprintf('%u', $array[1]);
}
/**
* 计算输入流的crc32检验码
*
* @param $data 待计算校验码的字符串
*
* @return string 输入字符串的crc32校验码
*/
function crc32_data($data)
{
$hash = hash('crc32b', $data);
$array = unpack('N', pack('H*', $hash));
return sprintf('%u', $array[1]);
}
/**
* 对提供的数据进行urlsafe的base64编码。
*
* @param string $data 待编码的数据,一般为字符串
*
* @return string 编码后的字符串
* @link http://developer.qiniu.com/docs/v6/api/overview/appendix.html#urlsafe-base64
*/
function base64_urlSafeEncode($data)
{
$find = array('+', '/');
$replace = array('-', '_');
return str_replace($find, $replace, base64_encode($data));
}
/**
* 对提供的urlsafe的base64编码的数据进行解码
*
* @param string $str 待解码的数据,一般为字符串
*
* @return string 解码后的字符串
*/
function base64_urlSafeDecode($str)
{
$find = array('-', '_');
$replace = array('+', '/');
return base64_decode(str_replace($find, $replace, $str));
}
/**
* 计算七牛API中的数据格式
*
* @param $bucket 待操作的空间名
* @param $key 待操作的文件名
*
* @return string 符合七牛API规格的数据格式
* @link http://developer.qiniu.com/docs/v6/api/reference/data-formats.html
*/
function entry($bucket, $key)
{
$en = $bucket;
if (!empty($key)) {
$en = $bucket . ':' . $key;
}
return base64_urlSafeEncode($en);
}
/**
* array 辅助方法无值时不set
*
* @param $array 待操作array
* @param $key key
* @param $value value 为null时 不设置
*
* @return array 原来的array便于连续操作
*/
function setWithoutEmpty(&$array, $key, $value)
{
if (!empty($value)) {
$array[$key] = $value;
}
return $array;
}
/**
* 缩略图链接拼接
*
* @param string $url 图片链接
* @param int $mode 缩略模式
* @param int $width 宽度
* @param int $height 长度
* @param string $format 输出类型
* @param int $quality 图片质量
* @param int $interlace 是否支持渐进显示
* @param int $ignoreError 忽略结果
* @return string
* @link http://developer.qiniu.com/code/v6/api/kodo-api/image/imageview2.html
* @author Sherlock Ren <sherlock_ren@icloud.com>
*/
function thumbnail(
$url,
$mode,
$width,
$height,
$format = null,
$quality = null,
$interlace = null,
$ignoreError = 1
) {
static $imageUrlBuilder = null;
if (is_null($imageUrlBuilder)) {
$imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder;
}
return call_user_func_array(array($imageUrlBuilder, 'thumbnail'), func_get_args());
}
/**
* 图片水印
*
* @param string $url 图片链接
* @param string $image 水印图片链接
* @param numeric $dissolve 透明度
* @param string $gravity 水印位置
* @param numeric $dx 横轴边距
* @param numeric $dy 纵轴边距
* @param numeric $watermarkScale 自适应原图的短边比例
* @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html
* @return string
* @author Sherlock Ren <sherlock_ren@icloud.com>
*/
function waterImg(
$url,
$image,
$dissolve = 100,
$gravity = 'SouthEast',
$dx = null,
$dy = null,
$watermarkScale = null
) {
static $imageUrlBuilder = null;
if (is_null($imageUrlBuilder)) {
$imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder;
}
return call_user_func_array(array($imageUrlBuilder, 'waterImg'), func_get_args());
}
/**
* 文字水印
*
* @param string $url 图片链接
* @param string $text 文字
* @param string $font 文字字体
* @param string $fontSize 文字字号
* @param string $fontColor 文字颜色
* @param numeric $dissolve 透明度
* @param string $gravity 水印位置
* @param numeric $dx 横轴边距
* @param numeric $dy 纵轴边距
* @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html#text-watermark
* @return string
* @author Sherlock Ren <sherlock_ren@icloud.com>
*/
function waterText(
$url,
$text,
$font = '黑体',
$fontSize = 0,
$fontColor = null,
$dissolve = 100,
$gravity = 'SouthEast',
$dx = null,
$dy = null
) {
static $imageUrlBuilder = null;
if (is_null($imageUrlBuilder)) {
$imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder;
}
return call_user_func_array(array($imageUrlBuilder, 'waterText'), func_get_args());
}
/**
* 从uptoken解析accessKey和bucket
*
* @param $upToken
* @return array(ak,bucket,err=null)
*/
function explodeUpToken($upToken)
{
$items = explode(':', $upToken);
if (count($items) != 3) {
return array(null, null, "invalid uptoken");
}
$accessKey = $items[0];
$putPolicy = json_decode(base64_urlSafeDecode($items[2]));
$scope = $putPolicy->scope;
$scopeItems = explode(':', $scope);
$bucket = $scopeItems[0];
return array($accessKey, $bucket, null);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,194 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/6/13
* Time: 2:08 PM
*/
namespace app\common\Qiniu;
use app\common\Qiniu\Zone;
final class Auth
{
private $accessKey;
private $secretKey;
public function __construct($accessKey, $secretKey)
{
$this->accessKey = $accessKey;
$this->secretKey = $secretKey;
}
public function getAccessKey()
{
return $this->accessKey;
}
public function sign($data)
{
$hmac = hash_hmac('sha1', $data, $this->secretKey, true);
return $this->accessKey . ':' . base64_urlSafeEncode($hmac);
}
public function signWithData($data)
{
$encodedData = base64_urlSafeEncode($data);
return $this->sign($encodedData) . ':' . $encodedData;
}
public function signRequest($urlString, $body, $contentType = null)
{
$url = parse_url($urlString);
$data = '';
if (array_key_exists('path', $url)) {
$data = $url['path'];
}
if (array_key_exists('query', $url)) {
$data .= '?' . $url['query'];
}
$data .= "\n";
if ($body !== null && $contentType === 'application/x-www-form-urlencoded') {
$data .= $body;
}
return $this->sign($data);
}
public function verifyCallback($contentType, $originAuthorization, $url, $body)
{
$authorization = 'QBox ' . $this->signRequest($url, $body, $contentType);
return $originAuthorization === $authorization;
}
public function privateDownloadUrl($baseUrl, $expires = 3600)
{
$deadline = time() + $expires;
$pos = strpos($baseUrl, '?');
if ($pos !== false) {
$baseUrl .= '&e=';
} else {
$baseUrl .= '?e=';
}
$baseUrl .= $deadline;
$token = $this->sign($baseUrl);
return "$baseUrl&token=$token";
}
public function uploadToken($bucket, $key = null, $expires = 3600, $policy = null, $strictPolicy = true)
{
$deadline = time() + $expires;
$scope = $bucket;
if ($key !== null) {
$scope .= ':' . $key;
}
$args = self::copyPolicy($args, $policy, $strictPolicy);
$args['scope'] = $scope;
$args['deadline'] = $deadline;
$b = json_encode($args);
return $this->signWithData($b);
}
/**
*上传策略,参数规格详见
*http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html
*/
private static $policyFields = array(
'callbackUrl',
'callbackBody',
'callbackHost',
'callbackBodyType',
'callbackFetchKey',
'returnUrl',
'returnBody',
'endUser',
'saveKey',
'insertOnly',
'detectMime',
'mimeLimit',
'fsizeMin',
'fsizeLimit',
'persistentOps',
'persistentNotifyUrl',
'persistentPipeline',
'deleteAfterDays',
'fileType',
'isPrefixalScope',
);
private static function copyPolicy(&$policy, $originPolicy, $strictPolicy)
{
if ($originPolicy === null) {
return array();
}
foreach ($originPolicy as $key => $value) {
if (!$strictPolicy || in_array((string)$key, self::$policyFields, true)) {
$policy[$key] = $value;
}
}
return $policy;
}
public function authorization($url, $body = null, $contentType = null)
{
$authorization = 'QBox ' . $this->signRequest($url, $body, $contentType);
return array('Authorization' => $authorization);
}
public function authorizationV2($url, $method, $body = null, $contentType = null)
{
$urlItems = parse_url($url);
$host = $urlItems['host'];
if (isset($urlItems['port'])) {
$port = $urlItems['port'];
} else {
$port = '';
}
$path = $urlItems['path'];
if (isset($urlItems['query'])) {
$query = $urlItems['query'];
} else {
$query = '';
}
//write request uri
$toSignStr = $method . ' ' . $path;
if (!empty($query)) {
$toSignStr .= '?' . $query;
}
//write host and port
$toSignStr .= "\nHost: " . $host;
if (!empty($port)) {
$toSignStr .= ":" . $port;
}
//write content type
if (!empty($contentType)) {
$toSignStr .= "\nContent-Type: " . $contentType;
}
$toSignStr .= "\n\n";
//write body
if (!empty($body)) {
$toSignStr .= $body;
}
$sign = $this->sign($toSignStr);
$auth = 'Qiniu ' . $sign;
return array('Authorization' => $auth);
}
}

View File

@ -0,0 +1,191 @@
<?php
namespace app\common\Qiniu\Cdn;
use app\common\Qiniu\Auth;
use app\common\Qiniu\Http\Error;
use app\common\Qiniu\Http\Client;
final class CdnManager
{
private $auth;
private $server;
public function __construct(Auth $auth)
{
$this->auth = $auth;
$this->server = 'http://fusion.qiniuapi.com';
}
/**
* @param array $urls 待刷新的文件链接数组
* @return array
*/
public function refreshUrls(array $urls)
{
return $this->refreshUrlsAndDirs($urls, array());
}
/**
* @param array $dirs 待刷新的文件链接数组
* @return array
* 目前客户默认没有目录刷新权限刷新会有400038报错参考https://developer.qiniu.com/fusion/api/1229/cache-refresh
* 需要刷新目录请工单联系技术支持 https://support.qiniu.com/tickets/category
*/
public function refreshDirs(array $dirs)
{
return $this->refreshUrlsAndDirs(array(), $dirs);
}
/**
* @param array $urls 待刷新的文件链接数组
* @param array $dirs 待刷新的目录链接数组
*
* @return array 刷新的请求回复和错误,参考 examples/cdn_manager.php 代码
* @link http://developer.qiniu.com/article/fusion/api/refresh.html
*
* 目前客户默认没有目录刷新权限刷新会有400038报错参考https://developer.qiniu.com/fusion/api/1229/cache-refresh
* 需要刷新目录请工单联系技术支持 https://support.qiniu.com/tickets/category
*/
public function refreshUrlsAndDirs(array $urls, array $dirs)
{
$req = array();
if (!empty($urls)) {
$req['urls'] = $urls;
}
if (!empty($dirs)) {
$req['dirs'] = $dirs;
}
$url = $this->server . '/v2/tune/refresh';
$body = json_encode($req);
return $this->post($url, $body);
}
/**
* @param array $urls 待预取的文件链接数组
*
* @return array 预取的请求回复和错误,参考 examples/cdn_manager.php 代码
*
* @link http://developer.qiniu.com/article/fusion/api/refresh.html
*/
public function prefetchUrls(array $urls)
{
$req = array(
'urls' => $urls,
);
$url = $this->server . '/v2/tune/prefetch';
$body = json_encode($req);
return $this->post($url, $body);
}
/**
* @param array $domains 待获取带宽数据的域名数组
* @param string $startDate 开始的日期,格式类似 2017-01-01
* @param string $endDate 结束的日期,格式类似 2017-01-01
* @param string $granularity 获取数据的时间间隔,可以是 5min, hour 或者 day
*
* @return array 带宽数据和错误信息,参考 examples/cdn_manager.php 代码
*
* @link http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html
*/
public function getBandwidthData(array $domains, $startDate, $endDate, $granularity)
{
$req = array();
$req['domains'] = implode(';', $domains);
$req['startDate'] = $startDate;
$req['endDate'] = $endDate;
$req['granularity'] = $granularity;
$url = $this->server . '/v2/tune/bandwidth';
$body = json_encode($req);
return $this->post($url, $body);
}
/**
* @param array $domains 待获取流量数据的域名数组
* @param string $startDate 开始的日期,格式类似 2017-01-01
* @param string $endDate 结束的日期,格式类似 2017-01-01
* @param string $granularity 获取数据的时间间隔,可以是 5min, hour 或者 day
*
* @return array 流量数据和错误信息,参考 examples/cdn_manager.php 代码
*
* @link http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html
*/
public function getFluxData(array $domains, $startDate, $endDate, $granularity)
{
$req = array();
$req['domains'] = implode(';', $domains);
$req['startDate'] = $startDate;
$req['endDate'] = $endDate;
$req['granularity'] = $granularity;
$url = $this->server . '/v2/tune/flux';
$body = json_encode($req);
return $this->post($url, $body);
}
/**
* @param array $domains 待获取日志下载链接的域名数组
* @param string $logDate 获取指定日期的日志下载链接,格式类似 2017-01-01
*
* @return array 日志下载链接数据和错误信息,参考 examples/cdn_manager.php 代码
*
* @link http://developer.qiniu.com/article/fusion/api/log.html
*/
public function getCdnLogList(array $domains, $logDate)
{
$req = array();
$req['domains'] = implode(';', $domains);
$req['day'] = $logDate;
$url = $this->server . '/v2/tune/log/list';
$body = json_encode($req);
return $this->post($url, $body);
}
private function post($url, $body)
{
$headers = $this->auth->authorization($url, $body, 'application/json');
$headers['Content-Type'] = 'application/json';
$ret = Client::post($url, $body, $headers);
if (!$ret->ok()) {
return array(null, new Error($url, $ret));
}
$r = ($ret->body === null) ? array() : $ret->json();
return array($r, null);
}
/**
* 构建时间戳防盗链鉴权的访问外链
*
* @param string $rawUrl 需要签名的资源url
* @param string $encryptKey 时间戳防盗链密钥
* @param string $durationInSeconds 链接的有效期(以秒为单位)
*
* @return string 带鉴权信息的资源外链,参考 examples/cdn_timestamp_antileech.php 代码
*/
public static function createTimestampAntiLeechUrl($rawUrl, $encryptKey, $durationInSeconds)
{
$parsedUrl = parse_url($rawUrl);
$deadline = time() + $durationInSeconds;
$expireHex = dechex($deadline);
$path = isset($parsedUrl['path']) ? $parsedUrl['path'] : '';
$path = implode('/', array_map('rawurlencode', explode('/', $path)));
$strToSign = $encryptKey . $path . $expireHex;
$signStr = md5($strToSign);
if (isset($parsedUrl['query'])) {
$signedUrl = $rawUrl . '&sign=' . $signStr . '&t=' . $expireHex;
} else {
$signedUrl = $rawUrl . '?sign=' . $signStr . '&t=' . $expireHex;
}
return $signedUrl;
}
}

View File

@ -0,0 +1,137 @@
<?php
namespace app\common\Qiniu;
final class Config
{
const SDK_VER = '7.2.7';
const BLOCK_SIZE = 4194304; //4*1024*1024 分块上传块大小,该参数为接口规格,不能修改
const RSF_HOST = 'rsf.qiniu.com';
const API_HOST = 'api.qiniu.com';
const RS_HOST = 'rs.qiniu.com'; //RS Host
const UC_HOST = 'https://api.qiniu.com'; //UC Host
const RTCAPI_HOST = 'http://rtc.qiniuapi.com';
const ARGUS_HOST = 'argus.atlab.ai';
const RTCAPI_VERSION = 'v3';
// Zone 空间对应的机房
public $zone;
//BOOL 是否使用https域名
public $useHTTPS;
//BOOL 是否使用CDN加速上传域名
public $useCdnDomains;
// Zone Cache
private $zoneCache;
// 构造函数
public function __construct(Zone $z = null)
{
$this->zone = $z;
$this->useHTTPS = false;
$this->useCdnDomains = false;
$this->zoneCache = array();
}
public function getUpHost($accessKey, $bucket)
{
$zone = $this->getZone($accessKey, $bucket);
if ($this->useHTTPS === true) {
$scheme = "https://";
} else {
$scheme = "http://";
}
$host = $zone->srcUpHosts[0];
if ($this->useCdnDomains === true) {
$host = $zone->cdnUpHosts[0];
}
return $scheme . $host;
}
public function getUpBackupHost($accessKey, $bucket)
{
$zone = $this->getZone($accessKey, $bucket);
if ($this->useHTTPS === true) {
$scheme = "https://";
} else {
$scheme = "http://";
}
$host = $zone->cdnUpHosts[0];
if ($this->useCdnDomains === true) {
$host = $zone->srcUpHosts[0];
}
return $scheme . $host;
}
public function getRsHost($accessKey, $bucket)
{
$zone = $this->getZone($accessKey, $bucket);
if ($this->useHTTPS === true) {
$scheme = "https://";
} else {
$scheme = "http://";
}
return $scheme . $zone->rsHost;
}
public function getRsfHost($accessKey, $bucket)
{
$zone = $this->getZone($accessKey, $bucket);
if ($this->useHTTPS === true) {
$scheme = "https://";
} else {
$scheme = "http://";
}
return $scheme . $zone->rsfHost;
}
public function getIovipHost($accessKey, $bucket)
{
$zone = $this->getZone($accessKey, $bucket);
if ($this->useHTTPS === true) {
$scheme = "https://";
} else {
$scheme = "http://";
}
return $scheme . $zone->iovipHost;
}
public function getApiHost($accessKey, $bucket)
{
$zone = $this->getZone($accessKey, $bucket);
if ($this->useHTTPS === true) {
$scheme = "https://";
} else {
$scheme = "http://";
}
return $scheme . $zone->apiHost;
}
private function getZone($accessKey, $bucket)
{
$cacheId = "$accessKey:$bucket";
if (isset($this->zoneCache[$cacheId])) {
$zone = $this->zoneCache[$cacheId];
} elseif (isset($this->zone)) {
$zone = $this->zone;
$this->zoneCache[$cacheId] = $zone;
} else {
$zone = Zone::queryZone($accessKey, $bucket);
$this->zoneCache[$cacheId] = $zone;
}
return $zone;
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace app\common\Qiniu;
use app\common\Qiniu\Config;
final class Etag
{
private static function packArray($v, $a)
{
return call_user_func_array('pack', array_merge(array($v), (array)$a));
}
private static function blockCount($fsize)
{
return intval(($fsize + (Config::BLOCK_SIZE - 1)) / Config::BLOCK_SIZE);
}
private static function calcSha1($data)
{
$sha1Str = sha1($data, true);
$err = error_get_last();
if ($err !== null) {
return array(null, $err);
}
$byteArray = unpack('C*', $sha1Str);
return array($byteArray, null);
}
public static function sum($filename)
{
$fhandler = fopen($filename, 'r');
$err = error_get_last();
if ($err !== null) {
return array(null, $err);
}
$fstat = fstat($fhandler);
$fsize = $fstat['size'];
if ((int)$fsize === 0) {
fclose($fhandler);
return array('Fto5o-5ea0sNMlW_75VgGJCv2AcJ', null);
}
$blockCnt = self::blockCount($fsize);
$sha1Buf = array();
if ($blockCnt <= 1) {
array_push($sha1Buf, 0x16);
$fdata = fread($fhandler, Config::BLOCK_SIZE);
if ($err !== null) {
fclose($fhandler);
return array(null, $err);
}
list($sha1Code,) = self::calcSha1($fdata);
$sha1Buf = array_merge($sha1Buf, $sha1Code);
} else {
array_push($sha1Buf, 0x96);
$sha1BlockBuf = array();
for ($i = 0; $i < $blockCnt; $i++) {
$fdata = fread($fhandler, Config::BLOCK_SIZE);
list($sha1Code, $err) = self::calcSha1($fdata);
if ($err !== null) {
fclose($fhandler);
return array(null, $err);
}
$sha1BlockBuf = array_merge($sha1BlockBuf, $sha1Code);
}
$tmpData = self::packArray('C*', $sha1BlockBuf);
list($sha1Final,) = self::calcSha1($tmpData);
$sha1Buf = array_merge($sha1Buf, $sha1Final);
}
$etag = \Qiniu\base64_urlSafeEncode(self::packArray('C*', $sha1Buf));
return array($etag, null);
}
}

View File

@ -0,0 +1,156 @@
<?php
namespace app\common\Qiniu\Http;
use app\common\Qiniu\Config;
use app\common\Qiniu\Http\Request;
use app\common\Qiniu\Http\Response;
final class Client
{
public static function get($url, array $headers = array())
{
$request = new Request('GET', $url, $headers);
return self::sendRequest($request);
}
public static function delete($url, array $headers = array())
{
$request = new Request('DELETE', $url, $headers);
return self::sendRequest($request);
}
public static function post($url, $body, array $headers = array())
{
$request = new Request('POST', $url, $headers, $body);
return self::sendRequest($request);
}
public static function multipartPost(
$url,
$fields,
$name,
$fileName,
$fileBody,
$mimeType = null,
array $headers = array()
) {
$data = array();
$mimeBoundary = md5(microtime());
foreach ($fields as $key => $val) {
array_push($data, '--' . $mimeBoundary);
array_push($data, "Content-Disposition: form-data; name=\"$key\"");
array_push($data, '');
array_push($data, $val);
}
array_push($data, '--' . $mimeBoundary);
$finalMimeType = empty($mimeType) ? 'application/octet-stream' : $mimeType;
$finalFileName = self::escapeQuotes($fileName);
array_push($data, "Content-Disposition: form-data; name=\"$name\"; filename=\"$finalFileName\"");
array_push($data, "Content-Type: $finalMimeType");
array_push($data, '');
array_push($data, $fileBody);
array_push($data, '--' . $mimeBoundary . '--');
array_push($data, '');
$body = implode("\r\n", $data);
$contentType = 'multipart/form-data; boundary=' . $mimeBoundary;
$headers['Content-Type'] = $contentType;
$request = new Request('POST', $url, $headers, $body);
return self::sendRequest($request);
}
private static function userAgent()
{
$sdkInfo = "QiniuPHP/" . Config::SDK_VER;
$systemInfo = php_uname("s");
$machineInfo = php_uname("m");
$envInfo = "($systemInfo/$machineInfo)";
$phpVer = phpversion();
$ua = "$sdkInfo $envInfo PHP/$phpVer";
return $ua;
}
public static function sendRequest($request)
{
$t1 = microtime(true);
$ch = curl_init();
$options = array(
CURLOPT_USERAGENT => self::userAgent(),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_HEADER => true,
CURLOPT_NOBODY => false,
CURLOPT_CUSTOMREQUEST => $request->method,
CURLOPT_URL => $request->url,
);
// Handle open_basedir & safe mode
if (!ini_get('safe_mode') && !ini_get('open_basedir')) {
$options[CURLOPT_FOLLOWLOCATION] = true;
}
if (!empty($request->headers)) {
$headers = array();
foreach ($request->headers as $key => $val) {
array_push($headers, "$key: $val");
}
$options[CURLOPT_HTTPHEADER] = $headers;
}
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
if (!empty($request->body)) {
$options[CURLOPT_POSTFIELDS] = $request->body;
}
curl_setopt_array($ch, $options);
$result = curl_exec($ch);
$t2 = microtime(true);
$duration = round($t2 - $t1, 3);
$ret = curl_errno($ch);
if ($ret !== 0) {
$r = new Response(-1, $duration, array(), null, curl_error($ch));
curl_close($ch);
return $r;
}
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = self::parseHeaders(substr($result, 0, $header_size));
$body = substr($result, $header_size);
curl_close($ch);
return new Response($code, $duration, $headers, $body, null);
}
private static function parseHeaders($raw)
{
$headers = array();
$headerLines = explode("\r\n", $raw);
foreach ($headerLines as $line) {
$headerLine = trim($line);
$kv = explode(':', $headerLine);
if (count($kv) > 1) {
$kv[0] =self::ucwordsHyphen($kv[0]);
$headers[$kv[0]] = trim($kv[1]);
}
}
return $headers;
}
private static function escapeQuotes($str)
{
$find = array("\\", "\"");
$replace = array("\\\\", "\\\"");
return str_replace($find, $replace, $str);
}
private static function ucwordsHyphen($str)
{
return str_replace('- ', '-', ucwords(str_replace('-', '- ', $str)));
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace app\common\Qiniu\Http;
/**
* 七牛业务请求逻辑错误封装类主要用来解析API请求返回如下的内容
* <pre>
* {"error" : "detailed error message"}
* </pre>
*/
final class Error
{
private $url;
private $response;
public function __construct($url, $response)
{
$this->url = $url;
$this->response = $response;
}
public function code()
{
return $this->response->statusCode;
}
public function getResponse()
{
return $this->response;
}
public function message()
{
return $this->response->error;
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace app\common\Qiniu\Http;
final class Request
{
public $url;
public $headers;
public $body;
public $method;
public function __construct($method, $url, array $headers = array(), $body = null)
{
$this->method = strtoupper($method);
$this->url = $url;
$this->headers = $headers;
$this->body = $body;
}
}

View File

@ -0,0 +1,176 @@
<?php
namespace app\common\Qiniu\Http;
/**
* HTTP response Object
*/
final class Response
{
public $statusCode;
public $headers;
public $body;
public $error;
private $jsonData;
public $duration;
/** @var array Mapping of status codes to reason phrases */
private static $statusTexts = array(
100 => 'Continue',
101 => 'Switching Protocols',
102 => 'Processing',
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
207 => 'Multi-Status',
208 => 'Already Reported',
226 => 'IM Used',
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
307 => 'Temporary Redirect',
308 => 'Permanent Redirect',
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Requested Range Not Satisfiable',
417 => 'Expectation Failed',
422 => 'Unprocessable Entity',
423 => 'Locked',
424 => 'Failed Dependency',
425 => 'Reserved for WebDAV advanced collections expired proposal',
426 => 'Upgrade required',
428 => 'Precondition Required',
429 => 'Too Many Requests',
431 => 'Request Header Fields Too Large',
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
506 => 'Variant Also Negotiates (Experimental)',
507 => 'Insufficient Storage',
508 => 'Loop Detected',
510 => 'Not Extended',
511 => 'Network Authentication Required',
);
/**
* @param int $code 状态码
* @param double $duration 请求时长
* @param array $headers 响应头部
* @param string $body 响应内容
* @param string $error 错误描述
*/
public function __construct($code, $duration, array $headers = array(), $body = null, $error = null)
{
$this->statusCode = $code;
$this->duration = $duration;
$this->headers = $headers;
$this->body = $body;
$this->error = $error;
$this->jsonData = null;
if ($error !== null) {
return;
}
if ($body === null) {
if ($code >= 400) {
$this->error = self::$statusTexts[$code];
}
return;
}
if (self::isJson($headers)) {
try {
$jsonData = self::bodyJson($body);
if ($code >= 400) {
$this->error = $body;
if ($jsonData['error'] !== null) {
$this->error = $jsonData['error'];
}
}
$this->jsonData = $jsonData;
} catch (\InvalidArgumentException $e) {
$this->error = $body;
if ($code >= 200 && $code < 300) {
$this->error = $e->getMessage();
}
}
} elseif ($code >= 400) {
$this->error = $body;
}
return;
}
public function json()
{
return $this->jsonData;
}
private static function bodyJson($body)
{
return json_decode((string) $body, true, 512);
}
public function xVia()
{
$via = $this->headers['X-Via'];
if ($via === null) {
$via = $this->headers['X-Px'];
}
if ($via === null) {
$via = $this->headers['Fw-Via'];
}
return $via;
}
public function xLog()
{
return $this->headers['X-Log'];
}
public function xReqId()
{
return $this->headers['X-Reqid'];
}
public function ok()
{
return $this->statusCode >= 200 && $this->statusCode < 300 && $this->error === null;
}
public function needRetry()
{
$code = $this->statusCode;
if ($code < 0 || ($code / 100 === 5 and $code !== 579) || $code === 996) {
return true;
}
}
private static function isJson($headers)
{
return array_key_exists('Content-Type', $headers) &&
strpos($headers['Content-Type'], 'application/json') === 0;
}
}

View File

@ -0,0 +1,282 @@
<?php
namespace app\common\Qiniu\Processing;
use app\common\Qiniu;
/**
* 主要涉及图片链接拼接
*
* @link http://developer.qiniu.com/code/v6/api/kodo-api/image/imageview2.html
*/
final class ImageUrlBuilder
{
/**
* mode合法范围值
*
* @var array
*/
protected $modeArr = array(0, 1, 2, 3, 4, 5);
/**
* format合法值
*
* @var array
*/
protected $formatArr = array('psd', 'jpeg', 'png', 'gif', 'webp', 'tiff', 'bmp');
/**
* 水印图片位置合法值
*
* @var array
*/
protected $gravityArr = array('NorthWest', 'North', 'NorthEast',
'West', 'Center', 'East', 'SouthWest', 'South', 'SouthEast');
/**
* 缩略图链接拼接
*
* @param string $url 图片链接
* @param int $mode 缩略模式
* @param int $width 宽度
* @param int $height 长度
* @param string $format 输出类型
* @param int $quality 图片质量
* @param int $interlace 是否支持渐进显示
* @param int $ignoreError 忽略结果
* @return string
* @link http://developer.qiniu.com/code/v6/api/kodo-api/image/imageview2.html
* @author Sherlock Ren <sherlock_ren@icloud.com>
*/
public function thumbnail(
$url,
$mode,
$width,
$height,
$format = null,
$interlace = null,
$quality = null,
$ignoreError = 1
) {
// url合法效验
if (!$this->isUrl($url)) {
return $url;
}
// 参数合法性效验
if (!in_array(intval($mode), $this->modeArr, true)) {
return $url;
}
if (!$width || !$height) {
return $url;
}
$thumbStr = 'imageView2/' . $mode . '/w/' . $width . '/h/' . $height . '/';
// 拼接输出格式
if (!is_null($format)
&& in_array($format, $this->formatArr)
) {
$thumbStr .= 'format/' . $format . '/';
}
// 拼接渐进显示
if (!is_null($interlace)
&& in_array(intval($interlace), array(0, 1), true)
) {
$thumbStr .= 'interlace/' . $interlace . '/';
}
// 拼接图片质量
if (!is_null($quality)
&& intval($quality) >= 0
&& intval($quality) <= 100
) {
$thumbStr .= 'q/' . $quality . '/';
}
$thumbStr .= 'ignore-error/' . $ignoreError . '/';
// 如果有query_string用|线分割实现多参数
return $url . ($this->hasQuery($url) ? '|' : '?') . $thumbStr;
}
/**
* 图片水印
*
* @param string $url 图片链接
* @param string $image 水印图片链接
* @param numeric $dissolve 透明度
* @param string $gravity 水印位置
* @param numeric $dx 横轴边距
* @param numeric $dy 纵轴边距
* @param numeric $watermarkScale 自适应原图的短边比例
* @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html
* @return string
* @author Sherlock Ren <sherlock_ren@icloud.com>
*/
public function waterImg(
$url,
$image,
$dissolve = 100,
$gravity = 'SouthEast',
$dx = null,
$dy = null,
$watermarkScale = null
) {
// url合法效验
if (!$this->isUrl($url)) {
return $url;
}
$waterStr = 'watermark/1/image/' . \Qiniu\base64_urlSafeEncode($image) . '/';
// 拼接水印透明度
if (is_numeric($dissolve)
&& $dissolve <= 100
) {
$waterStr .= 'dissolve/' . $dissolve . '/';
}
// 拼接水印位置
if (in_array($gravity, $this->gravityArr, true)) {
$waterStr .= 'gravity/' . $gravity . '/';
}
// 拼接横轴边距
if (!is_null($dx)
&& is_numeric($dx)
) {
$waterStr .= 'dx/' . $dx . '/';
}
// 拼接纵轴边距
if (!is_null($dy)
&& is_numeric($dy)
) {
$waterStr .= 'dy/' . $dy . '/';
}
// 拼接自适应原图的短边比例
if (!is_null($watermarkScale)
&& is_numeric($watermarkScale)
&& $watermarkScale > 0
&& $watermarkScale < 1
) {
$waterStr .= 'ws/' . $watermarkScale . '/';
}
// 如果有query_string用|线分割实现多参数
return $url . ($this->hasQuery($url) ? '|' : '?') . $waterStr;
}
/**
* 文字水印
*
* @param string $url 图片链接
* @param string $text 文字
* @param string $font 文字字体
* @param string $fontSize 文字字号
* @param string $fontColor 文字颜色
* @param numeric $dissolve 透明度
* @param string $gravity 水印位置
* @param numeric $dx 横轴边距
* @param numeric $dy 纵轴边距
* @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html#text-watermark
* @return string
* @author Sherlock Ren <sherlock_ren@icloud.com>
*/
public function waterText(
$url,
$text,
$font = '黑体',
$fontSize = 0,
$fontColor = null,
$dissolve = 100,
$gravity = 'SouthEast',
$dx = null,
$dy = null
) {
// url合法效验
if (!$this->isUrl($url)) {
return $url;
}
$waterStr = 'watermark/2/text/'
. \Qiniu\base64_urlSafeEncode($text) . '/font/'
. \Qiniu\base64_urlSafeEncode($font) . '/';
// 拼接文字大小
if (is_int($fontSize)) {
$waterStr .= 'fontsize/' . $fontSize . '/';
}
// 拼接文字颜色
if (!is_null($fontColor)
&& $fontColor
) {
$waterStr .= 'fill/' . \Qiniu\base64_urlSafeEncode($fontColor) . '/';
}
// 拼接水印透明度
if (is_numeric($dissolve)
&& $dissolve <= 100
) {
$waterStr .= 'dissolve/' . $dissolve . '/';
}
// 拼接水印位置
if (in_array($gravity, $this->gravityArr, true)) {
$waterStr .= 'gravity/' . $gravity . '/';
}
// 拼接横轴边距
if (!is_null($dx)
&& is_numeric($dx)
) {
$waterStr .= 'dx/' . $dx . '/';
}
// 拼接纵轴边距
if (!is_null($dy)
&& is_numeric($dy)
) {
$waterStr .= 'dy/' . $dy . '/';
}
// 如果有query_string用|线分割实现多参数
return $url . ($this->hasQuery($url) ? '|' : '?') . $waterStr;
}
/**
* 效验url合法性
*
* @param string $url url链接
* @return string
* @author Sherlock Ren <sherlock_ren@icloud.com>
*/
protected function isUrl($url)
{
$urlArr = parse_url($url);
return $urlArr['scheme']
&& in_array($urlArr['scheme'], array('http', 'https'))
&& $urlArr['host']
&& $urlArr['path'];
}
/**
* 检测是否有query
*
* @param string $url url链接
* @return string
* @author Sherlock Ren <sherlock_ren@icloud.com>
*/
protected function hasQuery($url)
{
$urlArr = parse_url($url);
return !empty($urlArr['query']);
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace app\common\Qiniu\Processing;
use app\common\Qiniu\Http\Client;
use app\common\Qiniu\Http\Error;
final class Operation
{
private $auth;
private $token_expire;
private $domain;
public function __construct($domain, $auth = null, $token_expire = 3600)
{
$this->auth = $auth;
$this->domain = $domain;
$this->token_expire = $token_expire;
}
/**
* 对资源文件进行处理
*
* @param $key 待处理的资源文件名
* @param $fops string|array fop操作多次fop操作以array的形式传入。
* eg. imageView2/1/w/200/h/200, imageMogr2/thumbnail/!75px
*
* @return array 文件处理后的结果及错误。
*
* @link http://developer.qiniu.com/docs/v6/api/reference/fop/
*/
public function execute($key, $fops)
{
$url = $this->buildUrl($key, $fops);
$resp = Client::get($url);
if (!$resp->ok()) {
return array(null, new Error($url, $resp));
}
if ($resp->json() !== null) {
return array($resp->json(), null);
}
return array($resp->body, null);
}
public function buildUrl($key, $fops, $protocol = 'http')
{
if (is_array($fops)) {
$fops = implode('|', $fops);
}
$url = $protocol . "://$this->domain/$key?$fops";
if ($this->auth !== null) {
$url = $this->auth->privateDownloadUrl($url, $this->token_expire);
}
return $url;
}
}

View File

@ -0,0 +1,94 @@
<?php
namespace app\common\Qiniu\Processing;
use app\common\Qiniu\Config;
use app\common\Qiniu\Http\Client;
use app\common\Qiniu\Http\Error;
use app\common\Qiniu\Processing\Operation;
/**
* 持久化处理类,该类用于主动触发异步持久化操作.
*
* @link http://developer.qiniu.com/docs/v6/api/reference/fop/pfop/pfop.html
*/
final class PersistentFop
{
/**
* @var 账号管理密钥对Auth对象
*/
private $auth;
/*
* @var 配置对象Config 对象
* */
private $config;
public function __construct($auth, $config = null)
{
$this->auth = $auth;
if ($config == null) {
$this->config = new Config();
} else {
$this->config = $config;
}
}
/**
* 对资源文件进行异步持久化处理
* @param $bucket 资源所在空间
* @param $key 待处理的源文件
* @param $fops string|array 待处理的pfop操作多个pfop操作以array的形式传入。
* eg. avthumb/mp3/ab/192k, vframe/jpg/offset/7/w/480/h/360
* @param $pipeline 资源处理队列
* @param $notify_url 处理结果通知地址
* @param $force 是否强制执行一次新的指令
*
*
* @return array 返回持久化处理的persistentId, 和返回的错误。
*
* @link http://developer.qiniu.com/docs/v6/api/reference/fop/
*/
public function execute($bucket, $key, $fops, $pipeline = null, $notify_url = null, $force = false)
{
if (is_array($fops)) {
$fops = implode(';', $fops);
}
$params = array('bucket' => $bucket, 'key' => $key, 'fops' => $fops);
\Qiniu\setWithoutEmpty($params, 'pipeline', $pipeline);
\Qiniu\setWithoutEmpty($params, 'notifyURL', $notify_url);
if ($force) {
$params['force'] = 1;
}
$data = http_build_query($params);
$scheme = "http://";
if ($this->config->useHTTPS === true) {
$scheme = "https://";
}
$url = $scheme . Config::API_HOST . '/pfop/';
$headers = $this->auth->authorization($url, $data, 'application/x-www-form-urlencoded');
$headers['Content-Type'] = 'application/x-www-form-urlencoded';
$response = Client::post($url, $data, $headers);
if (!$response->ok()) {
return array(null, new Error($url, $response));
}
$r = $response->json();
$id = $r['persistentId'];
return array($id, null);
}
public function status($id)
{
$scheme = "http://";
if ($this->config->useHTTPS === true) {
$scheme = "https://";
}
$url = $scheme . Config::API_HOST . "/status/get/prefop?id=$id";
$response = Client::get($url);
if (!$response->ok()) {
return array(null, new Error($url, $response));
}
return array($response->json(), null);
}
}

View File

@ -0,0 +1,204 @@
<?php
namespace app\common\Qiniu\Rtc;
use app\common\Qiniu\Http\Client;
use app\common\Qiniu\Http\Error;
use app\common\Qiniu\Config;
use app\common\Qiniu\Auth;
class AppClient
{
private $auth;
private $baseURL;
public function __construct(Auth $auth)
{
$this->auth = $auth;
$this->baseURL = sprintf("%s/%s/apps", Config::RTCAPI_HOST, Config::RTCAPI_VERSION);
}
/*
* hub: 直播空间名
* title: app 的名称 注意Title 不是唯一标识,重复 create 动作将生成多个 app
* maxUsers人数限制
* NoAutoKickUser: bool 类型,可选,禁止自动踢人(抢流)。默认为 false
即同一个身份的 client (app/room/user) ,新的连麦请求可以成功,旧连接被关闭。
*/
public function createApp($hub, $title, $maxUsers = null, $noAutoKickUser = null)
{
$params['hub'] = $hub;
$params['title'] = $title;
if (!empty($maxUsers)) {
$params['maxUsers'] = $maxUsers;
}
if (!empty($noAutoKickUser)) {
$params['noAutoKickUser'] = $noAutoKickUser;
}
$body = json_encode($params);
$ret = $this->post($this->baseURL, $body);
return $ret;
}
/*
* appId: app 的唯一标识,创建的时候由系统生成。
* Title: app 的名称, 可选。
* Hub: 绑定的直播 hub可选用于合流后 rtmp 推流。
* MaxUsers: int 类型,可选,连麦房间支持的最大在线人数。
* NoAutoKickUser: bool 类型,可选,禁止自动踢人。
* MergePublishRtmp: 连麦合流转推 RTMP 的配置,可选择。其详细配置包括如下
Enable: 布尔类型,用于开启和关闭所有房间的合流功能。
AudioOnly: 布尔类型,可选,指定是否只合成音频。
Height, Width: int64可选指定合流输出的高和宽默认为 640 x 480
OutputFps: int64可选指定合流输出的帧率默认为 25 fps
OutputKbps: int64可选指定合流输出的码率默认为 1000
URL: 合流后转推旁路直播的地址,可选,支持魔法变量配置按照连麦房间号生成不同的推流地址。如果是转推到七牛直播云,不建议使用该配置。
StreamTitle: 转推七牛直播云的流名,可选,支持魔法变量配置按照连麦房间号生成不同的流名。例如,配置 Hub qn-zhibo ,配置 StreamTitle $(roomName) ,则房间 meeting-001 的合流将会被转推到 rtmp://pili-publish.qn-zhibo.***.com/qn-zhibo/meeting-001地址。详细配置细则,请咨询七牛技术支持。
*/
public function updateApp($appId, $hub, $title, $maxUsers = null, $mergePublishRtmp = null, $noAutoKickUser = null)
{
$url = $this->baseURL . '/' . $appId;
$params['hub'] = $hub;
$params['title'] = $title;
if (!empty($maxUsers)) {
$params['maxUsers'] = $maxUsers;
}
if (!empty($noAutoKickUser)) {
$params['noAutoKickUser'] = $noAutoKickUser;
}
if (!empty($mergePublishRtmp)) {
$params['mergePublishRtmp'] = $mergePublishRtmp;
}
$body = json_encode($params);
$ret = $this->post($url, $body);
return $ret;
}
/*
* appId: app 的唯一标识,创建的时候由系统生成。
*/
public function getApp($appId)
{
$url = $this->baseURL . '/' . $appId;
$ret = $this->get($url);
return $ret;
}
/*
* appId: app 的唯一标识,创建的时候由系统生成
*/
public function deleteApp($appId)
{
$url = $this->baseURL . '/' . $appId;
list(, $err) = $this->delete($url);
return $err;
}
/*
* 获取房间的人数
* appId: app 的唯一标识,创建的时候由系统生成。
* roomName: 操作所查询的连麦房间。
*/
public function listUser($appId, $roomName)
{
$url = sprintf("%s/%s/rooms/%s/users", $this->baseURL, $appId, $roomName);
$ret = $this->get($url);
return $ret;
}
/*
* 踢出玩家
* appId: app 的唯一标识,创建的时候由系统生成。
* roomName: 连麦房间
* userId: 请求加入房间的用户ID
*/
public function kickUser($appId, $roomName, $userId)
{
$url = sprintf("%s/%s/rooms/%s/users/%s", $this->baseURL, $appId, $roomName, $userId);
list(, $err) = $this->delete($url);
return $err;
}
/*
* 获取房间的人数
* appId: app 的唯一标识,创建的时候由系统生成。
* prefix: 所查询房间名的前缀索引,可以为空。
* offset: int 类型,分页查询的位移标记。
* limit: int 类型,此次查询的最大长度。
* GET /v3/apps/<AppID>/rooms?prefix=<RoomNamePrefix>&offset=<Offset>&limit=<Limit>
*/
public function listActiveRooms($appId, $prefix = null, $offset = null, $limit = null)
{
if (isset($prefix)) {
$query['prefix'] = $prefix;
}
if (isset($offset)) {
$query['offset'] = $offset;
}
if (isset($limit)) {
$query['limit'] = $limit;
}
if (isset($query) && !empty($query)) {
$query = '?' . http_build_query($query);
$url = sprintf("%s/%s/rooms%s", $this->baseURL, $appId, $query);
} else {
$url = sprintf("%s/%s/rooms", $this->baseURL, $appId);
}
$ret = $this->get($url);
return $ret;
}
/*
* appId: app 的唯一标识,创建的时候由系统生成。
* roomName: 房间名称,需满足规格 ^[a-zA-Z0-9_-]{3,64}$
* userId: 请求加入房间的用户 ID需满足规格 ^[a-zA-Z0-9_-]{3,50}$
* expireAt: int64 类型鉴权的有效时间传入以秒为单位的64位Unix
绝对时间token 将在该时间后失效。
* permission: 该用户的房间管理权限,"admin" "user",默认为 "user"
当权限角色为 "admin" 时,拥有将其他用户移除出房间等特权.
*/
public function appToken($appId, $roomName, $userId, $expireAt, $permission)
{
$params['appId'] = $appId;
$params['userId'] = $userId;
$params['roomName'] = $roomName;
$params['permission'] = $permission;
$params['expireAt'] = $expireAt;
$appAccessString = json_encode($params);
return $this->auth->signWithData($appAccessString);
}
private function get($url, $cType = null)
{
$rtcToken = $this->auth->authorizationV2($url, "GET", null, $cType);
$rtcToken['Content-Type'] = $cType;
$ret = Client::get($url, $rtcToken);
if (!$ret->ok()) {
return array(null, new Error($url, $ret));
}
return array($ret->json(), null);
}
private function delete($url, $contentType = 'application/json')
{
$rtcToken = $this->auth->authorizationV2($url, "DELETE", null, $contentType);
$rtcToken['Content-Type'] = $contentType;
$ret = Client::delete($url, $rtcToken);
if (!$ret->ok()) {
return array(null, new Error($url, $ret));
}
return array($ret->json(), null);
}
private function post($url, $body, $contentType = 'application/json')
{
$rtcToken = $this->auth->authorizationV2($url, "POST", $body, $contentType);
$rtcToken['Content-Type'] = $contentType;
$ret = Client::post($url, $body, $rtcToken);
if (!$ret->ok()) {
return array(null, new Error($url, $ret));
}
$r = ($ret->body === null) ? array() : $ret->json();
return array($r, null);
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace app\common\Qiniu\Storage;
use app\common\Qiniu\Auth;
use app\common\Qiniu\Config;
use app\common\Qiniu\Zone;
use app\common\Qiniu\Http\Client;
use app\common\Qiniu\Http\Error;
/**
* 主要涉及了鉴黄接口的实现,具体的接口规格可以参考
*
* @link https://developer.qiniu.com/dora/manual/3674/kodo-product-introduction
*/
final class ArgusManager
{
private $auth;
private $config;
public function __construct(Auth $auth, Config $config = null)
{
$this->auth = $auth;
if ($config == null) {
$this->config = new Config();
} else {
$this->config = $config;
}
}
/**
* 视频鉴黄
*
* @param $body body信息
* @param $vid videoID
*
* @return mixed 成功返回NULL失败返回对象Qiniu\Http\Error
* @link https://developer.qiniu.com/dora/manual/4258/video-pulp
*/
public function pulpVideo($body, $vid)
{
$path = '/v1/video/' . $vid;
return $this->arPost($path, $body);
}
private function getArHost()
{
$scheme = "http://";
if ($this->config->useHTTPS == true) {
$scheme = "https://";
}
return $scheme . Config::ARGUS_HOST;
}
private function arPost($path, $body = null)
{
$url = $this->getArHost() . $path;
return $this->post($url, $body);
}
private function post($url, $body)
{
$headers = $this->auth->authorizationV2($url, 'POST', $body, 'application/json');
$headers['Content-Type']='application/json';
$ret = Client::post($url, $body, $headers);
if (!$ret->ok()) {
print($ret->statusCode);
return array(null, new Error($url, $ret));
}
$r = ($ret->body === null) ? array() : $ret->json();
return array($r, null);
}
}

View File

@ -0,0 +1,492 @@
<?php
namespace app\common\Qiniu\Storage;
use app\common\Qiniu\Auth;
use app\common\Qiniu\Config;
use app\common\Qiniu\Zone;
use app\common\Qiniu\Http\Client;
use app\common\Qiniu\Http\Error;
/**
* 主要涉及了空间资源管理及批量操作接口的实现,具体的接口规格可以参考
*
* @link https://developer.qiniu.com/kodo/api/1274/rs
*/
final class BucketManager
{
private $auth;
private $config;
public function __construct(Auth $auth, Config $config = null)
{
$this->auth = $auth;
if ($config == null) {
$this->config = new Config();
} else {
$this->config = $config;
}
}
/**
* 获取指定账号下所有的空间名。
*
* @return string[] 包含所有空间名
*/
public function buckets($shared = true)
{
$includeShared = "false";
if ($shared === true) {
$includeShared = "true";
}
return $this->rsGet('/buckets?shared=' . $includeShared);
}
/**
* 获取指定空间绑定的所有的域名
*
* @return string[] 包含所有空间域名
*/
public function domains($bucket)
{
return $this->apiGet('/v6/domain/list?tbl=' . $bucket);
}
/**
* 获取空间绑定的域名列表
* @return string[] 包含空间绑定的所有域名
*/
/**
* 列取空间的文件列表
*
* @param $bucket 空间名
* @param $prefix 列举前缀
* @param $marker 列举标识符
* @param $limit 单次列举个数限制
* @param $delimiter 指定目录分隔符
*
* @return array 包含文件信息的数组,类似:[
* {
* "hash" => "<Hash string>",
* "key" => "<Key string>",
* "fsize" => "<file size>",
* "putTime" => "<file modify time>"
* },
* ...
* ]
* @link http://developer.qiniu.com/docs/v6/api/reference/rs/list.html
*/
public function listFiles($bucket, $prefix = null, $marker = null, $limit = 1000, $delimiter = null)
{
$query = array('bucket' => $bucket);
setWithoutEmpty($query, 'prefix', $prefix);
setWithoutEmpty($query, 'marker', $marker);
setWithoutEmpty($query, 'limit', $limit);
setWithoutEmpty($query, 'delimiter', $delimiter);
$url = $this->getRsfHost() . '/list?' . http_build_query($query);
return $this->get($url);
}
/**
* 获取资源的元信息,但不返回文件内容
*
* @param $bucket 待获取信息资源所在的空间
* @param $key 待获取资源的文件名
*
* @return array 包含文件信息的数组,类似:
* [
* "hash" => "<Hash string>",
* "key" => "<Key string>",
* "fsize" => <file size>,
* "putTime" => "<file modify time>"
* "fileType" => <file type>
* ]
*
* @link http://developer.qiniu.com/docs/v6/api/reference/rs/stat.html
*/
public function stat($bucket, $key)
{
$path = '/stat/' . entry($bucket, $key);
return $this->rsGet($path);
}
/**
* 删除指定资源
*
* @param $bucket 待删除资源所在的空间
* @param $key 待删除资源的文件名
*
* @return mixed 成功返回NULL失败返回对象Qiniu\Http\Error
* @link http://developer.qiniu.com/docs/v6/api/reference/rs/delete.html
*/
public function delete($bucket, $key)
{
$path = '/delete/' . entry($bucket, $key);
list(, $error) = $this->rsPost($path);
return $error;
}
/**
* 给资源进行重命名本质为move操作。
*
* @param $bucket 待操作资源所在空间
* @param $oldname 待操作资源文件名
* @param $newname 目标资源文件名
*
* @return mixed 成功返回NULL失败返回对象Qiniu\Http\Error
*/
public function rename($bucket, $oldname, $newname)
{
return $this->move($bucket, $oldname, $bucket, $newname);
}
/**
* 给资源进行重命名本质为move操作。
*
* @param $from_bucket 待操作资源所在空间
* @param $from_key 待操作资源文件名
* @param $to_bucket 目标资源空间名
* @param $to_key 目标资源文件名
*
* @return mixed 成功返回NULL失败返回对象Qiniu\Http\Error
* @link http://developer.qiniu.com/docs/v6/api/reference/rs/copy.html
*/
public function copy($from_bucket, $from_key, $to_bucket, $to_key, $force = false)
{
$from = entry($from_bucket, $from_key);
$to = entry($to_bucket, $to_key);
$path = '/copy/' . $from . '/' . $to;
if ($force === true) {
$path .= '/force/true';
}
list(, $error) = $this->rsPost($path);
return $error;
}
/**
* 将资源从一个空间到另一个空间
*
* @param $from_bucket 待操作资源所在空间
* @param $from_key 待操作资源文件名
* @param $to_bucket 目标资源空间名
* @param $to_key 目标资源文件名
*
* @return mixed 成功返回NULL失败返回对象Qiniu\Http\Error
* @link http://developer.qiniu.com/docs/v6/api/reference/rs/move.html
*/
public function move($from_bucket, $from_key, $to_bucket, $to_key, $force = false)
{
$from = entry($from_bucket, $from_key);
$to = entry($to_bucket, $to_key);
$path = '/move/' . $from . '/' . $to;
if ($force) {
$path .= '/force/true';
}
list(, $error) = $this->rsPost($path);
return $error;
}
/**
* 主动修改指定资源的文件类型
*
* @param $bucket 待操作资源所在空间
* @param $key 待操作资源文件名
* @param $mime 待操作文件目标mimeType
*
* @return mixed 成功返回NULL失败返回对象Qiniu\Http\Error
* @link http://developer.qiniu.com/docs/v6/api/reference/rs/chgm.html
*/
public function changeMime($bucket, $key, $mime)
{
$resource = entry($bucket, $key);
$encode_mime = base64_urlSafeEncode($mime);
$path = '/chgm/' . $resource . '/mime/' . $encode_mime;
list(, $error) = $this->rsPost($path);
return $error;
}
/**
* 修改指定资源的存储类型
*
* @param $bucket 待操作资源所在空间
* @param $key 待操作资源文件名
* @param $fileType 待操作文件目标文件类型
*
* @return mixed 成功返回NULL失败返回对象Qiniu\Http\Error
* @link https://developer.qiniu.com/kodo/api/3710/modify-the-file-type
*/
public function changeType($bucket, $key, $fileType)
{
$resource = entry($bucket, $key);
$path = '/chtype/' . $resource . '/type/' . $fileType;
list(, $error) = $this->rsPost($path);
return $error;
}
/**
* 修改文件的存储状态,即禁用状态和启用状态间的的互相转换
*
* @param $bucket 待操作资源所在空间
* @param $key 待操作资源文件名
* @param $status 待操作文件目标文件类型
*
* @return mixed 成功返回NULL失败返回对象Qiniu\Http\Error
* @link https://developer.qiniu.com/kodo/api/4173/modify-the-file-status
*/
public function changeStatus($bucket, $key, $status)
{
$resource = entry($bucket, $key);
$path = '/chstatus/' . $resource . '/status/' . $status;
list(, $error) = $this->rsPost($path);
return $error;
}
/**
* 从指定URL抓取资源并将该资源存储到指定空间中
*
* @param $url 指定的URL
* @param $bucket 目标资源空间
* @param $key 目标资源文件名
*
* @return array 包含已拉取的文件信息。
* 成功时: [
* [
* "hash" => "<Hash string>",
* "key" => "<Key string>"
* ],
* null
* ]
*
* 失败时: [
* null,
* Qiniu/Http/Error
* ]
* @link http://developer.qiniu.com/docs/v6/api/reference/rs/fetch.html
*/
public function fetch($url, $bucket, $key = null)
{
$resource = base64_urlSafeEncode($url);
$to = entry($bucket, $key);
$path = '/fetch/' . $resource . '/to/' . $to;
$ak = $this->auth->getAccessKey();
$ioHost = $this->config->getIovipHost($ak, $bucket);
$url = $ioHost . $path;
return $this->post($url, null);
}
/**
* 从镜像源站抓取资源到空间中,如果空间中已经存在,则覆盖该资源
*
* @param $bucket 待获取资源所在的空间
* @param $key 代获取资源文件名
*
* @return mixed 成功返回NULL失败返回对象Qiniu\Http\Error
* @link http://developer.qiniu.com/docs/v6/api/reference/rs/prefetch.html
*/
public function prefetch($bucket, $key)
{
$resource = entry($bucket, $key);
$path = '/prefetch/' . $resource;
$ak = $this->auth->getAccessKey();
$ioHost = $this->config->getIovipHost($ak, $bucket);
$url = $ioHost . $path;
list(, $error) = $this->post($url, null);
return $error;
}
/**
* 在单次请求中进行多个资源管理操作
*
* @param $operations 资源管理操作数组
*
* @return array 每个资源的处理情况,结果类似:
* [
* { "code" => <HttpCode int>, "data" => <Data> },
* { "code" => <HttpCode int> },
* { "code" => <HttpCode int> },
* { "code" => <HttpCode int> },
* { "code" => <HttpCode int>, "data" => { "error": "<ErrorMessage string>" } },
* ...
* ]
* @link http://developer.qiniu.com/docs/v6/api/reference/rs/batch.html
*/
public function batch($operations)
{
$params = 'op=' . implode('&op=', $operations);
return $this->rsPost('/batch', $params);
}
/**
* 设置文件的生命周期
*
* @param $bucket 设置文件生命周期文件所在的空间
* @param $key 设置文件生命周期文件的文件名
* @param $days 设置该文件多少天后删除,当$days设置为0时表示取消该文件的生命周期
*
* @return Mixed
* @link https://developer.qiniu.com/kodo/api/update-file-lifecycle
*/
public function deleteAfterDays($bucket, $key, $days)
{
$entry = entry($bucket, $key);
$path = "/deleteAfterDays/$entry/$days";
list(, $error) = $this->rsPost($path);
return $error;
}
private function getRsfHost()
{
$scheme = "http://";
if ($this->config->useHTTPS == true) {
$scheme = "https://";
}
return $scheme . Config::RSF_HOST;
}
private function getRsHost()
{
$scheme = "http://";
if ($this->config->useHTTPS == true) {
$scheme = "https://";
}
return $scheme . Config::RS_HOST;
}
private function getApiHost()
{
$scheme = "http://";
if ($this->config->useHTTPS == true) {
$scheme = "https://";
}
return $scheme . Config::API_HOST;
}
private function rsPost($path, $body = null)
{
$url = $this->getRsHost() . $path;
return $this->post($url, $body);
}
private function apiGet($path)
{
$url = $this->getApiHost() . $path;
return $this->get($url);
}
private function rsGet($path)
{
$url = $this->getRsHost() . $path;
return $this->get($url);
}
private function get($url)
{
$headers = $this->auth->authorization($url);
$ret = Client::get($url, $headers);
if (!$ret->ok()) {
return array(null, new Error($url, $ret));
}
return array($ret->json(), null);
}
private function post($url, $body)
{
$headers = $this->auth->authorization($url, $body, 'application/x-www-form-urlencoded');
$ret = Client::post($url, $body, $headers);
if (!$ret->ok()) {
return array(null, new Error($url, $ret));
}
$r = ($ret->body === null) ? array() : $ret->json();
return array($r, null);
}
public static function buildBatchCopy($source_bucket, $key_pairs, $target_bucket, $force)
{
return self::twoKeyBatch('/copy', $source_bucket, $key_pairs, $target_bucket, $force);
}
public static function buildBatchRename($bucket, $key_pairs, $force)
{
return self::buildBatchMove($bucket, $key_pairs, $bucket, $force);
}
public static function buildBatchMove($source_bucket, $key_pairs, $target_bucket, $force)
{
return self::twoKeyBatch('/move', $source_bucket, $key_pairs, $target_bucket, $force);
}
public static function buildBatchDelete($bucket, $keys)
{
return self::oneKeyBatch('/delete', $bucket, $keys);
}
public static function buildBatchStat($bucket, $keys)
{
return self::oneKeyBatch('/stat', $bucket, $keys);
}
public static function buildBatchDeleteAfterDays($bucket, $key_day_pairs)
{
$data = array();
foreach ($key_day_pairs as $key => $day) {
array_push($data, '/deleteAfterDays/' . entry($bucket, $key) . '/' . $day);
}
return $data;
}
public static function buildBatchChangeMime($bucket, $key_mime_pairs)
{
$data = array();
foreach ($key_mime_pairs as $key => $mime) {
array_push($data, '/chgm/' . entry($bucket, $key) . '/mime/' . base64_encode($mime));
}
return $data;
}
public static function buildBatchChangeType($bucket, $key_type_pairs)
{
$data = array();
foreach ($key_type_pairs as $key => $type) {
array_push($data, '/chtype/' . entry($bucket, $key) . '/type/' . $type);
}
return $data;
}
private static function oneKeyBatch($operation, $bucket, $keys)
{
$data = array();
foreach ($keys as $key) {
array_push($data, $operation . '/' . entry($bucket, $key));
}
return $data;
}
private static function twoKeyBatch($operation, $source_bucket, $key_pairs, $target_bucket, $force)
{
if ($target_bucket === null) {
$target_bucket = $source_bucket;
}
$data = array();
$forceOp = "false";
if ($force) {
$forceOp = "true";
}
foreach ($key_pairs as $from_key => $to_key) {
$from = entry($source_bucket, $from_key);
$to = entry($target_bucket, $to_key);
array_push($data, $operation . '/' . $from . '/' . $to . "/force/" . $forceOp);
}
return $data;
}
}

View File

@ -0,0 +1,139 @@
<?php
namespace app\common\Qiniu\Storage;
use app\common\Qiniu\Http\Client;
use app\common\Qiniu\Http\Error;
final class FormUploader
{
/**
* 上传二进制流到七牛, 内部使用
*
* @param $upToken 上传凭证
* @param $key 上传文件名
* @param $data 上传二进制流
* @param $config 上传配置
* @param $params 自定义变量,规格参考
* http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar
* @param $mime 上传数据的mimeType
*
* @return array 包含已上传文件的信息,类似:
* [
* "hash" => "<Hash string>",
* "key" => "<Key string>"
* ]
*/
public static function put(
$upToken,
$key,
$data,
$config,
$params,
$mime,
$fname
) {
$fields = array('token' => $upToken);
if ($key === null) {
$fname='nullkey';
} else {
$fields['key'] = $key;
}
//enable crc32 check by default
$fields['crc32'] = crc32_data($data);
if ($params) {
foreach ($params as $k => $v) {
$fields[$k] = $v;
}
}
list($accessKey, $bucket, $err) = explodeUpToken($upToken);
if ($err != null) {
return array(null, $err);
}
$upHost = $config->getUpHost($accessKey, $bucket);
$response = Client::multipartPost($upHost, $fields, 'file', $fname, $data, $mime);
if (!$response->ok()) {
return array(null, new Error($upHost, $response));
}
return array($response->json(), null);
}
/**
* 上传文件到七牛,内部使用
*
* @param $upToken 上传凭证
* @param $key 上传文件名
* @param $filePath 上传文件的路径
* @param $config 上传配置
* @param $params 自定义变量,规格参考
* http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar
* @param $mime 上传数据的mimeType
*
* @return array 包含已上传文件的信息,类似:
* [
* "hash" => "<Hash string>",
* "key" => "<Key string>"
* ]
*/
public static function putFile(
$upToken,
$key,
$filePath,
$config,
$params,
$mime
) {
$fields = array('token' => $upToken, 'file' => self::createFile($filePath, $mime));
if ($key !== null) {
$fields['key'] = $key;
}
$fields['crc32'] = crc32_file($filePath);
if ($params) {
foreach ($params as $k => $v) {
$fields[$k] = $v;
}
}
$fields['key'] = $key;
$headers = array('Content-Type' => 'multipart/form-data');
list($accessKey, $bucket, $err) = explodeUpToken($upToken);
if ($err != null) {
return array(null, $err);
}
$upHost = $config->getUpHost($accessKey, $bucket);
$response = Client::post($upHost, $fields, $headers);
if (!$response->ok()) {
return array(null, new Error($upHost, $response));
}
return array($response->json(), null);
}
private static function createFile($filename, $mime)
{
// PHP 5.5 introduced a CurlFile object that deprecates the old @filename syntax
// See: https://wiki.php.net/rfc/curl-file-upload
if (function_exists('curl_file_create')) {
return curl_file_create($filename, $mime);
}
// Use the old style if using an older version of PHP
$value = "@{$filename}";
if (!empty($mime)) {
$value .= ';type=' . $mime;
}
return $value;
}
}

View File

@ -0,0 +1,169 @@
<?php
namespace app\common\Qiniu\Storage;
use app\common\Qiniu\Config;
use app\common\Qiniu\Http\Client;
use app\common\Qiniu\Http\Error;
/**
* 断点续上传类, 该类主要实现了断点续上传中的分块上传,
* 以及相应地创建块和创建文件过程.
*
* @link http://developer.qiniu.com/docs/v6/api/reference/up/mkblk.html
* @link http://developer.qiniu.com/docs/v6/api/reference/up/mkfile.html
*/
final class ResumeUploader
{
private $upToken;
private $key;
private $inputStream;
private $size;
private $params;
private $mime;
private $contexts;
private $host;
private $currentUrl;
private $config;
/**
* 上传二进制流到七牛
*
* @param $upToken 上传凭证
* @param $key 上传文件名
* @param $inputStream 上传二进制流
* @param $size 上传流的大小
* @param $params 自定义变量
* @param $mime 上传数据的mimeType
*
* @link http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar
*/
public function __construct(
$upToken,
$key,
$inputStream,
$size,
$params,
$mime,
$config
) {
$this->upToken = $upToken;
$this->key = $key;
$this->inputStream = $inputStream;
$this->size = $size;
$this->params = $params;
$this->mime = $mime;
$this->contexts = array();
$this->config = $config;
list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($upToken);
if ($err != null) {
return array(null, $err);
}
$upHost = $config->getUpHost($accessKey, $bucket);
if ($err != null) {
throw new \Exception($err->message(), 1);
}
$this->host = $upHost;
}
/**
* 上传操作
*/
public function upload($fname)
{
$uploaded = 0;
while ($uploaded < $this->size) {
$blockSize = $this->blockSize($uploaded);
$data = fread($this->inputStream, $blockSize);
if ($data === false) {
throw new \Exception("file read failed", 1);
}
$crc = \Qiniu\crc32_data($data);
$response = $this->makeBlock($data, $blockSize);
$ret = null;
if ($response->ok() && $response->json() != null) {
$ret = $response->json();
}
if ($response->statusCode < 0) {
list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($this->upToken);
if ($err != null) {
return array(null, $err);
}
$upHostBackup = $this->config->getUpBackupHost($accessKey, $bucket);
$this->host = $upHostBackup;
}
if ($response->needRetry() || !isset($ret['crc32']) || $crc != $ret['crc32']) {
$response = $this->makeBlock($data, $blockSize);
$ret = $response->json();
}
if (!$response->ok() || !isset($ret['crc32']) || $crc != $ret['crc32']) {
return array(null, new Error($this->currentUrl, $response));
}
array_push($this->contexts, $ret['ctx']);
$uploaded += $blockSize;
}
return $this->makeFile($fname);
}
/**
* 创建块
*/
private function makeBlock($block, $blockSize)
{
$url = $this->host . '/mkblk/' . $blockSize;
return $this->post($url, $block);
}
private function fileUrl($fname)
{
$url = $this->host . '/mkfile/' . $this->size;
$url .= '/mimeType/' . \Qiniu\base64_urlSafeEncode($this->mime);
if ($this->key != null) {
$url .= '/key/' . \Qiniu\base64_urlSafeEncode($this->key);
}
$url .= '/fname/' . \Qiniu\base64_urlSafeEncode($fname);
if (!empty($this->params)) {
foreach ($this->params as $key => $value) {
$val = \Qiniu\base64_urlSafeEncode($value);
$url .= "/$key/$val";
}
}
return $url;
}
/**
* 创建文件
*/
private function makeFile($fname)
{
$url = $this->fileUrl($fname);
$body = implode(',', $this->contexts);
$response = $this->post($url, $body);
if ($response->needRetry()) {
$response = $this->post($url, $body);
}
if (!$response->ok()) {
return array(null, new Error($this->currentUrl, $response));
}
return array($response->json(), null);
}
private function post($url, $data)
{
$this->currentUrl = $url;
$headers = array('Authorization' => 'UpToken ' . $this->upToken);
return Client::post($url, $data, $headers);
}
private function blockSize($uploaded)
{
if ($this->size < $uploaded + Config::BLOCK_SIZE) {
return $this->size - $uploaded;
}
return Config::BLOCK_SIZE;
}
}

View File

@ -0,0 +1,144 @@
<?php
namespace app\common\Qiniu\Storage;
use app\common\Qiniu\Config;
use app\common\Qiniu\Http\Client;
use app\common\Qiniu\Storage\ResumeUploader;
use app\common\Qiniu\Storage\FormUploader;
/**
* 主要涉及了资源上传接口的实现
*
* @link http://developer.qiniu.com/docs/v6/api/reference/up/
*/
final class UploadManager
{
private $config;
public function __construct(Config $config = null)
{
if ($config === null) {
$config = new Config();
}
$this->config = $config;
}
/**
* 上传二进制流到七牛
*
* @param $upToken 上传凭证
* @param $key 上传文件名
* @param $data 上传二进制流
* @param $params 自定义变量,规格参考
* http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar
* @param $mime 上传数据的mimeType
* @param $checkCrc 是否校验crc32
*
* @return array 包含已上传文件的信息,类似:
* [
* "hash" => "<Hash string>",
* "key" => "<Key string>"
* ]
*/
public function put(
$upToken,
$key,
$data,
$params = null,
$mime = 'application/octet-stream',
$fname = null
) {
$params = self::trimParams($params);
return FormUploader::put(
$upToken,
$key,
$data,
$this->config,
$params,
$mime,
$fname
);
}
/**
* 上传文件到七牛
*
* @param $upToken 上传凭证
* @param $key 上传文件名
* @param $filePath 上传文件的路径
* @param $params 自定义变量,规格参考
* http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar
* @param $mime 上传数据的mimeType
* @param $checkCrc 是否校验crc32
*
* @return array 包含已上传文件的信息,类似:
* [
* "hash" => "<Hash string>",
* "key" => "<Key string>"
* ]
*/
public function putFile(
$upToken,
$key,
$filePath,
$params = null,
$mime = 'application/octet-stream',
$checkCrc = false
) {
$file = fopen($filePath, 'rb');
if ($file === false) {
throw new \Exception("file can not open", 1);
}
$params = self::trimParams($params);
$stat = fstat($file);
$size = $stat['size'];
if ($size <= Config::BLOCK_SIZE) {
$data = fread($file, $size);
fclose($file);
if ($data === false) {
throw new \Exception("file can not read", 1);
}
return FormUploader::put(
$upToken,
$key,
$data,
$this->config,
$params,
$mime,
basename($filePath)
);
}
$up = new ResumeUploader(
$upToken,
$key,
$file,
$size,
$params,
$mime,
$this->config
);
$ret = $up->upload(basename($filePath));
fclose($file);
return $ret;
}
public static function trimParams($params)
{
if ($params === null) {
return null;
}
$ret = array();
foreach ($params as $k => $v) {
$pos1 = strpos($k, 'x:');
$pos2 = strpos($k, 'x-qn-meta-');
if (($pos1 === 0 || $pos2 === 0) && !empty($v)) {
$ret[$k] = $v;
}
}
return $ret;
}
}

View File

@ -0,0 +1,197 @@
<?php
namespace app\common\Qiniu;
use app\common\Qiniu\Http\Client;
use app\common\Qiniu\Http\Error;
final class Zone
{
//源站上传域名
public $srcUpHosts;
//CDN加速上传域名
public $cdnUpHosts;
//资源管理域名
public $rsHost;
//资源列举域名
public $rsfHost;
//资源处理域名
public $apiHost;
//IOVIP域名
public $iovipHost;
//构造一个Zone对象
public function __construct(
$srcUpHosts = array(),
$cdnUpHosts = array(),
$rsHost = "rs.qiniu.com",
$rsfHost = "rsf.qiniu.com",
$apiHost = "api.qiniu.com",
$iovipHost = null
) {
$this->srcUpHosts = $srcUpHosts;
$this->cdnUpHosts = $cdnUpHosts;
$this->rsHost = $rsHost;
$this->rsfHost = $rsfHost;
$this->apiHost = $apiHost;
$this->iovipHost = $iovipHost;
}
//华东机房
public static function zone0()
{
$Zone_z0 = new Zone(
array("up.qiniup.com", 'up-jjh.qiniup.com', 'up-xs.qiniup.com'),
array('upload.qiniup.com', 'upload-jjh.qiniup.com', 'upload-xs.qiniup.com'),
'rs.qbox.me',
'rsf.qbox.me',
'api.qiniu.com',
'iovip.qbox.me'
);
return $Zone_z0;
}
//华东机房内网上传
public static function zoneZ0()
{
$Zone_z01 = new Zone(
array("free-qvm-z0-xs.qiniup.com"),
'rs.qbox.me',
'rsf.qbox.me',
'api.qiniu.com',
'iovip.qbox.me'
);
return $Zone_z01;
}
//华北机房内网上传
public static function zoneZ1()
{
$Zone_z12 = new Zone(
array("free-qvm-z1-zz.qiniup.com"),
"rs-z1.qbox.me",
"rsf-z1.qbox.me",
"api-z1.qiniu.com",
"iovip-z1.qbox.me"
);
return $Zone_z12;
}
//华北机房
public static function zone1()
{
$Zone_z1 = new Zone(
array('up-z1.qiniup.com'),
array('upload-z1.qiniup.com'),
"rs-z1.qbox.me",
"rsf-z1.qbox.me",
"api-z1.qiniu.com",
"iovip-z1.qbox.me"
);
return $Zone_z1;
}
//华南机房
public static function zone2()
{
$Zone_z2 = new Zone(
array('up-z2.qiniup.com', 'up-dg.qiniup.com', 'up-fs.qiniup.com'),
array('upload-z2.qiniup.com', 'upload-dg.qiniup.com', 'upload-fs.qiniup.com'),
"rs-z2.qbox.me",
"rsf-z2.qbox.me",
"api-z2.qiniu.com",
"iovip-z2.qbox.me"
);
return $Zone_z2;
}
//北美机房
public static function zoneNa0()
{
//北美机房
$Zone_na0 = new Zone(
array('up-na0.qiniup.com'),
array('upload-na0.qiniup.com'),
"rs-na0.qbox.me",
"rsf-na0.qbox.me",
"api-na0.qiniu.com",
"iovip-na0.qbox.me"
);
return $Zone_na0;
}
//新加坡机房
public static function zoneAs0()
{
//新加坡机房
$Zone_as0 = new Zone(
array('up-as0.qiniup.com'),
array('upload-as0.qiniup.com'),
"rs-as0.qbox.me",
"rsf-as0.qbox.me",
"api-as0.qiniu.com",
"iovip-as0.qbox.me"
);
return $Zone_as0;
}
/*
* GET /v2/query?ak=<ak>&&bucket=<bucket>
**/
public static function queryZone($ak, $bucket)
{
$zone = new Zone();
$url = Config::UC_HOST . '/v2/query' . "?ak=$ak&bucket=$bucket";
$ret = Client::Get($url);
if (!$ret->ok()) {
return array(null, new Error($url, $ret));
}
$r = ($ret->body === null) ? array() : $ret->json();
//print_r($ret);
//parse zone;
$iovipHost = $r['io']['src']['main'][0];
$zone->iovipHost = $iovipHost;
$accMain = $r['up']['acc']['main'][0];
array_push($zone->cdnUpHosts, $accMain);
if (isset($r['up']['acc']['backup'])) {
foreach ($r['up']['acc']['backup'] as $key => $value) {
array_push($zone->cdnUpHosts, $value);
}
}
$srcMain = $r['up']['src']['main'][0];
array_push($zone->srcUpHosts, $srcMain);
if (isset($r['up']['src']['backup'])) {
foreach ($r['up']['src']['backup'] as $key => $value) {
array_push($zone->srcUpHosts, $value);
}
}
//set specific hosts
if (strstr($zone->iovipHost, "z1") !== false) {
$zone->rsHost = "rs-z1.qbox.me";
$zone->rsfHost = "rsf-z1.qbox.me";
$zone->apiHost = "api-z1.qiniu.com";
} elseif (strstr($zone->iovipHost, "z2") !== false) {
$zone->rsHost = "rs-z2.qbox.me";
$zone->rsfHost = "rsf-z2.qbox.me";
$zone->apiHost = "api-z2.qiniu.com";
} elseif (strstr($zone->iovipHost, "na0") !== false) {
$zone->rsHost = "rs-na0.qbox.me";
$zone->rsfHost = "rsf-na0.qbox.me";
$zone->apiHost = "api-na0.qiniu.com";
} elseif (strstr($zone->iovipHost, "as0") !== false) {
$zone->rsHost = "rs-as0.qbox.me";
$zone->rsfHost = "rsf-as0.qbox.me";
$zone->apiHost = "api-as0.qiniu.com";
} else {
$zone->rsHost = "rs.qbox.me";
$zone->rsfHost = "rsf.qbox.me";
$zone->apiHost = "api.qiniu.com";
}
return $zone;
}
}

View File

@ -0,0 +1,264 @@
<?php
namespace app\common\Qiniu;
use app\common\Qiniu\Config;
if (!defined('QINIU_FUNCTIONS_VERSION')) {
define('QINIU_FUNCTIONS_VERSION', Config::SDK_VER);
/**
* 计算文件的crc32检验码:
*
* @param $file string 待计算校验码的文件路径
*
* @return string 文件内容的crc32校验码
*/
function crc32_file($file)
{
$hash = hash_file('crc32b', $file);
$array = unpack('N', pack('H*', $hash));
return sprintf('%u', $array[1]);
}
/**
* 计算输入流的crc32检验码
*
* @param $data 待计算校验码的字符串
*
* @return string 输入字符串的crc32校验码
*/
function crc32_data($data)
{
$hash = hash('crc32b', $data);
$array = unpack('N', pack('H*', $hash));
return sprintf('%u', $array[1]);
}
/**
* 对提供的数据进行urlsafe的base64编码。
*
* @param string $data 待编码的数据,一般为字符串
*
* @return string 编码后的字符串
* @link http://developer.qiniu.com/docs/v6/api/overview/appendix.html#urlsafe-base64
*/
function base64_urlSafeEncode($data)
{
$find = array('+', '/');
$replace = array('-', '_');
return str_replace($find, $replace, base64_encode($data));
}
/**
* 对提供的urlsafe的base64编码的数据进行解码
*
* @param string $str 待解码的数据,一般为字符串
*
* @return string 解码后的字符串
*/
function base64_urlSafeDecode($str)
{
$find = array('-', '_');
$replace = array('+', '/');
return base64_decode(str_replace($find, $replace, $str));
}
/**
* Wrapper for JSON decode that implements error detection with helpful
* error messages.
*
* @param string $json JSON data to parse
* @param bool $assoc When true, returned objects will be converted
* into associative arrays.
* @param int $depth User specified recursion depth.
*
* @return mixed
* @throws \InvalidArgumentException if the JSON cannot be parsed.
* @link http://www.php.net/manual/en/function.json-decode.php
*/
function json_decode($json, $assoc = false, $depth = 512)
{
static $jsonErrors = array(
JSON_ERROR_DEPTH => 'JSON_ERROR_DEPTH - Maximum stack depth exceeded',
JSON_ERROR_STATE_MISMATCH => 'JSON_ERROR_STATE_MISMATCH - Underflow or the modes mismatch',
JSON_ERROR_CTRL_CHAR => 'JSON_ERROR_CTRL_CHAR - Unexpected control character found',
JSON_ERROR_SYNTAX => 'JSON_ERROR_SYNTAX - Syntax error, malformed JSON',
JSON_ERROR_UTF8 => 'JSON_ERROR_UTF8 - Malformed UTF-8 characters, possibly incorrectly encoded'
);
if (empty($json)) {
return null;
}
$data = \json_decode($json, $assoc, $depth);
if (JSON_ERROR_NONE !== json_last_error()) {
$last = json_last_error();
throw new \InvalidArgumentException(
'Unable to parse JSON data: '
. (isset($jsonErrors[$last])
? $jsonErrors[$last]
: 'Unknown error')
);
}
return $data;
}
/**
* 计算七牛API中的数据格式
*
* @param $bucket 待操作的空间名
* @param $key 待操作的文件名
*
* @return string 符合七牛API规格的数据格式
* @link http://developer.qiniu.com/docs/v6/api/reference/data-formats.html
*/
function entry($bucket, $key)
{
$en = $bucket;
if (!empty($key)) {
$en = $bucket . ':' . $key;
}
return base64_urlSafeEncode($en);
}
/**
* array 辅助方法无值时不set
*
* @param $array 待操作array
* @param $key key
* @param $value value 为null时 不设置
*
* @return array 原来的array便于连续操作
*/
function setWithoutEmpty(&$array, $key, $value)
{
if (!empty($value)) {
$array[$key] = $value;
}
return $array;
}
/**
* 缩略图链接拼接
*
* @param string $url 图片链接
* @param int $mode 缩略模式
* @param int $width 宽度
* @param int $height 长度
* @param string $format 输出类型
* @param int $quality 图片质量
* @param int $interlace 是否支持渐进显示
* @param int $ignoreError 忽略结果
* @return string
* @link http://developer.qiniu.com/code/v6/api/kodo-api/image/imageview2.html
* @author Sherlock Ren <sherlock_ren@icloud.com>
*/
function thumbnail(
$url,
$mode,
$width,
$height,
$format = null,
$quality = null,
$interlace = null,
$ignoreError = 1
) {
static $imageUrlBuilder = null;
if (is_null($imageUrlBuilder)) {
$imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder;
}
return call_user_func_array(array($imageUrlBuilder, 'thumbnail'), func_get_args());
}
/**
* 图片水印
*
* @param string $url 图片链接
* @param string $image 水印图片链接
* @param numeric $dissolve 透明度
* @param string $gravity 水印位置
* @param numeric $dx 横轴边距
* @param numeric $dy 纵轴边距
* @param numeric $watermarkScale 自适应原图的短边比例
* @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html
* @return string
* @author Sherlock Ren <sherlock_ren@icloud.com>
*/
function waterImg(
$url,
$image,
$dissolve = 100,
$gravity = 'SouthEast',
$dx = null,
$dy = null,
$watermarkScale = null
) {
static $imageUrlBuilder = null;
if (is_null($imageUrlBuilder)) {
$imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder;
}
return call_user_func_array(array($imageUrlBuilder, 'waterImg'), func_get_args());
}
/**
* 文字水印
*
* @param string $url 图片链接
* @param string $text 文字
* @param string $font 文字字体
* @param string $fontSize 文字字号
* @param string $fontColor 文字颜色
* @param numeric $dissolve 透明度
* @param string $gravity 水印位置
* @param numeric $dx 横轴边距
* @param numeric $dy 纵轴边距
* @link http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html#text-watermark
* @return string
* @author Sherlock Ren <sherlock_ren@icloud.com>
*/
function waterText(
$url,
$text,
$font = '黑体',
$fontSize = 0,
$fontColor = null,
$dissolve = 100,
$gravity = 'SouthEast',
$dx = null,
$dy = null
) {
static $imageUrlBuilder = null;
if (is_null($imageUrlBuilder)) {
$imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder;
}
return call_user_func_array(array($imageUrlBuilder, 'waterText'), func_get_args());
}
/**
* 从uptoken解析accessKey和bucket
*
* @param $upToken
* @return array(ak,bucket,err=null)
*/
function explodeUpToken($upToken)
{
$items = explode(':', $upToken);
if (count($items) != 3) {
return array(null, null, "invalid uptoken");
}
$accessKey = $items[0];
$putPolicy = json_decode(base64_urlSafeDecode($items[2]));
$scope = $putPolicy->scope;
$scopeItems = explode(':', $scope);
$bucket = $scopeItems[0];
return array($accessKey, $bucket, null);
}
}

View File

@ -0,0 +1,88 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/1/19
* Time: 11:11 AM
*/
namespace app\common;
class SignatureHelper
{
/**
* 生成签名并发起请求
*
* @param $accessKeyId string AccessKeyId (https://ak-console.aliyun.com/)
* @param $accessKeySecret string AccessKeySecret
* @param $domain string API接口所在域名
* @param $params array API具体参数
* @param $security boolean 使用https
* @return bool|\stdClass 返回API接口调用结果当发生错误时返回false
*/
public function request($accessKeyId, $accessKeySecret, $domain, $params, $security=false) {
$apiParams = array_merge(array (
"SignatureMethod" => "HMAC-SHA1",
"SignatureNonce" => uniqid(mt_rand(0,0xffff), true),
"SignatureVersion" => "1.0",
"AccessKeyId" => $accessKeyId,
"Timestamp" => gmdate("Y-m-d\TH:i:s\Z"),
"Format" => "JSON",
), $params);
ksort($apiParams);
$sortedQueryStringTmp = "";
foreach ($apiParams as $key => $value) {
$sortedQueryStringTmp .= "&" . $this->encode($key) . "=" . $this->encode($value);
}
$stringToSign = "GET&%2F&" . $this->encode(substr($sortedQueryStringTmp, 1));
$sign = base64_encode(hash_hmac("sha1", $stringToSign, $accessKeySecret . "&",true));
$signature = $this->encode($sign);
$url = ($security ? 'https' : 'http')."://{$domain}/?Signature={$signature}{$sortedQueryStringTmp}";
try {
$content = $this->fetchContent($url);
return json_decode($content);
} catch( \Exception $e) {
return false;
}
}
private function encode($str)
{
$res = urlencode($str);
$res = preg_replace("/\+/", "%20", $res);
$res = preg_replace("/\*/", "%2A", $res);
$res = preg_replace("/%7E/", "~", $res);
return $res;
}
private function fetchContent($url) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
"x-sdk-client" => "php/2.0.0"
));
if(substr($url, 0,5) == 'https') {
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
}
$rtn = curl_exec($ch);
if($rtn === false) {
trigger_error("[CURL_" . curl_errno($ch) . "]: " . curl_error($ch), E_USER_ERROR);
}
curl_close($ch);
return $rtn;
}
}

View File

@ -0,0 +1,69 @@
# 变更历史
## 1.0.7 - 2021-07-22
[变更细节](../../compare/v1.0.6...v1.0.7)
- 完善`APIv3`及`APIv2`工厂方法初始化说明,推荐优先使用`APIv3`;
## 1.0.6 - 2021-07-21
[变更细节](../../compare/v1.0.5...v1.0.6)
- 调整 `Formatter::nonce` 算法,使用密码学安全的`random_bytes`生产`BASE62`随机字符串;
## 1.0.5 - 2021-07-08
[变更细节](../../compare/v1.0.4...v1.0.5)
- 核心代码全部转入严格类型`declare(strict_types=1)`校验模式;
- 调整 `Authorization` 头格式顺序debug时优先展示关键信息;
- 调整 媒体文件`MediaUtil`类读取文件时,严格二进制读,避免跨平台干扰问题;
- 增加 测试用例覆盖`APIv2`版用法;
## 1.0.4 - 2021-07-05
[变更细节](../../compare/v1.0.3...v1.0.4)
- 修正 `segments` 首字符大写时异常问题;
- 调整 初始入参如果有提供`handler`,透传给了下游客户端问题;
- 增加 `PHP`最低版本说明,相关问题 [#10](https://github.com/wechatpay-apiv3/wechatpay-php/issues/10);
- 增加 测试用例已基本全覆盖`APIv3`版用法;
## 1.0.3 - 2021-06-28
[变更细节](../../compare/v1.0.2...v1.0.3)
- 初始化`jsonBased`入参判断,`平台证书及序列号`结构体内不能含`商户序列号`,相关问题 [#8](https://github.com/wechatpay-apiv3/wechatpay-php/issues/8);
- 修复文档错误,相关 [#7](https://github.com/wechatpay-apiv3/wechatpay-php/issues/7);
- 优化 `github actions`针对PHP7.2单独缓存依赖(`PHP7.2`下只能跑`PHPUnit8``PHP7.3`以上均可跑`PHPUnit9`);
- 增加 `composer test` 命令并集成进 `CI` 内(测试用例持续增加中);
- 修复 `PHPStan` 所有遗留问题;
## 1.0.2 - 2021-06-24
[变更细节](../../compare/v1.0.1...v1.0.2)
- 优化了一些性能;
- 增加 `github actions` 覆盖 PHP7.2/7.3/7.4/8.0 + Linux/macOs/Windows环境
- 提升 `phpstan``level8` 最严谨级别,并修复大量遗留问题;
- 优化 `\WeChatPay\Exception\WeChatPayException` 异常类接口;
- 完善文档及平台证书下载器用法说明;
## 1.0.1 - 2021-06-21
[变更细节](../../compare/v1.0.0...v1.0.1)
- 优化了一些性能;
- 修复了大量 `phpstan level6` 静态分析遗留问题;
- 新增`\WeChatPay\Exception\WeChatPayException`异常类接口;
- 完善文档及方法类型签名;
## 1.0.0 - 2021-06-18
源自 `wechatpay-guzzle-middleware`,不兼容源版,顾自 `v1.0.0` 开始。
- `APIv2` & `APIv3` 同质化调用SDK默认为 `APIv3` 版;
- 标记 `APIv2` 为不推荐调用,预期 `v2.0` 会移除掉;
- 支持 `同步(sync)`(默认)及 `异步(async)` 请求服务端接口;
- 支持 `链式(chain)` 请求服务端接口;

View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,462 @@
# 微信支付 WeChatPay OpenAPI SDK
[A]Sync Chainable WeChatPay v2&v3's OpenAPI SDK for PHP
[![GitHub actions](https://github.com/wechatpay-apiv3/wechatpay-php/workflows/CI/badge.svg)](https://github.com/wechatpay-apiv3/wechatpay-php/actions)
[![Packagist Stars](https://img.shields.io/packagist/stars/wechatpay/wechatpay)](https://packagist.org/packages/wechatpay/wechatpay)
[![Packagist Downloads](https://img.shields.io/packagist/dm/wechatpay/wechatpay)](https://packagist.org/packages/wechatpay/wechatpay)
[![Packagist Version](https://img.shields.io/packagist/v/wechatpay/wechatpay)](https://packagist.org/packages/wechatpay/wechatpay)
[![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/wechatpay/wechatpay)](https://packagist.org/packages/wechatpay/wechatpay)
[![Packagist License](https://img.shields.io/packagist/l/wechatpay/wechatpay)](https://packagist.org/packages/wechatpay/wechatpay)
## 概览
微信支付 APIv2&APIv3 的[Guzzle HttpClient](http://docs.guzzlephp.org/)封装组合,
APIv2已内置请求数据签名及`XML`转换器,应答做了数据`签名验签`,转换提供有`WeChatPay\Transformer::toArray`静态方法,按需转换;
APIv3已内置 `请求签名``应答验签` 两个middleware中间件创新性地实现了链式面向对象同步/异步调用远程接口。
如果你是使用 `Guzzle` 的商户开发者,可以使用 `WeChatPay\Builder` 工厂方法直接创建一个 `GuzzleHttp\Client` 的链式调用封装器,
实例在执行请求时将自动携带身份认证信息,并检查应答的微信支付签名。
## 项目状态
当前版本为`1.0.7`测试版本。请商户的专业技术人员在使用时注意系统和软件的正确性和兼容性,以及带来的风险。
**版本说明:** `开发版`指: `类库API`随时会变;`测试版`指: 少量`类库API`可能会变;`稳定版`指: `类库API`稳定持续;版本号我们遵循[语义化版本号](https://semver.org/lang/zh-CN/)。
为了向广大开发者提供更好的使用体验,微信支付诚挚邀请您将**使用微信支付 API v3 SDK**中的感受反馈给我们。本问卷可能会占用您不超过2分钟的时间感谢您的支持。
问卷系统使用的腾讯问卷,您可以点击[这里](https://wj.qq.com/s2/8779987/8dae/),或者扫描以下二维码参与调查。
[![PHP SDK Questionnaire](https://user-images.githubusercontent.com/1812516/126434257-834ef6ab-e66b-4aa2-9104-8e37d7a14b93.png)](https://wj.qq.com/s2/8779987/8dae/)
## 环境要求
我们开发和测试使用的环境如下:
+ PHP >=7.2
+ guzzlehttp/guzzle ^7.0
**注:** 随`Guzzle7`支持的PHP版本最低为`7.2.5`另PHP官方已于`30 Nov 2020`停止维护`PHP7.2`,详见附注链接。
## 安装
推荐使用PHP包管理工具`composer`引入SDK到项目中
### 方式一
在项目目录中通过composer命令行添加
```shell
composer require wechatpay/wechatpay
```
### 方式二
在项目的`composer.json`中加入以下配置:
```json
"require": {
"wechatpay/wechatpay": "^1.0.7"
}
```
添加配置后,执行安装
```shell
composer install
```
## 约定
本类库是以 `OpenAPI` 对应的接入点 `URL.pathname` 以`/`做切分,映射成`segments`,编码书写方式有如下约定:
1. 请求 `pathname` 切分后的每个`segment`,可直接以对象获取形式串接,例如 `v3/pay/transactions/native` 即串成 `v3->pay->transactions->native`;
2. 每个 `pathname` 所支持的 `HTTP METHOD`,即作为被串接对象的末尾执行方法,例如: `v3->pay->transactions->native->post(['json' => []])`;
3. 每个 `pathname` 所支持的 `HTTP METHOD`,同时支持`Async`语法糖,例如: `v3->pay->transactions->native->postAsync(['json' => []])`;
4. 每个 `segment` 有中线(dash)分隔符的,可以使用驼峰`camelCase`风格书写,例如: `merchant-service`可写成 `merchantService`,或如 `{'merchant-service'}`;
5. 每个 `segment` 中,若有`uri_template`动态参数,例如 `business_code/{business_code}` 推荐以`business_code->{'{business_code}'}`形式书写,其格式语义与`pathname`基本一致,阅读起来比较自然;
6. SDK内置以 `v2` 特殊标识为 `APIv2` 的起始 `segmemt`,之后串接切分后的 `segments`,如源 `pay/micropay` 即串成 `v2->pay->micropay->post(['xml' => []])` 即以XML形式请求远端接口
7. 在IDE集成环境下也可以按照内置的`chain($segment)`接口规范,直接以`pathname`作为变量`$segment`,来获取`OpenAPI`接入点的`endpoints`串接对象,驱动末尾执行方法(填入对应参数),发起请求,例如 `chain('v3/pay/transactions/jsapi')->post(['json' => []])`
以下示例用法,以`异步(Async/PromiseA+)`或`同步(Sync)`结合此种编码模式展开。
> Note of the `segments`: See [RFC3986 #section-3.3](https://www.rfc-editor.org/rfc/rfc3986.html#section-3.3)
> > A path consists of a sequence of path segments separated by a slash ("/") character.
>
> Note of the `uri_template`: See [RFC6570](https://www.rfc-editor.org/rfc/rfc6570.html)
## 开始
首先,通过 `WeChatPay\Builder` 工厂方法构建一个实例,然后如上述`约定`,链式`同步`或`异步`请求远端`OpenAPI`接口。
```php
use WeChatPay\Builder;
use WeChatPay\Util\PemUtil;
// 商户号,假定为`1000100`
$merchantId = '1000100';
// 商户私钥,文件路径假定为 `/path/to/merchant/apiclient_key.pem`
$merchantPrivateKeyFilePath = '/path/to/merchant/apiclient_key.pem';
// 加载商户私钥
$merchantPrivateKeyInstance = PemUtil::loadPrivateKey($merchantPrivateKeyFilePath);
$merchantCertificateSerial = '可以从商户平台直接获取到';// API证书不重置商户证书序列号就是个常量
// // 或者从以下代码也可以直接加载
// // 商户证书,文件路径假定为 `/path/to/merchant/apiclient_cert.pem`
// $merchantCertificateFilePath = '/path/to/merchant/apiclient_cert.pem';
// // 加载商户证书
// $merchantCertificateInstance = PemUtil::loadCertificate($merchantCertificateFilePath);
// // 解析商户证书序列号
// $merchantCertificateSerial = PemUtil::parseCertificateSerialNo($merchantCertificateInstance);
// 平台证书,可由下载器 `./bin/CertificateDownloader.php` 生成并假定保存为 `/path/to/wechatpay/cert.pem`
$platformCertificateFilePath = '/path/to/wechatpay/cert.pem';
// 加载平台证书
$platformCertificateInstance = PemUtil::loadCertificate($platformCertificateFilePath);
// 解析平台证书序列号
$platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateInstance);
// 工厂方法构造一个实例
$instance = Builder::factory([
'mchid' => $merchantId,
'serial' => $merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
$platformCertificateSerial => $platformCertificateInstance,
],
// APIv2密钥(32字节)--不使用APIv2可选
// 'secret' => 'ZZZZZZZZZZ',// `ZZZZZZZZZZ` 为变量占位符如需使用APIv2请替换为实际值
// 'merchant' => [// --不使用APIv2可选
// // 商户证书 文件路径 --不使用APIv2可选
// 'cert' => $merchantCertificateFilePath,
// // 商户API私钥 文件路径 --不使用APIv2可选
// 'key' => $merchantPrivateKeyFilePath,
// ],
]);
```
初始化字典说明如下:
- `mchid` 为你的`商户号`一般是10字节纯数字
- `serial` 为你的`商户证书序列号`一般是40字节字符串
- `privateKey` 为你的`商户API私钥`,一般是通过官方证书生成工具生成的文件名是`apiclient_key.pem`文件,支持纯字符串或者文件`resource`格式
- `certs[$serial_number => #resource]` 为通过下载工具下载的平台证书`key/value`键值对,键为`平台证书序列号`,值为`平台证书`pem格式的纯字符串或者文件`resource`格式
- `secret` 为APIv2版的`密钥`商户平台上设置的32字节字符串
- `merchant[cert => $path]` 为你的`商户证书`,一般是文件名为`apiclient_cert.pem`文件路径,接受`[$path, $passphrase]` 格式,其中`$passphrase`为证书密码
- `merchant[key => $path]` 为你的`商户API私钥`,一般是通过官方证书生成工具生成的文件名是`apiclient_key.pem`文件路径,接受`[$path, $passphrase]` 格式,其中`$passphrase`为私钥密码
**注:** `APIv3`, `APIv2` 以及 `GuzzleHttp\Client``$config = []` 初始化参数,均融合在一个型参上; 另外初始化参数说明中的`平台证书下载器`可阅读[使用说明文档](bin/README.md)。
## APIv3
### Native下单
```php
try {
$resp = $instance->v3->pay->transactions->native->post(['json' => [
'mchid' => '1900006XXX',
'out_trade_no' => 'native12177525012014070332333',
'appid' => 'wxdace645e0bc2cXXX',
'description' => 'Image形象店-深圳腾大-QQ公仔',
'notify_url' => 'https://weixin.qq.com/',
'amount' => [
'total' => 1,
'currency' => 'CNY'
],
]]);
echo $resp->getStatusCode() . ' ' . $resp->getReasonPhrase(), PHP_EOL;
echo $resp->getBody(), PHP_EOL;
} catch (Exception $e) {
// 进行错误处理
echo $e->getMessage(), PHP_EOL;
if ($e instanceof \Psr\Http\Message\ResponseInterface && $e->hasResponse()) {
echo $e->getResponse()->getStatusCode() . ' ' . $e->getResponse()->getReasonPhrase(), PHP_EOL;
echo $e->getResponse()->getBody();
}
}
```
### 查单
```php
$res = $instance->v3->pay->transactions->id->{'{transaction_id}'}
->getAsync([
// 查询参数结构
'query' => ['mchid' => '1230000109'],
// uri_template 字面量参数
'transaction_id' => '1217752501201407033233368018',
])
->then(static function($response) {
// 正常逻辑回调处理
echo $response->getBody()->getContents(), PHP_EOL;
return $response;
})
->otherwise(static function($exception) {
// 异常错误处理
if ($exception instanceof \Psr\Http\Message\ResponseInterface) {
$body = $exception->getResponse()->getBody();
echo $body->getContents(), PHP_EOL, PHP_EOL, PHP_EOL;
}
echo $exception->getTraceAsString(), PHP_EOL;
})
->wait();
```
### 关单
```php
$res = $instance->v3->pay->transactions->outTradeNo->{'{out_trade_no}'}->close
->postAsync([
// 请求参数结构
'json' => ['mchid' => '1230000109'],
// uri_template 字面量参数
'out_trade_no' => '1217752501201407033233368018',
])
->then(static function($response) {
// 正常逻辑回调处理
echo $response->getBody()->getContents(), PHP_EOL;
return $response;
})
->otherwise(static function($exception) {
// 异常错误处理
if ($exception instanceof \Psr\Http\Message\ResponseInterface) {
$body = $exception->getResponse()->getBody();
echo $body->getContents(), PHP_EOL, PHP_EOL, PHP_EOL;
}
echo $exception->getTraceAsString(), PHP_EOL;
})
->wait();
```
### 退款
```php
$res = $instance->chain('v3/refund/domestic/refunds')
->postAsync([
'json' => [
'transaction_id' => '1217752501201407033233368018',
'out_refund_no' => '1217752501201407033233368018',
'amount' => [
'refund' => 888,
'total' => 888,
'currency' => 'CNY',
],
],
])
->then(static function($response) {
// 正常逻辑回调处理
echo $response->getBody()->getContents(), PHP_EOL;
return $response;
})
->otherwise(static function($exception) {
// 异常错误处理
if ($exception instanceof \Psr\Http\Message\ResponseInterface) {
$body = $exception->getResponse()->getBody();
echo $body->getContents(), PHP_EOL, PHP_EOL, PHP_EOL;
}
echo $exception->getTraceAsString(), PHP_EOL;
})
->wait();
```
### 视频文件上传
```php
// 参考上述指引说明,并引入 `MediaUtil` 正常初始化,无额外条件
use WeChatPay\Util\MediaUtil;
// 实例化一个媒体文件流,注意文件后缀名需符合接口要求
$media = new MediaUtil('/your/file/path/video.mp4');
try {
$resp = $instance['v3/merchant/media/video_upload']->post([
'body' => $media->getStream(),
'headers' => [
'content-type' => $media->getContentType(),
]
]);
echo $resp->getStatusCode() . ' ' . $resp->getReasonPhrase(), PHP_EOL;
echo $resp->getBody(), PHP_EOL;
} catch (Exception $e) {
echo $e->getMessage(), PHP_EOL;
if ($e instanceof \Psr\Http\Message\ResponseInterface && $e->hasResponse()) {
echo $e->getResponse()->getStatusCode() . ' ' . $e->getResponse()->getReasonPhrase(), PHP_EOL;
echo $e->getResponse()->getBody();
}
}
```
### 图片上传
```php
use WeChatPay\Util\MediaUtil;
$media = new MediaUtil('/your/file/path/image.jpg');
$resp = $instance->v3->marketing->favor->media->imageUpload
->postAsync([
'body' => $media->getStream(),
'headers' => [
'content-type' => $media->getContentType(),
]
])
->then(static function($response) {
echo $response->getBody()->getContents(), PHP_EOL;
return $response;
})
->otherwise(static function($exception) {
if ($exception instanceof \Psr\Http\Message\ResponseInterface) {
$body = $exception->getResponse()->getBody();
echo $body->getContents(), PHP_EOL, PHP_EOL, PHP_EOL;
}
echo $exception->getTraceAsString(), PHP_EOL;
})
->wait();
```
### 敏感信息加/解密
```php
// 参考上上述说明,引入 `WeChatPay\Crypto\Rsa`
use WeChatPay\Crypto\Rsa;
// 加载最新的平台证书
$publicKey = PemUtil::loadCertificate('/path/to/wechatpay/cert.pem');
// 做一个匿名方法,供后续方便使用
$encryptor = function($msg) use ($publicKey) { return Rsa::encrypt($msg, $publicKey); };
// 正常使用Guzzle发起API请求
try {
// POST 语法糖
$resp = $instance->chain('v3/applyment4sub/applyment/')->post([
'json' => [
'business_code' => 'APL_98761234',
'contact_info' => [
'contact_name' => $encryptor('value of `contact_name`'),
'contact_id_number' => $encryptor('value of `contact_id_number'),
'mobile_phone' => $encryptor('value of `mobile_phone`'),
'contact_email' => $encryptor('value of `contact_email`'),
],
//...
],
'headers' => [
// 命令行获取证书序列号
// openssl x509 -in /path/to/wechatpay/cert.pem -noout -serial | awk -F= '{print $2}'
// 或者使用工具类获取证书序列号 `PemUtil::parseCertificateSerialNo($certificate)`
'Wechatpay-Serial' => '下载的平台证书序列号',
],
]);
echo $resp->getStatusCode() . ' ' . $resp->getReasonPhrase(), PHP_EOL;
echo $resp->getBody(), PHP_EOL;
} catch (Exception $e) {
echo $e->getMessage(), PHP_EOL;
if ($e instanceof \Psr\Http\Message\ResponseInterface && $e->hasResponse()) {
echo $e->getResponse()->getStatusCode() . ' ' . $e->getResponse()->getReasonPhrase(), PHP_EOL;
echo $e->getResponse()->getBody();
}
return;
}
```
## APIv2
末尾驱动的 `HTTP METHOD(POST)` 方法入参 `array $options`,接受两个自定义参数,释义如下:
- `$options['nonceless']` - 标量 `scalar` 任意值,语义上即,本次请求不用自动添加`nonce_str`参数,推荐 `boolean(True)`
- `$options['security']` - 布尔量`True`语义上即本次请求需要加载ssl证书对应的是初始化 `array $config['merchant']` 结构体
### 初始化
```php
use WeChatPay\Builder;
// 商户号,假定为`1000100`
$merchantId = '1000100';
// APIv2密钥(32字节) 假定为`ZZZZZZZZZZ`,使用请替换为实际值
$apiv2Secret = 'ZZZZZZZZZZ';
// 商户私钥,文件路径假定为 `/path/to/merchant/apiclient_key.pem`
$merchantPrivateKeyFilePath = '/path/to/merchant/apiclient_key.pem';
// 商户证书,文件路径假定为 `/path/to/merchant/apiclient_cert.pem`
$merchantCertificateFilePath = '/path/to/merchant/apiclient_cert.pem';
// 工厂方法构造一个实例
$instance = Builder::factory([
'mchid' => $merchantId,
'serial' => 'nop',
'privateKey' => 'any',
'certs' => ['any' => null],
'secret' => $apiv2Secret,
'merchant' => [
'cert' => $merchantCertificateFilePath,
'key' => $merchantPrivateKeyFilePath,
],
]);
```
初始化字典说明如下:
- `mchid` 为你的`商户号`一般是10字节纯数字
- `serial` 为你的`商户证书序列号`不使用APIv3可填任意值
- `privateKey` 为你的`商户API私钥`不使用APIv3可填任意值
- `certs[$serial_number => #resource]` 不使用APIv3可填任意值, `$serial_number` 注意不要与商户证书序列号`serial`相同
- `secret` 为APIv2版的`密钥`商户平台上设置的32字节字符串
- `merchant[cert => $path]` 为你的`商户证书`,一般是文件名为`apiclient_cert.pem`文件路径,接受`[$path, $passphrase]` 格式,其中`$passphrase`为证书密码
- `merchant[key => $path]` 为你的`商户API私钥`,一般是通过官方证书生成工具生成的文件名是`apiclient_key.pem`文件路径,接受`[$path, $passphrase]` 格式,其中`$passphrase`为私钥密码
**注:** `APIv3`, `APIv2` 以及 `GuzzleHttp\Client``$config = []` 初始化参数,均融合在一个型参上。
### 企业付款到零钱
```php
use WeChatPay\Transformer;
$res = $instance->v2->mmpaymkttransfers->promotion->transfers
->postAsync([
'xml' => [
'appid' => 'wx8888888888888888',
'mch_id' => '1900000109',
'partner_trade_no' => '10000098201411111234567890',
'openid' => 'oxTWIuGaIt6gTKsQRLau2M0yL16E',
'check_name' => 'FORCE_CHECK',
're_user_name' => '王小王',
'amount' => 10099,
'desc' => '理赔',
'spbill_create_ip' => '192.168.0.1',
],
'security' => true,
'debug' => true //开启调试模式
])
->then(static function($response) { return Transformer::toArray($response->getBody()->getContents()); })
->otherwise(static function($exception) { return Transformer::toArray($exception->getResponse()->getBody()->getContents()); })
->wait();
print_r($res);
```
## 常见问题
### 如何下载平台证书?
使用内置的[平台证书下载器](bin/README.md) `./bin/CertificateDownloader.php` ,验签逻辑与有`平台证书`请求其他接口一致,即在请求完成后,立即用获得的`平台证书`对返回的消息进行验签,下载器同时开启了 `Guzzle``debug => true` 参数,方便查询请求/响应消息的基础调试信息。
### 证书和回调解密需要的AesGcm解密在哪里
请参考[AesGcm.php](src/Crypto/AesGcm.php)。
### 配合swoole使用时上传文件接口报错
建议升级至swoole 4.6+swoole在 4.6.0 中增加了native-curl([swoole/swoole-src#3863](https://github.com/swoole/swoole-src/pull/3863))支持,我们测试能正常使用了。
更详细的信息,请参考[#36](https://github.com/wechatpay-apiv3/wechatpay-guzzle-middleware/issues/36)。
## 联系我们
如果你发现了**BUG**或者有任何疑问、建议请通过issue进行反馈。
也欢迎访问我们的[开发者社区](https://developers.weixin.qq.com/community/pay)。
## 链接
- [GuzzleHttp官方版本支持](https://docs.guzzlephp.org/en/stable/overview.html#requirements)
- [PHP官方版本支持](https://www.php.net/supported-versions.php)
- [变更历史](CHANGELOG.md)
## License
[Apache-2.0 License](LICENSE)

View File

@ -0,0 +1,179 @@
#!/usr/bin/env php
<?php declare(strict_types=1);
// load autoload.php
$possibleFiles = [__DIR__.'/../vendor/autoload.php', __DIR__.'/../../../autoload.php', __DIR__.'/../../autoload.php'];
$file = null;
foreach ($possibleFiles as $possibleFile) {
if (file_exists($possibleFile)) {
$file = $possibleFile;
break;
}
}
if (null === $file) {
throw new RuntimeException('Unable to locate autoload.php file.');
}
require_once $file;
unset($possibleFiles, $possibleFile, $file);
use GuzzleHttp\Middleware;
use GuzzleHttp\Utils;
use WeChatPay\Builder;
use WeChatPay\ClientDecoratorInterface;
use WeChatPay\Crypto\AesGcm;
/**
* CertificateDownloader class
*/
class CertificateDownloader
{
public function run(): void
{
$opts = $this->parseOpts();
if (!$opts) {
$this->printHelp();
return;
}
if (isset($opts['help'])) {
$this->printHelp();
return;
}
if (isset($opts['version'])) {
echo ClientDecoratorInterface::VERSION, PHP_EOL;
return;
}
$this->job($opts);
}
/**
* @param array<string,string|true> $opts
*
* @return void
*/
private function job(array $opts): void
{
static $certs = ['any' => null];
$outputDir = $opts['output'] ?? \sys_get_temp_dir();
$apiv3Key = (string) $opts['key'];
$instance = Builder::factory([
'mchid' => $opts['mchid'],
'serial' => $opts['serialno'],
'privateKey' => \file_get_contents((string)$opts['privatekey']),
'certs' => &$certs,
]);
$handler = $instance->getDriver()->select(ClientDecoratorInterface::JSON_BASED)->getConfig('handler');
$handler->after('verifier', Middleware::mapResponse(static function($response) use ($apiv3Key, &$certs) {
$body = $response->getBody()->getContents();
/** @var object{data:array<object{encrypt_certificate:object{serial_no:string,nonce:string,associated_data:string}}>} $json */
$json = Utils::jsonDecode($body);
\array_map(static function($row) use ($apiv3Key, &$certs) {
$cert = $row->encrypt_certificate;
$certs[$row->serial_no] = AesGcm::decrypt($cert->ciphertext, $apiv3Key, $cert->nonce, $cert->associated_data);
}, \is_object($json) && isset($json->data) && \is_array($json->data) ? $json->data : []);
return $response;
}), 'injector');
$instance->chain('v3/certificates')->getAsync(['debug' => true])->then(static function($response) use ($outputDir, &$certs) {
$body = $response->getBody()->getContents();
$timeZone = new \DateTimeZone('Asia/Shanghai');
/** @var object{data:array<object{effective_time:string,expire_time:string:serial_no:string}>} $json */
$json = Utils::jsonDecode($body);
$data = \is_object($json) && isset($json->data) && \is_array($json->data) ? $json->data : [];
\array_walk($data, static function($row, $index, $certs) use ($outputDir, $timeZone) {
$serialNo = $row->serial_no;
$outpath = $outputDir . DIRECTORY_SEPARATOR . 'wechatpay_' . $serialNo . '.pem';
echo 'Certificate #', $index, ' {', PHP_EOL;
echo ' Serial Number: ', $serialNo, PHP_EOL;
echo ' Not Before: ', (new \DateTime($row->effective_time, $timeZone))->format('Y-m-d H:i:s'), PHP_EOL;
echo ' Not After: ', (new \DateTime($row->expire_time, $timeZone))->format('Y-m-d H:i:s'), PHP_EOL;
echo ' Saved to: ', $outpath, PHP_EOL;
echo ' Content: ', PHP_EOL, PHP_EOL, $certs[$serialNo], PHP_EOL, PHP_EOL;
echo '}', PHP_EOL;
\file_put_contents($outpath, $certs[$serialNo]);
}, $certs);
return $response;
})->otherwise(static function($exception) {
$body = $exception->getResponse()->getBody();
echo $body->getContents(), PHP_EOL, PHP_EOL, PHP_EOL;
echo $exception->getTraceAsString(), PHP_EOL;
})->wait();
}
/**
* @return ?array<string,string|true>
*/
private function parseOpts(): ?array
{
$opts = [
[ 'key', 'k', true ],
[ 'mchid', 'm', true ],
[ 'privatekey', 'f', true ],
[ 'serialno', 's', true ],
[ 'output', 'o', false ],
];
$shortopts = 'hV';
$longopts = [ 'help', 'version' ];
foreach ($opts as $opt) {
list($key, $alias) = $opt;
$shortopts .= $alias . ':';
$longopts[] = $key . ':';
}
$parsed = \getopt($shortopts, $longopts);
if (!$parsed) {
return null;
}
$args = [];
foreach ($opts as $opt) {
list($key, $alias, $mandatory) = $opt;
if (isset($parsed[$key]) || isset($parsed[$alias])) {
$possiable = $parsed[$key] ?? $parsed[$alias] ?? '';
$args[$key] = (string) (is_array($possiable) ? $possiable[0] : $possiable);
} elseif ($mandatory) {
return null;
}
}
if (isset($parsed['h']) || isset($parsed['help'])) {
$args['help'] = true;
}
if (isset($parsed['V']) || isset($parsed['version'])) {
$args['version'] = true;
}
return $args;
}
private function printHelp(): void
{
echo <<<EOD
Usage: 微信支付平台证书下载工具 [-hV]
-f=<privateKeyFilePath> -k=<apiv3Key> -m=<merchantId>
-o=[outputFilePath] -s=<serialNo>
-m, --mchid=<merchantId> 商户号
-s, --serialno=<serialNo> 商户证书的序列号
-f, --privatekey=<privateKeyFilePath>
商户的私钥文件
-k, --key=<apiv3Key> API v3密钥
-o, --output=[outputFilePath]
下载成功后保存证书的路径,可选参数,默认为临时文件目录夹
-V, --version Print version information and exit.
-h, --help Show this help message and exit.
EOD;
}
}
// main
(new CertificateDownloader())->run();

View File

@ -0,0 +1,53 @@
# Certificate Downloader
Certificate Downloader 是 PHP版 微信支付 APIv3 平台证书的命令行下载工具。该工具可从 `https://api.mch.weixin.qq.com/v3/certificates` 接口获取商户可用证书,并使用 [APIv3 密钥](https://wechatpay-api.gitbook.io/wechatpay-api-v3/ren-zheng/api-v3-mi-yao) 和 AES_256_GCM 算法进行解密,并把解密后证书下载到指定位置。
## 使用
使用方法与 [Java版Certificate Downloader](https://github.com/wechatpay-apiv3/CertificateDownloader) 一致,参数与常见问题请参考[其文档](https://github.com/wechatpay-apiv3/CertificateDownloader/blob/master/README.md)。
```shell
> bin/CertificateDownloader.php
Usage: 微信支付平台证书下载工具 [-hV]
-f=<privateKeyFilePath> -k=<apiV3key> -m=<merchantId>
-o=[outputFilePath] -s=<serialNo>
-m, --mchid=<merchantId> 商户号
-s, --serialno=<serialNo> 商户证书的序列号
-f, --privatekey=<privateKeyFilePath>
商户的私钥文件
-k, --key=<apiV3key> ApiV3Key
-o, --output=[outputFilePath]
下载成功后保存证书的路径,可选参数,默认为临时文件目录夹
-V, --version Print version information and exit.
-h, --help Show this help message and exit.
```
完整命令示例:
```shell
./bin/CertificateDownloader.php -k ${apiV3key} -m ${mchId} -f ${mchPrivateKeyFilePath} -s ${mchSerialNo} -o ${outputFilePath}
```
使用`composer`安装的软件包,可以通过如下命令下载:
```shell
vendor/bin/CertificateDownloader.php -k ${apiV3key} -m ${mchId} -f ${mchPrivateKeyFilePath} -s ${mchSerialNo} -o ${outputFilePath}
```
使用源码克隆版本,也可以使用`composer`通过以下命令下载:
```shell
composer v3-certificates -k ${apiV3key} -m ${mchId} -f ${mchPrivateKeyFilePath} -s ${mchSerialNo} -o ${outputFilePath}
```
## 常见问题
### 如何保证证书正确
请参见CertificateDownloader文档中[关于如何保证证书正确的说明](https://github.com/wechatpay-apiv3/CertificateDownloader#%E5%A6%82%E4%BD%95%E4%BF%9D%E8%AF%81%E8%AF%81%E4%B9%A6%E6%AD%A3%E7%A1%AE)。
### 如何使用信任链验证平台证书
请参见CertificateDownloader文档中[关于如何使用信任链验证平台证书的说明](https://github.com/wechatpay-apiv3/CertificateDownloader#%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8%E4%BF%A1%E4%BB%BB%E9%93%BE%E9%AA%8C%E8%AF%81%E5%B9%B3%E5%8F%B0%E8%AF%81%E4%B9%A6)。
### 第一次下载证书
请参见CertificateDownloader文档中[相关说明](https://github.com/wechatpay-apiv3/CertificateDownloader#%E7%AC%AC%E4%B8%80%E6%AC%A1%E4%B8%8B%E8%BD%BD%E8%AF%81%E4%B9%A6)。

View File

@ -0,0 +1,51 @@
{
"name": "wechatpay/wechatpay",
"version": "1.0.7",
"description": "[A]Sync Chainable WeChatPay v2&v3's OpenAPI SDK for PHP",
"type": "library",
"keywords": [
"wechatpay",
"chainable HTTP requests client",
"xml-parser",
"xml-builder",
"aes-ecb",
"aes-gcm",
"rsa-oaep"
],
"authors": [
{
"name": "WeChatPay Community",
"homepage": "https://developers.weixin.qq.com/community/pay"
}
],
"homepage": "https://pay.weixin.qq.com/",
"license": "Apache-2.0",
"require": {
"php": ">=7.2",
"ext-curl": "*",
"ext-libxml": "*",
"ext-simplexml": "*",
"ext-openssl": "*",
"guzzlehttp/uri-template": "^0.2",
"guzzlehttp/guzzle": "^7.0"
},
"require-dev": {
"phpunit/phpunit": "^8.5.5 || ^9.3.5",
"phpstan/phpstan": "^0.12.89"
},
"autoload": {
"psr-4": { "WeChatPay\\" : "src/" }
},
"autoload-dev": {
"psr-4": { "WeChatPay\\Tests\\" : "tests/" }
},
"bin": [
"bin/CertificateDownloader.php"
],
"scripts": {
"v3-certificates": "bin/CertificateDownloader.php",
"test": "vendor/bin/phpunit",
"phpstan": "vendor/bin/phpstan analyse --no-progress",
"phpstan-7": "vendor/bin/phpstan analyse --no-progress -c phpstan-php7.neon"
}
}

View File

@ -0,0 +1,79 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2021/7/23
* Time: 9:58 AM
*/
use WeChatPay\Builder;
use WeChatPay\Util\MediaUtil;
require_once 'src/Util/PemUtil.php';
ini_set("display_errors","On");
error_reporting(E_ALL);
// 商户号,假定为`1000100`
$merchantId = '1609669563';
// 商户私钥,文件路径假定为 `/path/to/merchant/apiclient_key.pem`
$merchantPrivateKeyFilePath = '/var/www/ysfzmmp1/ThinkPHP/Extend/Vendor/Wechat3/cert/apiclient_key.pem';
// 加载商户私钥
//$merchantPrivateKeyInstance = PemUtil::loadPrivateKey($merchantPrivateKeyFilePath);
$pemutil_model = new PemUtil();
$merchantPrivateKeyInstance = $pemutil_model::loadPrivateKey($merchantPrivateKeyFilePath);
echo 222;
exit();
$merchantCertificateSerial = '4929BF5BD31D96D752EA8BAE6F3B62B2C5114A2E';// API证书不重置商户证书序列号就是个常量
// // 或者从以下代码也可以直接加载
// // 商户证书,文件路径假定为 `/path/to/merchant/apiclient_cert.pem`
// $merchantCertificateFilePath = '/path/to/merchant/apiclient_cert.pem';
// // 加载商户证书
// $merchantCertificateInstance = PemUtil::loadCertificate($merchantCertificateFilePath);
// // 解析商户证书序列号
// $merchantCertificateSerial = PemUtil::parseCertificateSerialNo($merchantCertificateInstance);
// 平台证书,可由下载器 `./bin/CertificateDownloader.php` 生成并假定保存为 `/path/to/wechatpay/cert.pem`
$platformCertificateFilePath = '/var/www/ysfzmmp1/ThinkPHP/Extend/Vendor/Wechat3/cert/wxpay_cert.pem';
// 加载平台证书
$platformCertificateInstance = PemUtil::loadCertificate($platformCertificateFilePath);
// 解析平台证书序列号
$platformCertificateSerial = PemUtil::parseCertificateSerialNo($platformCertificateInstance);
echo "ok";
exit();
// 工厂方法构造一个实例
$instance = Builder::factory([
'mchid' => $merchantId,
'serial' => $merchantCertificateSerial,
'privateKey' => $merchantPrivateKeyInstance,
'certs' => [
$platformCertificateSerial => $platformCertificateInstance,
],
]);
$media = new MediaUtil('/your/file/path/image.jpg');
$resp = $instance->v3->marketing->favor->media->imageUpload
->postAsync([
'body' => $media->getStream(),
'headers' => [
'content-type' => $media->getContentType(),
]
])
->then(static function($response) {
echo $response->getBody()->getContents(), PHP_EOL;
return $response;
})
->otherwise(static function($exception) {
if ($exception instanceof \Psr\Http\Message\ResponseInterface) {
$body = $exception->getResponse()->getBody();
echo $body->getContents(), PHP_EOL, PHP_EOL, PHP_EOL;
}
echo $exception->getTraceAsString(), PHP_EOL;
})
->wait();

View File

@ -0,0 +1,151 @@
<?php declare(strict_types=1);
namespace WeChatPay;
use function preg_replace_callback_array;
use function strtolower;
use function implode;
use function array_filter;
use function array_push;
use ArrayIterator;
/**
* Chainable the client for sending HTTP requests.
*/
final class Builder
{
/**
* Building & decorate the chainable `GuzzleHttp\Client`
*
* Minimum mandatory \$config parameters structure
* - mchid: string - The merchant ID
* - serial: string - The serial number of the merchant certificate
* - privateKey: \OpenSSLAsymmetricKey|\OpenSSLCertificate|object|resource|string - The merchant private key.
* - certs: array<string, \OpenSSLAsymmetricKey|\OpenSSLCertificate|object|resource|string> - The wechatpay platform serial and certificate(s), `[$serial => $cert]` pair
* - secret?: string - The secret key string (optional)
* - merchant?: array{key?: string, cert?: string} - The merchant private key and certificate array. (optional)
* - merchant<?key, string|string[]> - The merchant private key(file path string). (optional)
* - merchant<?cert, string|string[]> - The merchant certificate(file path string). (optional)
*
* ```php
* // usage samples
* $instance = Builder::factory([]);
* $res = $instance->chain('v3/merchantService/complaintsV2')->get(['debug' => true]);
* $res = $instance->chain('v3/merchant-service/complaint-notifications')->get(['debug' => true]);
* $instance->v3->merchantService->ComplaintNotifications->postAsync([])->wait();
* $instance->v3->certificates->getAsync()->then(function() {})->otherwise(function() {})->wait();
* ```
*
* @param array<string,string|int|bool|array|mixed> $config - `\GuzzleHttp\Client`, `APIv3` and `APIv2` configuration settings.
*/
public static function factory(array $config = []): BuilderChainable
{
return new class([], new ClientDecorator($config)) extends ArrayIterator implements BuilderChainable
{
use BuilderTrait;
/**
* Compose the chainable `ClientDecorator` instance, most starter with the tree root point
* @param array<string|int> $input
* @param ?ClientDecoratorInterface $instance
*/
public function __construct(array $input = [], ?ClientDecoratorInterface $instance = null) {
parent::__construct($input, self::STD_PROP_LIST | self::ARRAY_AS_PROPS);
$this->setDriver($instance);
}
/**
* @var ClientDecoratorInterface $driver - The `ClientDecorator` instance
*/
protected $driver;
/**
* `$driver` setter
* @param ClientDecoratorInterface $instance - The `ClientDecorator` instance
*/
public function setDriver(ClientDecoratorInterface &$instance): BuilderChainable
{
$this->driver = $instance;
return $this;
}
/**
* @inheritDoc
*/
public function getDriver(): ClientDecoratorInterface
{
return $this->driver;
}
/**
* Normalize the `$thing` by the rules: `PascalCase` -> `camelCase`
* & `camelCase` -> `camel-case`
* & `_dynamic_` -> `{dynamic}`
*
* @param string $thing - The string waiting for normalization
*
* @return string
*/
protected function normalize(string $thing = ''): string
{
return preg_replace_callback_array([
'#^[A-Z]#' => static function(array $piece): string { return strtolower($piece[0]); },
'#[A-Z]#' => static function(array $piece): string { return '-' . strtolower($piece[0]); },
'#^_(.*)_$#' => static function(array $piece): string { return '{' . $piece[1] . '}'; },
], $thing) ?? $thing;
}
/**
* URI pathname
*
* @param string $seperator - The URI seperator, default is slash(`/`) character
*
* @return string - The URI string
*/
protected function pathname(string $seperator = '/'): string
{
return implode($seperator, $this->simplized());
}
/**
* Only retrieve a copy array of the URI segments
*
* @return array<string|int> - The URI segments array
*/
protected function simplized(): array
{
return array_filter($this->getArrayCopy(), static function($v) { return !($v instanceof BuilderChainable); });
}
/**
* @inheritDoc
*/
public function offsetGet($key): BuilderChainable
{
if (!$this->offsetExists($key)) {
$index = $this->simplized();
array_push($index, $this->normalize($key));
$this->offsetSet($key, new self($index, $this->getDriver()));
}
return parent::offsetGet($key);
}
/**
* @inheritDoc
*/
public function chain($segment): BuilderChainable
{
return $this->offsetGet($segment);
}
};
}
private function __construct()
{
// cannot be instantiated
}
}

View File

@ -0,0 +1,94 @@
<?php declare(strict_types=1);
namespace WeChatPay;
use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Signature of the Chainable `GuzzleHttp\Client` interface
*/
interface BuilderChainable
{
/**
* `$driver` getter
*/
public function getDriver(): ClientDecoratorInterface;
/**
* Chainable the given `$segment` with the `ClientDecoratorInterface` instance
*
* @param string|int $segment - The sgement or `URI`
*/
public function chain($segment): BuilderChainable;
/**
* Create and send an HTTP GET request.
*
* @param array<string,string|int|bool|array|mixed> $options Request options to apply.
*/
public function get(array $options = []): ResponseInterface;
/**
* Create and send an HTTP PUT request.
*
* @param array<string,string|int|bool|array|mixed> $options Request options to apply.
*/
public function put(array $options = []): ResponseInterface;
/**
* Create and send an HTTP POST request.
*
* @param array<string,string|int|bool|array|mixed> $options Request options to apply.
*/
public function post(array $options = []): ResponseInterface;
/**
* Create and send an HTTP PATCH request.
*
* @param array<string,string|int|bool|array|mixed> $options Request options to apply.
*/
public function patch(array $options = []): ResponseInterface;
/**
* Create and send an HTTP DELETE request.
*
* @param array<string,string|int|bool|array|mixed> $options Request options to apply.
*/
public function delete(array $options = []): ResponseInterface;
/**
* Create and send an asynchronous HTTP GET request.
*
* @param array<string,string|int|bool|array|mixed> $options Request options to apply.
*/
public function getAsync(array $options = []): PromiseInterface;
/**
* Create and send an asynchronous HTTP PUT request.
*
* @param array<string,string|int|bool|array|mixed> $options Request options to apply.
*/
public function putAsync(array $options = []): PromiseInterface;
/**
* Create and send an asynchronous HTTP POST request.
*
* @param array<string,string|int|bool|array|mixed> $options Request options to apply.
*/
public function postAsync(array $options = []): PromiseInterface;
/**
* Create and send an asynchronous HTTP PATCH request.
*
* @param array<string,string|int|bool|array|mixed> $options Request options to apply.
*/
public function patchAsync(array $options = []): PromiseInterface;
/**
* Create and send an asynchronous HTTP DELETE request.
*
* @param array<string,string|int|bool|array|mixed> $options Request options to apply.
*/
public function deleteAsync(array $options = []): PromiseInterface;
}

View File

@ -0,0 +1,103 @@
<?php declare(strict_types=1);
namespace WeChatPay;
use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Chainable points the client interface for sending HTTP requests.
*/
trait BuilderTrait
{
abstract public function getDriver(): ClientDecoratorInterface;
/**
* URI pathname
*
* @param string $seperator - The URI seperator, default is slash(`/`) character
*
* @return string - The URI string
*/
abstract protected function pathname(string $seperator = '/'): string;
/**
* @inheritDoc
*/
public function get(array $options = []): ResponseInterface
{
return $this->getDriver()->request('GET', $this->pathname(), $options);
}
/**
* @inheritDoc
*/
public function put(array $options = []): ResponseInterface
{
return $this->getDriver()->request('PUT', $this->pathname(), $options);
}
/**
* @inheritDoc
*/
public function post(array $options = []): ResponseInterface
{
return $this->getDriver()->request('POST', $this->pathname(), $options);
}
/**
* @inheritDoc
*/
public function patch(array $options = []): ResponseInterface
{
return $this->getDriver()->request('PATCH', $this->pathname(), $options);
}
/**
* @inheritDoc
*/
public function delete(array $options = []): ResponseInterface
{
return $this->getDriver()->request('DELETE', $this->pathname(), $options);
}
/**
* @inheritDoc
*/
public function getAsync(array $options = []): PromiseInterface
{
return $this->getDriver()->requestAsync('GET', $this->pathname(), $options);
}
/**
* @inheritDoc
*/
public function putAsync(array $options = []): PromiseInterface
{
return $this->getDriver()->requestAsync('PUT', $this->pathname(), $options);
}
/**
* @inheritDoc
*/
public function postAsync(array $options = []): PromiseInterface
{
return $this->getDriver()->requestAsync('POST', $this->pathname(), $options);
}
/**
* @inheritDoc
*/
public function patchAsync(array $options = []): PromiseInterface
{
return $this->getDriver()->requestAsync('PATCH', $this->pathname(), $options);
}
/**
* @inheritDoc
*/
public function deleteAsync(array $options = []): PromiseInterface
{
return $this->getDriver()->requestAsync('DELETE', $this->pathname(), $options);
}
}

View File

@ -0,0 +1,144 @@
<?php declare(strict_types=1);
namespace WeChatPay;
use function array_replace_recursive;
use function array_push;
use function extension_loaded;
use function function_exists;
use function sprintf;
use function php_uname;
use function implode;
use function strncasecmp;
use function strcasecmp;
use function substr;
use const PHP_OS;
use const PHP_VERSION;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\UriTemplate\UriTemplate;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Decorate the `GuzzleHttp\Client` instance
*/
final class ClientDecorator implements ClientDecoratorInterface
{
use ClientXmlTrait;
use ClientJsonTrait;
/**
* Deep merge the input with the defaults
*
* @param array<string,string|int|bool|array|mixed> $config - The configuration.
*
* @return array<string, string|mixed> - With the built-in configuration.
*/
protected static function withDefaults(array $config = []): array
{
return array_replace_recursive(static::$defaults, ['headers' => static::userAgent()], $config);
}
/**
* Prepare the `User-Agent` value key/value pair
*
* @return array<string, string>
*/
protected static function userAgent(): array
{
$value = ['wechatpay-php/' . static::VERSION];
array_push($value, 'GuzzleHttp/' . ClientInterface::MAJOR_VERSION);
extension_loaded('curl') && function_exists('curl_version') && array_push($value, 'curl/' . ((array)curl_version())['version']);
array_push($value, sprintf('(%s/%s) PHP/%s', PHP_OS, php_uname('r'), PHP_VERSION));
return ['User-Agent' => implode(' ', $value)];
}
/**
* Taken body string
*
* @param MessageInterface $message - The message
*/
protected static function body(MessageInterface $message): string
{
$body = '';
$bodyStream = $message->getBody();
if ($bodyStream->isSeekable()) {
$body = (string)$bodyStream;
$bodyStream->rewind();
}
return $body;
}
/**
* Decorate the `GuzzleHttp\Client` factory
*
* Acceptable \$config parameters stucture
* - mchid: string - The merchant ID
* - serial: string - The serial number of the merchant certificate
* - privateKey: \OpenSSLAsymmetricKey|\OpenSSLCertificate|object|resource|string - The merchant private key.
* - certs: array<string, \OpenSSLAsymmetricKey|\OpenSSLCertificate|object|resource|string> - The wechatpay platform serial and certificate(s), `[$serial => $cert]` pair
* - secret?: string - The secret key string (optional)
* - merchant?: array{key?: string, cert?: string} - The merchant private key and certificate array. (optional)
* - merchant<?key, string|string[]> - The merchant private key(file path string). (optional)
* - merchant<?cert, string|string[]> - The merchant certificate(file path string). (optional)
*
* @param array<string,string|int|bool|array|mixed> $config - `\GuzzleHttp\Client`, `APIv3` and `APIv2` configuration settings.
*/
public function __construct(array $config = [])
{
$this->{static::XML_BASED} = static::xmlBased($config);
$this->{static::JSON_BASED} = static::jsonBased($config);
}
/**
* Identify the `protocol` and `uri`
*
* @param string $uri - The uri string.
*
* @return string[] - the first element is the API version aka `protocol`, the second is the real `uri`
*/
private static function prepare(string $uri): array
{
return $uri && 0 === strncasecmp(static::XML_BASED . '/', $uri, 3)
? [static::XML_BASED, substr($uri, 3)]
: [static::JSON_BASED, $uri];
}
/**
* @inheritDoc
*/
public function select(?string $protocol = null): ClientInterface
{
return $protocol && 0 === strcasecmp(static::XML_BASED, $protocol)
? $this->{static::XML_BASED}
: $this->{static::JSON_BASED};
}
/**
* @inheritDoc
*/
public function request(string $method, string $uri, array $options = []): ResponseInterface
{
list($protocol, $pathname) = static::prepare($uri);
return $this->select($protocol)->request($method, UriTemplate::expand($pathname, $options), $options);
}
/**
* @inheritDoc
*/
public function requestAsync(string $method, string $uri, array $options = []): PromiseInterface
{
list($protocol, $pathname) = static::prepare($uri);
return $this->select($protocol)->requestAsync($method, UriTemplate::expand($pathname, $options), $options);
}
}

View File

@ -0,0 +1,59 @@
<?php declare(strict_types=1);
namespace WeChatPay;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Promise\PromiseInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Decorate the `GuzzleHttp\Client` interface
*/
interface ClientDecoratorInterface
{
/**
* @var string - This library version
*/
const VERSION = '1.0.7';
/**
* @var string - The HTTP transfer `xml` based protocol
* @deprecated 1.0 - @see \WeChatPay\Exception\WeChatPayException::DEP_XML_PROTOCOL_UNDER_END_OF_LIFE
*/
const XML_BASED = 'v2';
/**
* @var string - The HTTP transfer `json` based protocol
*/
const JSON_BASED = 'v3';
/**
* Protocol selector
*
* @param string|null $protocol - one of the constants of `XML_BASED`, `JSON_BASED`, default is `JSON_BASED`
* @return ClientInterface
*/
public function select(?string $protocol = null): ClientInterface;
/**
* Request the remote `$uri` by a HTTP `$method` verb
*
* @param string $uri - The uri string.
* @param string $method - The method string.
* @param array<string,string|int|bool|array|mixed> $options - The options.
*
* @return ResponseInterface - The `Psr\Http\Message\ResponseInterface` instance
*/
public function request(string $method, string $uri, array $options = []): ResponseInterface;
/**
* Async request the remote `$uri` by a HTTP `$method` verb
*
* @param string $uri - The uri string.
* @param string $method - The method string.
* @param array<string,string|int|bool|array|mixed> $options - The options.
*
* @return PromiseInterface - The `GuzzleHttp\Promise\PromiseInterface` instance
*/
public function requestAsync(string $method, string $uri, array $options = []): PromiseInterface;
}

View File

@ -0,0 +1,180 @@
<?php declare(strict_types=1);
namespace WeChatPay;
use function abs;
use function intval;
use function is_string;
use function is_numeric;
use function is_resource;
use function is_object;
use function is_array;
use function count;
use function sprintf;
use function array_key_exists;
use function array_keys;
use UnexpectedValueException;
use GuzzleHttp\Client;
use GuzzleHttp\Middleware;
use GuzzleHttp\HandlerStack;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\MessageInterface;
/** @var int - The maximum clock offset in second */
const MAXIMUM_CLOCK_OFFSET = 300;
const WechatpayNonce = 'Wechatpay-Nonce';
const WechatpaySerial = 'Wechatpay-Serial';
const WechatpaySignature = 'Wechatpay-Signature';
const WechatpayTimestamp = 'Wechatpay-Timestamp';
/**
* JSON based Client interface for sending HTTP requests.
*/
trait ClientJsonTrait
{
/**
* @var Client - The APIv3's `GuzzleHttp\Client`
*/
protected $v3;
/**
* @var array<string, string|array<string, string>> - The defaults configuration whose pased in `GuzzleHttp\Client`.
*/
protected static $defaults = [
'base_uri' => 'https://api.mch.weixin.qq.com/',
'headers' => [
'Accept' => 'application/json, text/plain, application/x-gzip',
'Content-Type' => 'application/json; charset=utf-8',
],
];
abstract protected static function body(MessageInterface $message): string;
abstract protected static function withDefaults(array $config = []): array;
/**
* APIv3's signer middleware stack
*
* @param string $mchid - The merchant ID
* @param string $serial - The serial number of the merchant certificate
* @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|object|resource|string $privateKey - The merchant private key.
*
* @return callable(RequestInterface)
*/
public static function signer(string $mchid, string $serial, $privateKey): callable
{
return static function (RequestInterface $request) use ($mchid, $serial, $privateKey): RequestInterface {
$nonce = Formatter::nonce();
$timestamp = (string) Formatter::timestamp();
$signature = Crypto\Rsa::sign(Formatter::request(
$request->getMethod(), $request->getRequestTarget(), $timestamp, $nonce, static::body($request)
), $privateKey);
return $request->withHeader('Authorization', Formatter::authorization(
$mchid, $nonce, $signature, $timestamp, $serial
));
};
}
/**
* APIv3's verifier middleware stack
*
* @param array<string, \OpenSSLAsymmetricKey|\OpenSSLCertificate|object|resource|string> $certs The wechatpay platform serial and certificate(s), `[$serial => $cert]` pair
*
* @return callable(ResponseInterface)
* @throws UnexpectedValueException
*/
public static function verifier(array &$certs): callable
{
return static function (ResponseInterface $response) use (&$certs): ResponseInterface {
if (!($response->hasHeader(WechatpayNonce) && $response->hasHeader(WechatpaySerial)
&& $response->hasHeader(WechatpaySignature) && $response->hasHeader(WechatpayTimestamp))) {
throw new UnexpectedValueException(sprintf(
Exception\WeChatPayException::EV3_RES_HEADERS_INCOMPLETE,
WechatpayNonce, WechatpaySerial, WechatpaySignature, WechatpayTimestamp
));
}
list($nonce) = $response->getHeader(WechatpayNonce);
list($serial) = $response->getHeader(WechatpaySerial);
list($signature) = $response->getHeader(WechatpaySignature);
list($timestamp) = $response->getHeader(WechatpayTimestamp);
$localTimestamp = Formatter::timestamp();
if (abs($localTimestamp - intval($timestamp)) > MAXIMUM_CLOCK_OFFSET) {
throw new UnexpectedValueException(sprintf(
Exception\WeChatPayException::EV3_RES_HEADER_TIMESTAMP_OFFSET,
MAXIMUM_CLOCK_OFFSET, $timestamp, $localTimestamp
));
}
if (!array_key_exists($serial, $certs)) {
throw new UnexpectedValueException(sprintf(
Exception\WeChatPayException::EV3_RES_HEADER_PLATFORM_SERIAL,
$serial, WechatpaySerial, implode(',', array_keys($certs))
));
}
if (!Crypto\Rsa::verify(Formatter::response($timestamp, $nonce, static::body($response)), $signature, $certs[$serial])) {
throw new UnexpectedValueException(sprintf(
Exception\WeChatPayException::EV3_RES_HEADER_SIGNATURE_DIGEST,
$timestamp, $nonce, $signature, $serial
));
}
return $response;
};
}
/**
* Create an APIv3's client
*
* Mandatory \$config array paramters
* - mchid: string - The merchant ID
* - serial: string - The serial number of the merchant certificate
* - privateKey: \OpenSSLAsymmetricKey|\OpenSSLCertificate|object|resource|string - The merchant private key.
* - certs: array{string, \OpenSSLAsymmetricKey|\OpenSSLCertificate|object|resource|string} - The wechatpay platform serial and certificate(s), `[$serial => $cert]` pair
*
* @param array<string,string|int|bool|array|mixed> $config - The configuration
* @throws \WeChatPay\Exception\InvalidArgumentException
*/
public static function jsonBased(array $config = []): Client
{
if (!(
isset($config['mchid']) && (is_string($config['mchid']) || is_numeric($config['mchid']))
)) { throw new Exception\InvalidArgumentException(Exception\ERR_INIT_MCHID_IS_MANDATORY); }
if (!(
isset($config['serial']) && is_string($config['serial'])
)) { throw new Exception\InvalidArgumentException(Exception\ERR_INIT_SERIAL_IS_MANDATORY); }
if (!(
isset($config['privateKey']) && (is_string($config['privateKey']) || is_resource($config['privateKey']) || is_object($config['privateKey']))
)) { throw new Exception\InvalidArgumentException(Exception\ERR_INIT_PRIVATEKEY_IS_MANDATORY); }
if (!(
isset($config['certs']) && is_array($config['certs']) && count($config['certs'])
)) { throw new Exception\InvalidArgumentException(Exception\ERR_INIT_CERTS_IS_MANDATORY); }
if (array_key_exists($config['serial'], $config['certs'])) {
throw new Exception\InvalidArgumentException(sprintf(
Exception\ERR_INIT_CERTS_EXCLUDE_MCHSERIAL, implode(',', array_keys($config['certs'])), $config['serial']
));
}
/** @var \GuzzleHttp\HandlerStack $handler */
$handler = isset($config['handler']) && ($config['handler'] instanceof HandlerStack) ? (clone $config['handler']) : HandlerStack::create();
$handler->unshift(Middleware::mapRequest(static::signer((string)$config['mchid'], $config['serial'], $config['privateKey'])), 'signer');
$handler->unshift(Middleware::mapResponse(static::verifier($config['certs'])), 'verifier');
$config['handler'] = $handler;
unset($config['mchid'], $config['serial'], $config['privateKey'], $config['certs'], $config['secret'], $config['merchant']);
return new Client(static::withDefaults($config));
}
}

View File

@ -0,0 +1,143 @@
<?php declare(strict_types=1);
namespace WeChatPay;
use function strlen;
use function array_replace_recursive;
use function trigger_error;
use function sprintf;
use const E_USER_DEPRECATED;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Utils;
use GuzzleHttp\Promise as P;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\MessageInterface;
/**
* XML based Client interface for sending HTTP requests.
*/
trait ClientXmlTrait
{
/**
* @var Client - The APIv2's `GuzzleHttp\Client`
*/
protected $v2;
/**
* @var array<string, string> - The default headers whose passed in `GuzzleHttp\Client`.
*/
protected static $headers = [
'Accept' => 'text/xml, text/plain, application/x-gzip',
'Content-Type' => 'text/xml; charset=utf-8',
];
abstract protected static function body(MessageInterface $message): string;
abstract protected static function withDefaults(array $config = []): array;
/**
* APIv2's transformRequest, did the `datasign` and `array2xml` together
*
* @param ?string $mchid - The merchant ID
* @param string $secret - The secret key string (optional)
* @param array{cert?: ?string, key?: ?string} $merchant - The merchant private key and certificate array. (optional)
*
* @return callable(callable(RequestInterface, array))
* @throws \WeChatPay\Exception\InvalidArgumentException
*/
public static function transformRequest(?string $mchid = null, string $secret = '', ?array $merchant = null): callable
{
return static function (callable $handler) use ($mchid, $secret, $merchant): callable {
trigger_error(Exception\WeChatPayException::DEP_XML_PROTOCOL_UNDER_END_OF_LIFE, E_USER_DEPRECATED);
return static function (RequestInterface $request, array $options = []) use ($handler, $mchid, $secret, $merchant): P\PromiseInterface {
$data = $options['xml'] ?? [];
if ($mchid && $mchid !== ($data['mch_id'] ?? null)) {
throw new Exception\InvalidArgumentException(sprintf(Exception\EV2_REQ_XML_NOTMATCHED_MCHID, $data['mch_id'] ?? '', $mchid));
}
$type = $data['sign_type'] ?? Crypto\Hash::ALGO_MD5;
isset($options['nonceless']) || $data['nonce_str'] = $data['nonce_str'] ?? Formatter::nonce();
$data['sign'] = Crypto\Hash::sign($type, Formatter::queryStringLike(Formatter::ksort($data)), $secret);
$modify = ['body' => Transformer::toXml($data)];
// for security request, it was required the merchant's private_key and certificate
if (isset($options['security']) && true === $options['security']) {
// @uses GuzzleHttp\RequestOptions::SSL_KEY
$options['ssl_key'] = $merchant['key'] ?? null;
// @uses GuzzleHttp\RequestOptions::CERT
$options['cert'] = $merchant['cert'] ?? null;
}
unset($options['xml'], $options['nonceless'], $options['security']);
return $handler(Utils::modifyRequest($request, $modify), $options);
};
};
}
/**
* APIv2's transformResponse, doing the `xml2array` then `verify` the signature job only
*
* @param string $secret - The secret key string (optional)
*
* @return callable(callable(RequestInterface, array))
*/
public static function transformResponse(string $secret = ''): callable
{
return static function (callable $handler) use ($secret): callable {
return static function (RequestInterface $request, array $options = []) use ($secret, $handler): P\PromiseInterface {
$promise = $handler($request, $options);
return $promise->then(static function(ResponseInterface $response) use ($secret) {
$result = Transformer::toArray(static::body($response));
$sign = $result['sign'] ?? null;
$type = $sign && strlen($sign) === 64 ? Crypto\Hash::ALGO_HMAC_SHA256 : Crypto\Hash::ALGO_MD5;
if ($sign !== Crypto\Hash::sign($type, Formatter::queryStringLike(Formatter::ksort($result)), $secret)) {
return P\Create::rejectionFor($response);
}
return $response;
});
};
};
}
/**
* Create an APIv2's client
*
* @deprecated 1.0 - @see \WeChatPay\Exception\WeChatPayException::DEP_XML_PROTOCOL_UNDER_END_OF_LIFE
*
* Optional acceptable \$config parameters
* - mchid?: ?string - The merchant ID
* - secret?: ?string - The secret key string
* - merchant?: array{key?: string, cert?: string} - The merchant private key and certificate array. (optional)
* - merchant<?key, string|string[]> - The merchant private key(file path string). (optional)
* - merchant<?cert, string|string[]> - The merchant certificate(file path string). (optional)
*
* @param array<string,string|int|bool|array|mixed> $config - The configuration
*/
public static function xmlBased(array $config = []): Client
{
/** @var \GuzzleHttp\HandlerStack $handler */
$handler = isset($config['handler']) && ($config['handler'] instanceof HandlerStack) ? (clone $config['handler']) : HandlerStack::create();
$handler->before('prepare_body', static::transformRequest($config['mchid'] ?? null, $config['secret'] ?? '', $config['merchant'] ?? []), 'transform_request');
$handler->before('prepare_body', static::transformResponse($config['secret'] ?? ''), 'transform_response');
$config['handler'] = $handler;
$config['headers'] = array_replace_recursive(static::$headers, $config['headers'] ?? []);
unset($config['mchid'], $config['serial'], $config['privateKey'], $config['certs'], $config['secret'], $config['merchant']);
return new Client(static::withDefaults($config));
}
}

View File

@ -0,0 +1,46 @@
<?php declare(strict_types=1);
namespace WeChatPay\Crypto;
use function openssl_encrypt;
use function base64_encode;
use function openssl_decrypt;
use function base64_decode;
use const OPENSSL_RAW_DATA;
use UnexpectedValueException;
/**
* Aes encrypt/decrypt using `aes-256-ecb` algorithm with pkcs7padding.
*/
class AesEcb implements AesInterface
{
/**
* @inheritDoc
*/
public static function encrypt(string $plaintext, string $key, string $iv = ''): string
{
$ciphertext = openssl_encrypt($plaintext, static::ALGO_AES_256_ECB, $key, OPENSSL_RAW_DATA, $iv = '');
if (false === $ciphertext) {
throw new UnexpectedValueException('Encrypting the input $plaintext failed, please checking your $key and $iv whether or nor correct.');
}
return base64_encode($ciphertext);
}
/**
* @inheritDoc
*/
public static function decrypt(string $ciphertext, string $key, string $iv = ''): string
{
$plaintext = openssl_decrypt(base64_decode($ciphertext), static::ALGO_AES_256_ECB, $key, OPENSSL_RAW_DATA, $iv = '');
if (false === $plaintext) {
throw new UnexpectedValueException('Decrypting the input $ciphertext failed, please checking your $key and $iv whether or nor correct.');
}
return $plaintext;
}
}

View File

@ -0,0 +1,91 @@
<?php declare(strict_types=1);
namespace WeChatPay\Crypto;
use function in_array;
use function openssl_get_cipher_methods;
use function openssl_encrypt;
use function base64_encode;
use function base64_decode;
use function substr;
use function strlen;
use function openssl_decrypt;
use function intval;
use const OPENSSL_RAW_DATA;
use RuntimeException;
use UnexpectedValueException;
/**
* Aes encrypt/decrypt using `aes-256-gcm` algorithm with additional authenticated data(`aad`).
*/
class AesGcm implements AesInterface
{
/**
* Detect the ext-openssl whether or nor including the `aes-256-gcm` algorithm
*
* @throws RuntimeException
*/
private static function preCondition(): void
{
if (!in_array(static::ALGO_AES_256_GCM, openssl_get_cipher_methods())) {
throw new RuntimeException('It looks like the ext-openssl extension missing the `aes-256-gcm` cipher method.');
}
}
/**
* Encrypts given data with given key, iv and aad, returns a base64 encoded string.
*
* @param string $plaintext - Text to encode.
* @param string $key - The secret key, 32 bytes string.
* @param string $iv - The initialization vector, 16 bytes string.
* @param string $aad - The additional authenticated data, maybe empty string.
*
* @return string - The base64-encoded ciphertext.
*/
public static function encrypt(string $plaintext, string $key, string $iv = '', string $aad = ''): string
{
static::preCondition();
$ciphertext = openssl_encrypt($plaintext, static::ALGO_AES_256_GCM, $key, OPENSSL_RAW_DATA, $iv, $tag, $aad, static::BLOCK_SIZE);
if (false === $ciphertext) {
throw new UnexpectedValueException('Encrypting the input $plaintext failed, please checking your $key and $iv whether or nor correct.');
}
return base64_encode($ciphertext . $tag);
}
/**
* Takes a base64 encoded string and decrypts it using a given key, iv and aad.
*
* @param string $ciphertext - The base64-encoded ciphertext.
* @param string $key - The secret key, 32 bytes string.
* @param string $iv - The initialization vector, 16 bytes string.
* @param string $aad - The additional authenticated data, maybe empty string.
*
* @return string - The utf-8 plaintext.
*/
public static function decrypt(string $ciphertext, string $key, string $iv = '', string $aad = ''): string
{
static::preCondition();
$ciphertext = base64_decode($ciphertext);
$authTag = substr($ciphertext, intval(-static::BLOCK_SIZE));
$tagLength = strlen($authTag);
/* Manually checking the length of the tag, because the `openssl_decrypt` was mentioned there, it's the caller's responsibility. */
if ($tagLength > static::BLOCK_SIZE || ($tagLength < 12 && $tagLength !== 8 && $tagLength !== 4)) {
throw new RuntimeException('The inputs `$ciphertext` incomplete, the bytes length must be one of 16, 15, 14, 13, 12, 8 or 4.');
}
$plaintext = openssl_decrypt(substr($ciphertext, 0, intval(-static::BLOCK_SIZE)), static::ALGO_AES_256_GCM, $key, OPENSSL_RAW_DATA, $iv, $authTag, $aad);
if (false === $plaintext) {
throw new UnexpectedValueException('Decrypting the input $ciphertext failed, please checking your $key and $iv whether or nor correct.');
}
return $plaintext;
}
}

View File

@ -0,0 +1,58 @@
<?php declare(strict_types=1);
namespace WeChatPay\Crypto;
/**
* Advanced Encryption Standard Interface
*/
interface AesInterface
{
/**
* Bytes Length of the AES block
*/
const BLOCK_SIZE = 16;
/**
* Bytes length of the AES secret key.
*/
const KEY_LENGTH_BYTE = 32;
/**
* Bytes Length of the authentication tag in AEAD cipher mode
* @deprecated 1.0 - As of the OpenSSL described, the `auth_tag` length may be one of 16, 15, 14, 13, 12, 8 or 4.
* Keep it only compatible for the samples on the official documentation.
*/
const AUTH_TAG_LENGTH_BYTE = 16;
/**
* The `aes-256-gcm` algorithm string
*/
const ALGO_AES_256_GCM = 'aes-256-gcm';
/**
* The `aes-256-ecb` algorithm string
*/
const ALGO_AES_256_ECB = 'aes-256-ecb';
/**
* Encrypts given data with given key and iv, returns a base64 encoded string.
*
* @param string $plaintext - Text to encode.
* @param string $key - The secret key, 32 bytes string.
* @param string $iv - The initialization vector, 16 bytes string.
*
* @return string - The base64-encoded ciphertext.
*/
public static function encrypt(string $plaintext, string $key, string $iv = ''): string;
/**
* Takes a base64 encoded string and decrypts it using a given key and iv.
*
* @param string $ciphertext - The base64-encoded ciphertext.
* @param string $key - The secret key, 32 bytes string.
* @param string $iv - The initialization vector, 16 bytes string.
*
* @return string - The utf-8 plaintext.
*/
public static function decrypt(string $ciphertext, string $key, string $iv = ''): string;
}

View File

@ -0,0 +1,81 @@
<?php declare(strict_types=1);
namespace WeChatPay\Crypto;
use function hash_init;
use function hash_update;
use function hash_final;
use function array_key_exists;
use function strtoupper;
use const HASH_HMAC;
const ALGO_MD5 = 'MD5';
const ALGO_HMAC_SHA256 = 'HMAC-SHA256';
const ALGO_DICTONARIES = [ALGO_HMAC_SHA256 => 'hmac', ALGO_MD5 => 'md5'];
/**
* Crypto hash functions utils.
* [Specification]{@link https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3}
*/
class Hash
{
/** @var string - hashing `MD5` algorithm */
const ALGO_MD5 = ALGO_MD5;
/** @var string - hashing `HMAC-SHA256` algorithm */
const ALGO_HMAC_SHA256 = ALGO_HMAC_SHA256;
/**
* Calculate the input string with an optional secret `key` in MD5,
* when the `key` is Falsey, this method works as normal `MD5`.
*
* @param string $thing - The input string.
* @param string $key - The secret key string.
* @param boolean|int|string $agency - The secret **key** is from work.weixin.qq.com, default is `false`,
* placed with `true` or better of the `AgentId` value.
* [spec]{@link https://work.weixin.qq.com/api/doc/90000/90135/90281}
*
* @return string - The data signature
*/
public static function md5(string $thing, string $key = '', $agency = false): string
{
$ctx = hash_init(ALGO_MD5);
hash_update($ctx, $thing) && $key && hash_update($ctx, $agency ? '&secret=' : '&key=') && hash_update($ctx, $key);
return hash_final($ctx);
}
/**
* Calculate the input string with a secret `key` as of `algorithm` string which is one of the 'sha256', 'sha512' etc.
*
* @param string $thing - The input string.
* @param string $key - The secret key string.
* @param string $algorithm - The algorithm string, default is `sha256`.
*
* @return string - The data signature
*/
public static function hmac(string $thing, string $key, string $algorithm = 'sha256'): string
{
$ctx = hash_init($algorithm, HASH_HMAC, $key);
hash_update($ctx, $thing) && hash_update($ctx, '&key=') && hash_update($ctx, $key);
return hash_final($ctx);
}
/**
* Utils of the data signature calculation.
*
* @param string $type - The sign type, one of the `MD5` or `HMAC-SHA256`.
* @param string $data - The input data.
* @param string $key - The secret key string.
*
* @return ?string - The data signature in UPPERCASE.
*/
public static function sign(string $type, string $data, string $key): ?string
{
return array_key_exists($type, ALGO_DICTONARIES) ? strtoupper(static::{ALGO_DICTONARIES[$type]}($data, $key)) : null;
}
}

View File

@ -0,0 +1,114 @@
<?php declare(strict_types=1);
namespace WeChatPay\Crypto;
use function in_array;
use function openssl_get_md_methods;
use function openssl_public_encrypt;
use function base64_encode;
use function openssl_verify;
use function base64_decode;
use function openssl_sign;
use function openssl_private_decrypt;
use const OPENSSL_PKCS1_OAEP_PADDING;
use RuntimeException;
use UnexpectedValueException;
const sha256WithRSAEncryption = 'sha256WithRSAEncryption';
/**
* Provides some methods for the RSA `sha256WithRSAEncryption` with `OPENSSL_PKCS1_OAEP_PADDING`.
*/
class Rsa
{
/**
* Detect the ext-openssl whether or nor including the `sha256WithRSAEncryption` cipher method
*
* @throws RuntimeException
*/
private static function preCondition(): void
{
if (!in_array(sha256WithRSAEncryption, openssl_get_md_methods(true))) {
throw new RuntimeException('It looks like the ext-openssl extension missing the `sha256WithRSAEncryption` digest method.');
}
}
/**
* Encrypts text with `OPENSSL_PKCS1_OAEP_PADDING`.
*
* @param string $plaintext - Cleartext to encode.
* @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|object|resource|string|mixed $publicKey - A PEM encoded public key.
*
* @return string - The base64-encoded ciphertext.
* @throws UnexpectedValueException
*/
public static function encrypt(string $plaintext, $publicKey): string
{
if (!openssl_public_encrypt($plaintext, $encrypted, $publicKey, OPENSSL_PKCS1_OAEP_PADDING)) {
throw new UnexpectedValueException('Encrypting the input $plaintext failed, please checking your $publicKey whether or nor correct.');
}
return base64_encode($encrypted);
}
/**
* Verifying the `message` with given `signature` string that uses `sha256WithRSAEncryption`.
*
* @param string $message - Content will be `openssl_verify`.
* @param string $signature - The base64-encoded ciphertext.
* @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|object|resource|string|mixed $publicKey - A PEM encoded public key.
*
* @return boolean - True is passed, false is failed.
* @throws UnexpectedValueException
*/
public static function verify(string $message, string $signature, $publicKey): bool
{
static::preCondition();
if (($result = openssl_verify($message, base64_decode($signature), $publicKey, sha256WithRSAEncryption)) === false) {
throw new UnexpectedValueException('Verified the input $message failed, please checking your $publicKey whether or nor correct.');
}
return $result === 1;
}
/**
* Creates and returns a `base64_encode` string that uses `sha256WithRSAEncryption`.
*
* @param string $message - Content will be `openssl_sign`.
* @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|object|resource|string|mixed $privateKey - A PEM encoded private key.
*
* @return string - The base64-encoded signature.
* @throws UnexpectedValueException
*/
public static function sign(string $message, $privateKey): string
{
static::preCondition();
if (!openssl_sign($message, $signature, $privateKey, sha256WithRSAEncryption)) {
throw new UnexpectedValueException('Signing the input $message failed, please checking your $privateKey whether or nor correct.');
}
return base64_encode($signature);
}
/**
* Decrypts base64 encoded string with `privateKey` with `OPENSSL_PKCS1_OAEP_PADDING`.
*
* @param string $ciphertext - Was previously encrypted string using the corresponding public key.
* @param \OpenSSLAsymmetricKey|\OpenSSLCertificate|resource|string|mixed $privateKey - A PEM encoded private key.
*
* @return string - The utf-8 plaintext.
* @throws UnexpectedValueException
*/
public static function decrypt(string $ciphertext, $privateKey): string
{
if (!openssl_private_decrypt(base64_decode($ciphertext), $decrypted, $privateKey, OPENSSL_PKCS1_OAEP_PADDING)) {
throw new UnexpectedValueException('Decrypting the input $ciphertext failed, please checking your $privateKey whether or nor correct.');
}
return $decrypted;
}
}

View File

@ -0,0 +1,9 @@
<?php declare(strict_types=1);
namespace WeChatPay\Exception;
use GuzzleHttp\Exception\GuzzleException;
class InvalidArgumentException extends \InvalidArgumentException implements WeChatPayException, GuzzleException
{
}

View File

@ -0,0 +1,27 @@
<?php declare(strict_types=1);
namespace WeChatPay\Exception;
const DEP_XML_PROTOCOL_UNDER_END_OF_LIFE = 'New features are all in `APIv3`, there\'s no reason to continue use this kind client since v2.0.';
const ERR_INIT_MCHID_IS_MANDATORY = 'The merchant\' ID aka `mchid` is required, usually numerical.';
const ERR_INIT_SERIAL_IS_MANDATORY = 'The serial number of the merchant\'s certificate aka `serial` is required, usually hexadecial.';
const ERR_INIT_PRIVATEKEY_IS_MANDATORY = 'The merchant\'s private key aka `privateKey` is required, usual as pem format.';
const ERR_INIT_CERTS_IS_MANDATORY = 'The platform certificate(s) aka `certs` is required, paired as of `[$serial => $certificate]`.';
const ERR_INIT_CERTS_EXCLUDE_MCHSERIAL = 'The `certs(%1$s)` contains the merchant\'s certificate serial number(%2$s) which is not allowed here.';
const EV2_REQ_XML_NOTMATCHED_MCHID = 'The xml\'s structure[mch_id(%1$s)] doesn\'t matched the init one mchid(%2$s).';
const EV3_RES_HEADERS_INCOMPLETE = 'The response\'s Headers incomplete, must have(`%1$s`, `%2$s`, `%3$s` and `%4$s`).';
const EV3_RES_HEADER_TIMESTAMP_OFFSET = 'It\'s allowed time offset in ± %1$s seconds, the response was on %2$s, your\'s localtime on %3$s.';
const EV3_RES_HEADER_PLATFORM_SERIAL = 'Cannot found the serial(`%1$s`)\'s configuration, which\'s from the response(header:%2$s), your\'s %3$s.';
const EV3_RES_HEADER_SIGNATURE_DIGEST = 'Verify the response\'s data with: timestamp=%1$s, nonce=%2$s, signature=%3$s, cert=[%4$s => ...] failed.';
interface WeChatPayException
{
const DEP_XML_PROTOCOL_UNDER_END_OF_LIFE = DEP_XML_PROTOCOL_UNDER_END_OF_LIFE;
const EV3_RES_HEADERS_INCOMPLETE = EV3_RES_HEADERS_INCOMPLETE;
const EV3_RES_HEADER_TIMESTAMP_OFFSET = EV3_RES_HEADER_TIMESTAMP_OFFSET;
const EV3_RES_HEADER_PLATFORM_SERIAL = EV3_RES_HEADER_PLATFORM_SERIAL;
const EV3_RES_HEADER_SIGNATURE_DIGEST = EV3_RES_HEADER_SIGNATURE_DIGEST;
}

View File

@ -0,0 +1,149 @@
<?php declare(strict_types=1);
namespace WeChatPay;
use function str_split;
use function array_map;
use function ord;
use function random_bytes;
use function time;
use function sprintf;
use function implode;
use function array_merge;
use function ksort;
use function is_null;
use const SORT_FLAG_CASE;
use const SORT_NATURAL;
use InvalidArgumentException;
/**
* Provides easy used methods using in this project.
*/
class Formatter
{
/**
* Generate a random BASE62 string aka `nonce`, similar as `random_bytes`.
*
* @param int $size - Nonce string length, default is 32.
*
* @return string - base62 random string.
*/
public static function nonce(int $size = 32): string
{
if ($size < 1) {
throw new InvalidArgumentException('Size must be a positive integer.');
}
return implode('', array_map(static function(string $c): string {
return '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'[ord($c) % 62];
}, str_split(random_bytes($size))));
}
/**
* Retrieve the current `Unix` timestamp.
*
* @return int - Epoch timestamp.
*/
public static function timestamp(): int
{
return time();
}
/**
* Formatting for the heading `Authorization` value.
*
* @param string $mchid - The merchant ID.
* @param string $nonce - The Nonce string.
* @param string $signature - The base64-encoded `Rsa::sign` ciphertext.
* @param string $timestamp - The `Unix` timestamp.
* @param string $serial - The serial number of the merchant public certification.
*
* @return string - The APIv3 Authorization `header` value
*/
public static function authorization(string $mchid, string $nonce, string $signature, string $timestamp, string $serial): string
{
return sprintf(
'WECHATPAY2-SHA256-RSA2048 mchid="%s",serial_no="%s",timestamp="%s",nonce_str="%s",signature="%s"',
$mchid, $serial, $timestamp, $nonce, $signature
);
}
/**
* Formatting this `HTTP::request` for `Rsa::sign` input.
*
* @param string $method - The HTTP verb, must be the uppercase sting.
* @param string $uri - Combined string with `URL::pathname` and `URL::search`.
* @param string $timestamp - The `Unix` timestamp, should be the one used in `authorization`.
* @param string $nonce - The `Nonce` string, should be the one used in `authorization`.
* @param string $body - The playload string, HTTP `GET` should be an empty string.
*
* @return string - The content for `Rsa::sign`
*/
public static function request(string $method, string $uri, string $timestamp, string $nonce, string $body = ''): string
{
return static::joinedByLineFeed($method, $uri, $timestamp, $nonce, $body);
}
/**
* Formatting this `HTTP::response` for `Rsa::verify` input.
*
* @param string $timestamp - The `Unix` timestamp, should be the one from `response::headers[Wechatpay-Timestamp]`.
* @param string $nonce - The `Nonce` string, should be the one from `response::headers[Wechatpay-Nonce]`.
* @param string $body - The response payload string, HTTP status(`201`, `204`) should be an empty string.
*
* @return string - The content for `Rsa::verify`
*/
public static function response(string $timestamp, string $nonce, string $body = ''): string
{
return static::joinedByLineFeed($timestamp, $nonce, $body);
}
/**
* Joined this inputs by for `Line Feed`(LF) char.
*
* @param string[] ...$pieces - The string(s) joined by line feed.
*
* @return string - The joined string.
*/
public static function joinedByLineFeed(...$pieces): string
{
return implode("\n", array_merge($pieces, ['']));
}
/**
* Sort an array by key with `SORT_FLAG_CASE | SORT_NATURAL` flag.
*
* @param array<string, string|int> $thing - The input array.
*
* @return array<string, string|int> - The sorted array.
*/
public static function ksort(array $thing = []): array
{
ksort($thing, SORT_FLAG_CASE | SORT_NATURAL);
return $thing;
}
/**
* Like `queryString` does but without the `sign` and `empty value` entities.
*
* @param array<string, string|int|null> $thing - The input array.
*
* @return string - The `key=value` pair string whose joined by `&` char.
*/
public static function queryStringLike(array $thing = []): string
{
$data = [];
foreach ($thing as $key => $value) {
if ($key === 'sign' || is_null($value) || $value === '') {
continue;
}
$data[] = implode('=', [$key, $value]);
}
return implode('&', $data);
}
}

View File

@ -0,0 +1,185 @@
<?php declare(strict_types=1);
namespace WeChatPay;
use const LIBXML_VERSION;
use const LIBXML_NONET;
use const LIBXML_COMPACT;
use const LIBXML_NOCDATA;
use const LIBXML_NOBLANKS;
use function array_walk;
use function is_array;
use function is_object;
use function preg_replace;
use function strpos;
use function preg_match;
use SimpleXMLElement;
use Traversable;
use XMLWriter;
/**
* Transform the `XML` to `Array` or `Array` to `XML`.
*/
class Transformer
{
/**
* Convert the $xml string to array.
*
* Always issue the `additional Libxml parameters` asof `LIBXML_NONET`
* | `LIBXML_COMPACT`
* | `LIBXML_NOCDATA`
* | `LIBXML_NOBLANKS`
*
* @param string $xml - The xml string, default is `<xml/>` string
*
* @return array<string,string|array|mixed>
*/
public static function toArray(string $xml = '<xml/>'): array
{
LIBXML_VERSION < 20900 && $previous = libxml_disable_entity_loader(true);
$el = simplexml_load_string(static::sanitize($xml), SimpleXMLElement::class, LIBXML_NONET | LIBXML_COMPACT | LIBXML_NOCDATA | LIBXML_NOBLANKS);
LIBXML_VERSION < 20900 && isset($previous) && libxml_disable_entity_loader($previous);
return static::cast($el);
}
/**
* Recursive cast the $thing as array data structure.
*
* @param array<string,mixed>|object|\SimpleXMLElement|false $thing - The thing
*
* @return array<string,string|array|mixed>
*/
protected static function cast($thing): array
{
$data = (array) $thing;
array_walk($data, static function(&$value) { static::value($value); });
return $data;
}
/**
* Cast the value $thing, specially doing the `array`, `object`, `SimpleXMLElement` to `array`
*
* @param string|array<string,string|\SimpleXMLElement|mixed>|object|\SimpleXMLElement $thing - The value thing reference
*/
protected static function value(&$thing): void
{
is_array($thing) && $thing = static::cast($thing);
if (is_object($thing) && $thing instanceof SimpleXMLElement) {
$thing = $thing->count() ? static::cast($thing) : (string) $thing;
}
}
/**
* Trim invalid characters from the $xml string
*
* @see https://github.com/w7corp/easywechat/pull/1419
* @license https://github.com/w7corp/easywechat/blob/4.x/LICENSE
*
* @param string $xml - The xml string
*/
public static function sanitize(string $xml): string
{
return preg_replace('#[^\x{9}\x{A}\x{D}\x{20}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]+#u', '', $xml) ?? '';
}
/**
* Transform the given $data array as of an XML string.
*
* @param array<string,string|array|mixed> $data - The data array
* @param boolean $headless - The headless flag, default `true` means without the `<?xml version="1.0" encoding="UTF-8" ?>` doctype
* @param boolean $indent - Toggle indentation on/off, default is `false` off
* @param string $root - The root node label, default is `xml` string
* @param string $item - The nest array identify text, default is `item` string
*
* @return string - The xml string
*/
public static function toXml(array $data, bool $headless = true, bool $indent = false, string $root = 'xml', string $item = 'item'): string
{
$writer = new XMLWriter();
$writer->openMemory();
$writer->setIndent($indent);
$headless || $writer->startDocument('1.0', 'utf-8');
$writer->startElement($root);
static::walk($writer, $data, $item);
$writer->endElement();
$headless || $writer->endDocument();
$xml = $writer->outputMemory();
$writer = null;
return $xml;
}
/**
* Walk the given data array by the `XMLWriter` instance.
*
* @param \XMLWriter $writer - The `XMLWriter` instance reference
* @param array<string,string|array|mixed> $data - The data array
* @param string $item - The nest array identify tag text
*/
protected static function walk(XMLWriter &$writer, array $data, string $item): void
{
foreach ($data as $key => $value) {
$tag = is_string($key) && static::isElementNameValid($key) ? $key : $item;
$writer->startElement($tag);
if (is_array($value) || (is_object($value) && $value instanceof Traversable)) {
static::walk($writer, (array) $value, $item);
} else {
static::content($writer, $value);
}
$writer->endElement();
}
}
/**
* Write content text.
*
* The content text includes the characters `<`, `>`, `&` and `"` are written as CDATA references.
* All others including `'` are written literally.
*
* @param \XMLWriter $writer - The `XMLWriter` instance reference
* @param string $thing - The content text
*/
protected static function content(XMLWriter &$writer, string $thing = ''): void
{
static::needsCdataWrapping($thing) && $writer->writeCdata($thing) || $writer->text($thing);
}
/**
* Checks the name is a valid xml element name.
*
* @see \Symfony\Component\Serializer\Encoder\XmlEncoder::isElementNameValid
* @license https://github.com/symfony/serializer/blob/5.3/LICENSE
*
* @param string $name - The name
*
* @return boolean - True means valid
*/
protected static function isElementNameValid(string $name = ''): bool
{
return $name && false === strpos($name, ' ') && preg_match('#^[\pL_][\pL0-9._:-]*$#ui', $name);
}
/**
* Checks if a value contains any characters which would require CDATA wrapping.
*
* Notes here: the `XMLWriter` shall been wrapped the `"` string as `&quot;` symbol string,
* it's strictly following the `XMLWriter` specification here.
*
* @see \Symfony\Component\Serializer\Encoder\XmlEncoder::needsCdataWrapping
* @license https://github.com/symfony/serializer/blob/5.3/LICENSE
*
* @param string $value - The value
*
* @return boolean - True means need
*/
protected static function needsCdataWrapping(string $value = ''): bool
{
return $value && 0 < preg_match('#[>&"<]#', $value);
}
}

View File

@ -0,0 +1,133 @@
<?php declare(strict_types=1);
namespace WeChatPay\Util;
use function basename;
use function sprintf;
use UnexpectedValueException;
use GuzzleHttp\Utils as GHU;
use GuzzleHttp\Psr7\Utils;
use GuzzleHttp\Psr7\LazyOpenStream;
use GuzzleHttp\Psr7\MultipartStream;
use GuzzleHttp\Psr7\FnStream;
use GuzzleHttp\Psr7\CachingStream;
use Psr\Http\Message\StreamInterface;
/**
* Util for Media(image or video) uploading.
*/
class MediaUtil
{
/**
* @var string - local file path
*/
private $filepath;
/**
* @var ?StreamInterface - file content stream to upload
*/
private $fileStream;
/**
* @var string - upload meta json string
*/
private $meta;
/**
* @var MultipartStream - upload contents stream
*/
private $multipart;
/**
* @var StreamInterface - multipart stream wrapper
*/
private $stream;
/**
* Constructor
*
* @param string $filepath The media file path or file name,
* should be one of the
* images(jpg|bmp|png)
* or
* video(avi|wmv|mpeg|mp4|mov|mkv|flv|f4v|m4v|rmvb)
* @param ?StreamInterface $fileStream File content stream, optional
*/
public function __construct(string $filepath, ?StreamInterface $fileStream = null)
{
$this->filepath = $filepath;
$this->fileStream = $fileStream;
$this->composeStream();
}
/**
* Compose the GuzzleHttp\Psr7\FnStream
*/
private function composeStream(): void
{
$basename = basename($this->filepath);
$stream = $this->fileStream ?? new LazyOpenStream($this->filepath, 'rb');
if ($stream instanceof StreamInterface && !($stream->isSeekable())) {
$stream = new CachingStream($stream);
}
if (!($stream instanceof StreamInterface)) {
throw new UnexpectedValueException(sprintf('Cannot open or caching the file: `%s`', $this->filepath));
}
$json = GHU::jsonEncode([
'filename' => $basename,
'sha256' => Utils::hash($stream, 'sha256'),
]);
$this->meta = $json;
$multipart = new MultipartStream([
[
'name' => 'meta',
'contents' => $json,
'headers' => [
'Content-Type' => 'application/json',
],
],
[
'name' => 'file',
'filename' => $basename,
'contents' => $stream,
],
]);
$this->multipart = $multipart;
$this->stream = FnStream::decorate($multipart, [
/** @var callable __toString - for signature */
'__toString' => static function () use ($json) { return $json; },
/** @var callable getSize - let the `CURL` to use `CURLOPT_UPLOAD` context */
'getSize' => static function () { return null; },
]);
}
/**
* Get the `meta` of the multipart data string
*/
public function getMeta(): string
{
return $this->meta;
}
/**
* Get the `GuzzleHttp\Psr7\CachingStream`|`GuzzleHttp\Psr7\LazyOpenStream` context
*/
public function getStream(): StreamInterface
{
return $this->stream;
}
/**
* Get the `Content-Type` value from the `{$this->multipart}` instance
*/
public function getContentType(): string
{
return 'multipart/form-data; boundary=' . $this->multipart->getBoundary();
}
}

View File

@ -0,0 +1,101 @@
<?php declare(strict_types=1);
namespace WeChatPay\Util;
use function openssl_get_privatekey;
use function openssl_x509_read;
use function openssl_x509_parse;
use function file_get_contents;
use function strtoupper;
use UnexpectedValueException;
/**
* Util for read private key and certificate.
*/
class PemUtil1
{
/**
* Read private key from file
*
* @param string $filepath - PEM encoded private key file path
* @param string $passphrase The optional parameter passphrase must be used if the specified key is encrypted (protected by a passphrase).
*
* @return \OpenSSLAsymmetricKey|object|resource|bool - Private key resource identifier on success, or FALSE on error
* @throws UnexpectedValueException
*/
public static function loadPrivateKey(string $filepath, string $passphrase = '')
{
echo 111;
exit();
$content = file_get_contents($filepath);
echo $content;
exit();
if (false === $content) {
throw new UnexpectedValueException("Loading the privateKey failed, please checking your {$filepath} input.");
}
return openssl_get_privatekey($content, $passphrase);
}
/**
* Read certificate from file
*
* @param string $filepath - PEM encoded X.509 certificate file path
*
* @return \OpenSSLCertificate|object|resource|bool - X.509 certificate resource identifier on success or FALSE on failure
* @throws UnexpectedValueException
*/
public static function loadCertificate(string $filepath)
{
$content = file_get_contents($filepath);
if (false === $content) {
throw new UnexpectedValueException("Loading the certificate failed, please checking your {$filepath} input.");
}
return openssl_x509_read($content);
}
/**
* Read private key from string
*
* @param \OpenSSLAsymmetricKey|object|resource|string|mixed $content - PEM encoded private key string content
* @param string $passphrase The optional parameter passphrase must be used if the specified key is encrypted (protected by a passphrase).
*
* @return \OpenSSLAsymmetricKey|object|resource|bool - Private key resource identifier on success, or FALSE on error
*/
public static function loadPrivateKeyFromString($content, string $passphrase = '')
{
return openssl_get_privatekey($content, $passphrase);
}
/**
* Read certificate from string
*
* @param \OpenSSLCertificate|object|resource|string|mixed $content - PEM encoded X.509 certificate string content
*
* @return \OpenSSLCertificate|object|resource|bool - X.509 certificate resource identifier on success or FALSE on failure
*/
public static function loadCertificateFromString($content)
{
return openssl_x509_read($content);
}
/**
* Parse Serial Number from Certificate
*
* @param \OpenSSLCertificate|object|resource|string|mixed $certificate Certificates string or resource
*
* @return string - The serial number
* @throws UnexpectedValueException
*/
public static function parseCertificateSerialNo($certificate): string
{
$info = openssl_x509_parse($certificate);
if (false === $info || !isset($info['serialNumberHex'])) {
throw new UnexpectedValueException('Read the $certificate failed, please check it whether or nor correct');
}
return strtoupper($info['serialNumberHex'] ?? '');
}
}

View File

@ -0,0 +1,12 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2021/7/23
* Time: 11:08 AM
*/
class test
{
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/3/3
* Time: 5:04 PM
*/
namespace app\common\alipay\aop;
class EncryptParseItem
{
public $startIndex;
public $endIndex;
public $encryptContent;
}

View File

@ -0,0 +1,26 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/3/3
* Time: 5:04 PM
*/
namespace app\common\alipay\aop;
class LtInflector
{
public $conf = array("separator" => "_");
public function camelize($uncamelized_words)
{
$uncamelized_words = $this->conf["separator"] . str_replace($this->conf["separator"] , " ", strtolower($uncamelized_words));
return ltrim(str_replace(" ", "", ucwords($uncamelized_words)), $this->conf["separator"] );
}
public function uncamelize($camelCaps)
{
return strtolower(preg_replace('/([a-z])([A-Z])/', "$1" . $this->conf["separator"] . "$2", $camelCaps));
}
}

View File

@ -0,0 +1,52 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/3/3
* Time: 5:04 PM
*/
namespace app\common\alipay\aop;
class LtLogger
{
public $conf = array(
"separator" => "\t",
"log_file" => ""
);
private $fileHandle;
protected function getFileHandle()
{
if (null === $this->fileHandle)
{
if (empty($this->conf["log_file"]))
{
trigger_error("no log file spcified.");
}
$logDir = dirname($this->conf["log_file"]);
if (!is_dir($logDir))
{
mkdir($logDir, 0777, true);
}
$this->fileHandle = fopen($this->conf["log_file"], "a");
}
return $this->fileHandle;
}
public function log($logData)
{
if ("" == $logData || array() == $logData)
{
return false;
}
if (is_array($logData))
{
$logData = implode($this->conf["separator"], $logData);
}
$logData = $logData. "\n";
fwrite($this->getFileHandle(), $logData);
}
}

View File

@ -0,0 +1,18 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/3/3
* Time: 5:05 PM
*/
namespace app\common\alipay\aop;
class SignData
{
public $signSourceData=null;
public $sign=null;
}

View File

@ -0,0 +1,122 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/3/3
* Time: 4:59 PM
*/
namespace app\common\alipay\request;
class AlipayTradePayRequest
{
/**
* 用于在线下场景交易一次创建并支付掉
修改路由策略到R
**/
private $bizContent;
private $apiParas = array();
private $terminalType;
private $terminalInfo;
private $prodCode;
private $apiVersion="1.0";
private $notifyUrl;
private $returnUrl;
private $needEncrypt=false;
public function setBizContent($bizContent)
{
$this->bizContent = $bizContent;
$this->apiParas["biz_content"] = $bizContent;
}
public function getBizContent()
{
return $this->bizContent;
}
public function getApiMethodName()
{
return "alipay.trade.pay";
}
public function setNotifyUrl($notifyUrl)
{
$this->notifyUrl=$notifyUrl;
}
public function getNotifyUrl()
{
return $this->notifyUrl;
}
public function setReturnUrl($returnUrl)
{
$this->returnUrl=$returnUrl;
}
public function getReturnUrl()
{
return $this->returnUrl;
}
public function getApiParas()
{
return $this->apiParas;
}
public function getTerminalType()
{
return $this->terminalType;
}
public function setTerminalType($terminalType)
{
$this->terminalType = $terminalType;
}
public function getTerminalInfo()
{
return $this->terminalInfo;
}
public function setTerminalInfo($terminalInfo)
{
$this->terminalInfo = $terminalInfo;
}
public function getProdCode()
{
return $this->prodCode;
}
public function setProdCode($prodCode)
{
$this->prodCode = $prodCode;
}
public function setApiVersion($apiVersion)
{
$this->apiVersion=$apiVersion;
}
public function getApiVersion()
{
return $this->apiVersion;
}
public function setNeedEncrypt($needEncrypt)
{
$this->needEncrypt=$needEncrypt;
}
public function getNeedEncrypt()
{
return $this->needEncrypt;
}
}

View File

@ -0,0 +1,122 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/7/13
* Time: 5:12 PM
*/
namespace app\common\alipay\request;
class AlipayTradeQueryRequest
{
/**
* 统一收单线下交易查询
修改路由策略到R
**/
private $bizContent;
private $apiParas = array();
private $terminalType;
private $terminalInfo;
private $prodCode;
private $apiVersion="1.0";
private $notifyUrl;
private $returnUrl;
private $needEncrypt=false;
public function setBizContent($bizContent)
{
$this->bizContent = $bizContent;
$this->apiParas["biz_content"] = $bizContent;
}
public function getBizContent()
{
return $this->bizContent;
}
public function getApiMethodName()
{
return "alipay.trade.query";
}
public function setNotifyUrl($notifyUrl)
{
$this->notifyUrl=$notifyUrl;
}
public function getNotifyUrl()
{
return $this->notifyUrl;
}
public function setReturnUrl($returnUrl)
{
$this->returnUrl=$returnUrl;
}
public function getReturnUrl()
{
return $this->returnUrl;
}
public function getApiParas()
{
return $this->apiParas;
}
public function getTerminalType()
{
return $this->terminalType;
}
public function setTerminalType($terminalType)
{
$this->terminalType = $terminalType;
}
public function getTerminalInfo()
{
return $this->terminalInfo;
}
public function setTerminalInfo($terminalInfo)
{
$this->terminalInfo = $terminalInfo;
}
public function getProdCode()
{
return $this->prodCode;
}
public function setProdCode($prodCode)
{
$this->prodCode = $prodCode;
}
public function setApiVersion($apiVersion)
{
$this->apiVersion=$apiVersion;
}
public function getApiVersion()
{
return $this->apiVersion;
}
public function setNeedEncrypt($needEncrypt)
{
$this->needEncrypt=$needEncrypt;
}
public function getNeedEncrypt()
{
return $this->needEncrypt;
}
}

View File

@ -0,0 +1,121 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/3/3
* Time: 5:42 PM
*/
namespace app\common\alipay\request;
class AlipayTradeRefundRequest
{
/**
* 统一收单交易退款接口
**/
private $bizContent;
private $apiParas = array();
private $terminalType;
private $terminalInfo;
private $prodCode;
private $apiVersion="1.0";
private $notifyUrl;
private $returnUrl;
private $needEncrypt=false;
public function setBizContent($bizContent)
{
$this->bizContent = $bizContent;
$this->apiParas["biz_content"] = $bizContent;
}
public function getBizContent()
{
return $this->bizContent;
}
public function getApiMethodName()
{
return "alipay.trade.refund";
}
public function setNotifyUrl($notifyUrl)
{
$this->notifyUrl=$notifyUrl;
}
public function getNotifyUrl()
{
return $this->notifyUrl;
}
public function setReturnUrl($returnUrl)
{
$this->returnUrl=$returnUrl;
}
public function getReturnUrl()
{
return $this->returnUrl;
}
public function getApiParas()
{
return $this->apiParas;
}
public function getTerminalType()
{
return $this->terminalType;
}
public function setTerminalType($terminalType)
{
$this->terminalType = $terminalType;
}
public function getTerminalInfo()
{
return $this->terminalInfo;
}
public function setTerminalInfo($terminalInfo)
{
$this->terminalInfo = $terminalInfo;
}
public function getProdCode()
{
return $this->prodCode;
}
public function setProdCode($prodCode)
{
$this->prodCode = $prodCode;
}
public function setApiVersion($apiVersion)
{
$this->apiVersion=$apiVersion;
}
public function getApiVersion()
{
return $this->apiVersion;
}
public function setNeedEncrypt($needEncrypt)
{
$this->needEncrypt=$needEncrypt;
}
public function getNeedEncrypt()
{
return $this->needEncrypt;
}
}

View File

@ -0,0 +1,58 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/6/10
* Time: 11:59 AM
*/
namespace app\common\classLibrary;
class ClSendMess
{
/**
* sendMinProgram
* @param $access_token
* @param $user_openid
* @param $page
* @param $keyword1
* @param $keyword2
* @param $keyword3
* @param $keyword4
* @param $keyword5
* @return bool|mixed
*/
public static function sendMinProgram($access_token,$user_openid,$page,$form_id,$keyword1,$keyword2,$keyword3,$keyword4,$keyword5)
{
$url = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=".$access_token;
$data = array(
'touser' => $user_openid,
'weapp_template_msg' => array(
'template_id' => '1i7Dl1rpDQyb9cm9WioWSNnogEb2UrfCKCimLy77WhQ',
'page' => $page,
'form_id' => $form_id,
'data' => array(
'keyword1' => array(
'value' => $keyword1
),
'keyword2' => array(
'value' => $keyword2
),
'keyword3' => array(
'value' => $keyword3
),
'keyword4' => array(
'value' => $keyword4
),
'keyword5' => array(
'value' => $keyword5
),
),
"emphasis_keyword" => "keyword1.DATA"
)
);
$rs = ClWechat::http_post($url,json_encode($data));
return $rs;
}
}

View File

@ -0,0 +1,95 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/3/2
* Time: 1:43 PM
*/
namespace app\common\classLibrary;
class ClWechat
{
/**
* http get
* @param $url
* @return mixed
*/
public static function httpGet($url) {
$curl = curl_init();
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_TIMEOUT, 500);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($curl, CURLOPT_URL, $url);
$res = curl_exec($curl);
curl_close($curl);
return $res;
}
/**
* http post
* @param $url
* @param $param
* @param bool|false $post_file
* @return bool|mixed
*/
public static function http_post($url,$param,$post_file=false){
$oCurl = curl_init();
if(stripos($url,"https://")!==FALSE){
curl_setopt($oCurl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($oCurl, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($oCurl, CURLOPT_SSLVERSION, 1); //CURL_SSLVERSION_TLSv1
}
if (is_string($param) || $post_file) {
$strPOST = $param;
} else {
$aPOST = array();
foreach($param as $key=>$val){
$aPOST[] = $key."=".urlencode($val);
}
$strPOST = join("&", $aPOST);
}
curl_setopt($oCurl, CURLOPT_URL, $url);
curl_setopt($oCurl, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt($oCurl, CURLOPT_POST,true);
curl_setopt($oCurl, CURLOPT_POSTFIELDS,$strPOST);
$sContent = curl_exec($oCurl);
$aStatus = curl_getinfo($oCurl);
curl_close($oCurl);
if(intval($aStatus["http_code"])==200){
return $sContent;
}else{
return false;
}
}
/**
* send_pic
* @param $url
* @param $data
* @return bool|mixed
*/
public static function send_pic($url,$data)
{
$curl = curl_init();
curl_setopt($curl, CURLOPT_SAFE_UPLOAD, true);
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_POST, 1 );
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_USERAGENT,"TEST");
$sContent = curl_exec($curl);
//$error = curl_error($curl);
$aStatus = curl_getinfo($curl);
curl_close($curl);
if(intval($aStatus["http_code"])==200){
return $sContent;
}else{
return false;
}
}
}

View File

@ -0,0 +1,110 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/6/13
* Time: 10:44 PM
*/
namespace app\common\image;
class imgcompress
{
private $src;
private $image;
private $imageinfo;
private $percent = 0.5;
/**
* 图片压缩
* @param $src 源图
* @param float $percent 压缩比例
*/
public function __construct($src, $percent=1)
{
$this->src = $src;
$this->percent = $percent;
}
/** 高清压缩图片
* @param string $saveName 提供图片名(可不带扩展名,用源图扩展名)用于保存。或不提供文件名直接显示
*/
public function compressImg($saveName='')
{
$this->_openImage();
if(!empty($saveName)) $this->_saveImage($saveName); //保存
else $this->_showImage();
}
/**
* 内部:打开图片
*/
private function _openImage()
{
list($width, $height, $type, $attr) = getimagesize($this->src);
$this->imageinfo = array(
'width'=>$width,
'height'=>$height,
'type'=>image_type_to_extension($type,false),
'attr'=>$attr
);
$fun = "imagecreatefrom".$this->imageinfo['type'];
$this->image = $fun($this->src);
$this->_thumpImage();
}
/**
* 内部:操作图片
*/
private function _thumpImage()
{
$new_width = $this->imageinfo['width'] * $this->percent;
$new_height = $this->imageinfo['height'] * $this->percent;
$image_thump = imagecreatetruecolor($new_width,$new_height);
//将原图复制带图片载体上面,并且按照一定比例压缩,极大的保持了清晰度
imagecopyresampled($image_thump,$this->image,0,0,0,0,$new_width,$new_height,$this->imageinfo['width'],$this->imageinfo['height']);
imagedestroy($this->image);
$this->image = $image_thump;
}
/**
* 输出图片:保存图片则用saveImage()
*/
private function _showImage()
{
header('Content-Type: image/'.$this->imageinfo['type']);
$funcs = "image".$this->imageinfo['type'];
$funcs($this->image);
}
/**
* 保存图片到硬盘:
* @param string $dstImgName 1、可指定字符串不带后缀的名称,使用源图扩展名 。2、直接指定目标图片名带扩展名。
*/
private function _saveImage($dstImgName)
{
if(empty($dstImgName)) return false;
$allowImgs = ['.jpg', '.jpeg', '.png', '.bmp', '.wbmp','.gif']; //如果目标图片名有后缀就用目标图片扩展名 后缀,如果没有,则用源图的扩展名
$dstExt = strrchr($dstImgName ,".");
$sourseExt = strrchr($this->src ,".");
if(!empty($dstExt)) $dstExt =strtolower($dstExt);
if(!empty($sourseExt)) $sourseExt =strtolower($sourseExt);
//有指定目标名扩展名
if(!empty($dstExt) && in_array($dstExt,$allowImgs)){
$dstName = $dstImgName;
}elseif(!empty($sourseExt) && in_array($sourseExt,$allowImgs)){
$dstName = $dstImgName.$sourseExt;
}else{
$dstName = $dstImgName.$this->imageinfo['type'];
}
$funcs = "image".$this->imageinfo['type'];
$funcs($this->image,$dstName);
}
/**
* 销毁图片
*/
public function __destruct(){
imagedestroy($this->image);
}
}

View File

@ -0,0 +1,17 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2020/8/22
* Time: 1:57 PM
*/
namespace app\common\lazada\lazop;
class Constants
{
static $log_level_debug = "DEBUG";
static $log_level_info = "INFO";
static $log_level_error = "ERROR";
}

View File

@ -0,0 +1,338 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2020/8/22
* Time: 11:43 AM
*/
namespace app\common\lazada\lazop;
use think\Exception;
class LazopClient
{
public $appkey;
public $secretKey;
public $gatewayUrl;
public $connectTimeout;
public $readTimeout;
protected $signMethod = "sha256";
protected $sdkVersion = "lazop-sdk-php-20180422";
public $logLevel;
public function getAppkey()
{
return $this->appkey;
}
public function __construct($url = "",$appkey = "",$secretKey = "")
{
$length = strlen($url);
if($length == 0)
{
throw new Exception("url is empty",0);
}
$this->gatewayUrl = $url;
$this->appkey = $appkey;
$this->secretKey = $secretKey;
$this->logLevel = Constants::$log_level_error;
}
protected function generateSign($apiName,$params)
{
ksort($params);
$stringToBeSigned = '';
$stringToBeSigned .= $apiName;
foreach ($params as $k => $v)
{
$stringToBeSigned .= "$k$v";
}
unset($k, $v);
return strtoupper($this->hmac_sha256($stringToBeSigned,$this->secretKey));
}
function hmac_sha256($data, $key){
return hash_hmac('sha256', $data, $key);
}
public function curl_get($url,$apiFields = null,$headerFields = null)
{
$ch = curl_init();
foreach ($apiFields as $key => $value)
{
$url .= "&" ."$key=" . urlencode($value);
}
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FAILONERROR, false);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
if($headerFields)
{
$headers = array();
foreach ($headerFields as $key => $value)
{
$headers[] = "$key: $value";
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
unset($headers);
}
if ($this->readTimeout)
{
curl_setopt($ch, CURLOPT_TIMEOUT, $this->readTimeout);
}
if ($this->connectTimeout)
{
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->connectTimeout);
}
curl_setopt ( $ch, CURLOPT_USERAGENT, $this->sdkVersion );
//https ignore ssl check ?
if(strlen($url) > 5 && strtolower(substr($url,0,5)) == "https" )
{
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
}
$output = curl_exec($ch);
$errno = curl_errno($ch);
if ($errno)
{
curl_close($ch);
throw new Exception($errno,0);
}
else
{
$httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if (200 !== $httpStatusCode)
{
throw new Exception($output,$httpStatusCode);
}
}
return $output;
}
public function curl_post($url, $postFields = null, $fileFields = null,$headerFields = null)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FAILONERROR, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
if ($this->readTimeout)
{
curl_setopt($ch, CURLOPT_TIMEOUT, $this->readTimeout);
}
if ($this->connectTimeout)
{
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->connectTimeout);
}
if($headerFields)
{
$headers = array();
foreach ($headerFields as $key => $value)
{
$headers[] = "$key: $value";
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
unset($headers);
}
curl_setopt ( $ch, CURLOPT_USERAGENT, $this->sdkVersion );
//https ignore ssl check ?
if(strlen($url) > 5 && strtolower(substr($url,0,5)) == "https" )
{
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
}
$delimiter = '-------------' . uniqid();
$data = '';
if($postFields != null)
{
foreach ($postFields as $name => $content)
{
$data .= "--" . $delimiter . "\r\n";
$data .= 'Content-Disposition: form-data; name="' . $name . '"';
$data .= "\r\n\r\n" . $content . "\r\n";
}
unset($name,$content);
}
if($fileFields != null)
{
foreach ($fileFields as $name => $file)
{
$data .= "--" . $delimiter . "\r\n";
$data .= 'Content-Disposition: form-data; name="' . $name . '"; filename="' . $file['name'] . "\" \r\n";
$data .= 'Content-Type: ' . $file['type'] . "\r\n\r\n";
$data .= $file['content'] . "\r\n";
}
unset($name,$file);
}
$data .= "--" . $delimiter . "--";
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER ,
array(
'Content-Type: multipart/form-data; boundary=' . $delimiter,
'Content-Length: ' . strlen($data)
)
);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
$response = curl_exec($ch);
unset($data);
$errno = curl_errno($ch);
if ($errno)
{
curl_close($ch);
throw new Exception($errno,0);
}
else
{
$httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if (200 !== $httpStatusCode)
{
throw new Exception($response,$httpStatusCode);
}
}
return $response;
}
public function execute(LazopRequest $request, $accessToken = null)
{
$sysParams["app_key"] = $this->appkey;
$sysParams["sign_method"] = $this->signMethod;
$sysParams["timestamp"] = $this->msectime();
if (null != $accessToken)
{
$sysParams["access_token"] = $accessToken;
}
$apiParams = $request->udfParams;
$requestUrl = $this->gatewayUrl;
if($this->endWith($requestUrl,"/"))
{
$requestUrl = substr($requestUrl, 0, -1);
}
$requestUrl .= $request->apiName;
$requestUrl .= '?';
$sysParams["partner_id"] = $this->sdkVersion;
if($this->logLevel == Constants::$log_level_debug)
{
$sysParams["debug"] = 'true';
}
$sysParams["sign"] = $this->generateSign($request->apiName,array_merge($apiParams, $sysParams));
foreach ($sysParams as $sysParamKey => $sysParamValue)
{
$requestUrl .= "$sysParamKey=" . urlencode($sysParamValue) . "&";
}
$requestUrl = substr($requestUrl, 0, -1);
$resp = '';
try
{
if($request->httpMethod == 'POST')
{
$resp = $this->curl_post($requestUrl, $apiParams, $request->fileParams,$request->headerParams);
}
else
{
$resp = $this->curl_get($requestUrl, $apiParams,$request->headerParams);
}
}
catch (Exception $e)
{
$this->logApiError($requestUrl,"HTTP_ERROR_" . $e->getCode(),$e->getMessage());
throw $e;
}
unset($apiParams);
$respObject = json_decode($resp);
if(isset($respObject->code) && $respObject->code != "0")
{
$this->logApiError($requestUrl, $respObject->code, $respObject->message);
} else
{
if($this->logLevel == Constants::$log_level_debug || $this->logLevel == Constants::$log_level_info)
{
$this->logApiError($requestUrl, '', '');
}
}
return $resp;
}
protected function logApiError($requestUrl, $errorCode, $responseTxt)
{
$localIp = isset($_SERVER["SERVER_ADDR"]) ? $_SERVER["SERVER_ADDR"] : "CLI";
$logger = new LazopLogger;
$logger->conf["log_file"] = $_SERVER['DOCUMENT_ROOT'].'/' . "logs/lazopsdk.log." . date("Y-m-d");
$logger->conf["separator"] = "^_^";
$logData = array(
date("Y-m-d H:i:s"),
$this->appkey,
$localIp,
PHP_OS,
$this->sdkVersion,
$requestUrl,
$errorCode,
str_replace("\n","",$responseTxt)
);
$logger->log($logData);
}
function msectime() {
list($msec, $sec) = explode(' ', microtime());
return $sec . '000';
}
function endWith($haystack, $needle) {
$length = strlen($needle);
if($length == 0)
{
return false;
}
return (substr($haystack, -$length) === $needle);
}
}

View File

@ -0,0 +1,52 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2020/8/22
* Time: 1:58 PM
*/
namespace app\common\lazada\lazop;
class LazopLogger
{
public $conf = array(
"separator" => "\t",
"log_file" => ""
);
private $fileHandle;
protected function getFileHandle()
{
if (null === $this->fileHandle)
{
if (empty($this->conf["log_file"]))
{
trigger_error("no log file spcified.");
}
$logDir = dirname($this->conf["log_file"]);
if (!is_dir($logDir))
{
mkdir($logDir, 0777, true);
}
$this->fileHandle = fopen($this->conf["log_file"], "a");
}
return $this->fileHandle;
}
public function log($logData)
{
if ("" == $logData || array() == $logData)
{
return false;
}
if (is_array($logData))
{
$logData = implode($this->conf["separator"], $logData);
}
$logData = $logData. "\n";
fwrite($this->getFileHandle(), $logData);
}
}

View File

@ -0,0 +1,89 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2020/8/22
* Time: 1:58 PM
*/
namespace app\common\lazada\lazop;
use think\Exception;
class LazopRequest
{
public $apiName;
public $headerParams = array();
public $udfParams = array();
public $fileParams = array();
public $httpMethod = 'POST';
public function __construct($apiName,$httpMethod = 'POST')
{
$this->apiName = $apiName;
$this->httpMethod = $httpMethod;
if($this->startWith($apiName,"//"))
{
throw new Exception("api name is invalid. It should be start with /");
}
}
function addApiParam($key,$value)
{
if(!is_string($key))
{
throw new Exception("api param key should be string");
}
if(is_object($value))
{
$this->udfParams[$key] = json_decode($value);
}
else
{
$this->udfParams[$key] = $value;
}
}
function addFileParam($key,$content,$mimeType = 'application/octet-stream')
{
if(!is_string($key))
{
throw new Exception("api file param key should be string");
}
$file = array(
'type' => $mimeType,
'content' => $content,
'name' => $key
);
$this->fileParams[$key] = $file;
}
function addHttpHeaderParam($key,$value)
{
if(!is_string($key))
{
throw new Exception("http header param key should be string");
}
if(!is_string($value))
{
throw new Exception("http header param value should be string");
}
$this->headerParams[$key] = $value;
}
function startWith($str, $needle) {
return strpos($str, $needle) === 0;
}
}

View File

@ -0,0 +1,21 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2020/8/22
* Time: 2:00 PM
*/
namespace app\common\lazada\lazop;
class UrlConstants
{
static $api_gateway_url_sg = "https://api.lazada.sg/rest";
static $api_gateway_url_my = "https://api.lazada.com.my/rest";
static $api_gateway_url_vn = "https://api.lazada.vn/rest";
static $api_gateway_url_th = "https://api.lazada.co.th/rest";
static $api_gateway_url_ph = "https://api.lazada.com.ph/rest";
static $api_gateway_url_id = "https://api.lazada.co.id/rest";
static $api_authorization_url = "https://auth.lazada.com/rest";
}

View File

@ -0,0 +1,164 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/2/24
* Time: 12:35 PM
*/
namespace app\common\wxpay;
use app\common\wxpay\lib\WxPayApi;
use app\common\wxpay\lib\WxPayException;
use app\common\wxpay\lib\WxPayOrderQuery;
use app\common\wxpay\lib\WxPayReverse;
class MicroPay
{
/**
*
* 提交刷卡支付,并且确认结果,接口比较慢
* @param WxPayMicroPay $microPayInput
* @throws WxpayException
* @return 返回查询接口的结果
*/
public function pay($microPayInput)
{
//①、提交被扫支付
$config = new WxPayConfig();
$result = WxPayApi::micropay($config, $microPayInput, 5);
//如果返回成功
if(!array_key_exists("return_code", $result)
|| !array_key_exists("result_code", $result))
{
echo "接口调用失败,请确认是否输入是否有误!";
throw new WxPayException("接口调用失败!");
}
//取订单号
$out_trade_no = $microPayInput->GetOut_trade_no();
//sub_mch_id
$sub_mch_id = $microPayInput->GetSubMch_id();
//②、接口调用成功,明确返回调用失败
if($result["return_code"] == "SUCCESS" &&
$result["result_code"] == "FAIL" &&
$result["err_code"] != "USERPAYING" &&
$result["err_code"] != "SYSTEMERROR")
{
return $result;
}
//③、确认支付是否成功
$queryTimes = 10;
while($queryTimes > 0)
{
$succResult = 0;
$queryResult = $this->query($out_trade_no,$sub_mch_id, $succResult);
//如果需要等待1s后继续
if($succResult == 2){
sleep(2);
continue;
} else if($succResult == 1){//查询成功
return $queryResult;
} else {//订单交易失败
break;
}
}
//④、次确认失败,则撤销订单
if(!$this->cancel($out_trade_no,$sub_mch_id))
{
throw new WxpayException("撤销单失败!");
}
return array(
'result_code' => 'FAIL',
'err_code_des' => '支付失败'
);
}
/**
*
* 查询订单情况
* @param string $out_trade_no 商户订单号
* @param int $succCode 查询订单结果
* @return 0 订单不成功1表示订单成功2表示继续等待
*/
public function query($out_trade_no,$sub_mch_id, &$succCode)
{
$queryOrderInput = new WxPayOrderQuery();
$queryOrderInput->SetOut_trade_no($out_trade_no);
$queryOrderInput->SetSubMch_id($sub_mch_id);
$config = new WxPayConfig();
$result = array();
try{
$result = WxPayApi::orderQuery($config, $queryOrderInput);
} catch(Exception $e) {
Log::ERROR(json_encode($e));
}
if($result["return_code"] == "SUCCESS"
&& $result["result_code"] == "SUCCESS")
{
//支付成功
if($result["trade_state"] == "SUCCESS"){
$succCode = 1;
return $result;
}
//用户支付中
else if($result["trade_state"] == "USERPAYING"){
$succCode = 2;
return false;
}
else if($result["trade_state"] == "NOTPAY"){
$succCode = 0;
return $result;
}
}
//如果返回错误码为“此交易订单号不存在”则直接认定失败
if($result["err_code"] == "ORDERNOTEXIST")
{
$succCode = 0;
} else{
//如果是系统错误,则后续继续
$succCode = 2;
}
return false;
}
/**
*
* 撤销订单如果失败会重复调用10次
* @param string $out_trade_no
* @param 调用深度 $depth
*/
public function cancel($out_trade_no, $sub_mch_id,$depth = 0)
{
try {
if($depth > 10){
return false;
}
$clostOrder = new WxPayReverse();
$clostOrder->SetOut_trade_no($out_trade_no);
$clostOrder->SetSubMch_id($sub_mch_id);
$config = new WxPayConfig();
$result = WxPayApi::reverse($config, $clostOrder);
//接口调用失败
if($result["return_code"] != "SUCCESS"){
return false;
}
//如果结果为success且不需要重新调用撤销则表示撤销成功
if($result["result_code"] == "SUCCESS"
&& $result["recall"] == "N"){
return true;
} else if($result["recall"] == "Y") {
return $this->cancel($out_trade_no, ++$depth);
}
} catch(Exception $e) {
Log::ERROR(json_encode($e));
}
return false;
}
}

View File

@ -0,0 +1,112 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/2/24
* Time: 12:07 PM
*/
namespace app\common\wxpay;
use app\common\wxpay\lib\WxPayConfigInterface;
class WxPayConfig extends WxPayConfigInterface
{
//=======【基本信息设置】=====================================
/**
* TODO: 修改这里配置为您自己申请的商户信息
* 微信公众号信息配置
*
* APPID绑定支付的APPID必须配置开户邮件中可查看
*
* MCHID商户号必须配置开户邮件中可查看
*
*/
public function GetAppId()
{
return 'wxe810ed04c27a7d04';
}
public function GetMerchantId()
{
return '1521344381';
}
//=======【支付相关配置:支付成功回调地址/签名方式】===================================
/**
* TODO:支付回调url
* 签名和验证签名方式, 支持md5和sha256方式
**/
public function GetNotifyUrl()
{
return "";
}
public function GetSignType()
{
return "HMAC-SHA256";
}
//=======【curl代理设置】===================================
/**
* TODO这里设置代理机器只有需要代理的时候才设置不需要代理请设置为0.0.0.0和0
* 本例程通过curl使用HTTP POST方法此处可修改代理服务器
* 默认CURL_PROXY_HOST=0.0.0.0和CURL_PROXY_PORT=0,此时不开启代理(如有需要才设置)
* @var unknown_type
*/
public function GetProxy(&$proxyHost, &$proxyPort)
{
$proxyHost = "0.0.0.0";
$proxyPort = 0;
}
//=======【上报信息配置】===================================
/**
* TODO接口调用上报等级默认紧错误上报注意上报超时间为【1s】上报无论成败【永不抛出异常】
* 不会影响接口调用流程),开启上报之后,方便微信监控请求调用的质量,建议至少
* 开启错误上报。
* 上报等级0.关闭上报; 1.仅错误出错上报; 2.全量上报
* @var int
*/
public function GetReportLevenl()
{
return 1;
}
//=======【商户密钥信息-需要业务方继承】===================================
/*
* KEY商户支付密钥参考开户邮件设置必须配置登录商户平台自行设置, 请妥善保管, 避免密钥泄露
* 设置地址https://pay.weixin.qq.com/index.php/account/api_cert
*
* APPSECRET公众帐号secert仅JSAPI支付的时候需要配置 登录公众平台,进入开发者中心可设置), 请妥善保管, 避免密钥泄露
* 获取地址https://mp.weixin.qq.com/advanced/advanced?action=dev&t=advanced/dev&token=2005451881&lang=zh_CN
* @var string
*/
public function GetKey()
{
return '860f1bfe4fb6ab396dcf217e246178f5';
}
public function GetAppSecret()
{
return 'b4a11fc42462c8fd27b461977b5140b4';
}
//=======【证书路径设置-需要业务方继承】=====================================
/**
* TODO设置商户证书路径
* 证书路径,注意应该填写绝对路径(仅退款、撤销订单时需要,可登录商户平台下载,
* API证书下载地址https://pay.weixin.qq.com/index.php/account/api_cert下载之前需要安装商户操作证书
* 注意:
* 1.证书文件不能放在web服务器虚拟目录应放在有访问权限控制的目录中防止被他人下载
* 2.建议将证书文件名改为复杂且不容易猜测的文件名;
* 3.商户服务器要做好病毒和木马防护工作,不被非法侵入者窃取证书文件。
* @var path
*/
public function GetSSLCertPath(&$sslCertPath, &$sslKeyPath)
{
$sslCertPath = '/data/cert/apiclient_cert.pem';
$sslKeyPath = '/data/cert/apiclient_key.pem';
}
}

View File

@ -0,0 +1,604 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/2/23
* Time: 6:21 PM
*/
namespace app\common\wxpay\lib;
class WxPayApi
{
/**
*
* 统一下单WxPayUnifiedOrder中out_trade_no、body、total_fee、trade_type必填
* appid、mchid、spbill_create_ip、nonce_str不需要填入
* @param WxPayConfigInterface $config 配置对象
* @param WxPayUnifiedOrder $inputObj
* @param int $timeOut
* @throws WxPayException
* @return 成功时返回,其他抛异常
*/
public static function unifiedOrder($config, $inputObj, $timeOut = 6)
{
$url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
//检测必填参数
if(!$inputObj->IsOut_trade_noSet()) {
throw new WxPayException("缺少统一支付接口必填参数out_trade_no");
}else if(!$inputObj->IsBodySet()){
throw new WxPayException("缺少统一支付接口必填参数body");
}else if(!$inputObj->IsTotal_feeSet()) {
throw new WxPayException("缺少统一支付接口必填参数total_fee");
}else if(!$inputObj->IsTrade_typeSet()) {
throw new WxPayException("缺少统一支付接口必填参数trade_type");
}
//关联参数
if($inputObj->GetTrade_type() == "JSAPI" && !$inputObj->IsOpenidSet()){
throw new WxPayException("统一支付接口中缺少必填参数openidtrade_type为JSAPI时openid为必填参数");
}
if($inputObj->GetTrade_type() == "NATIVE" && !$inputObj->IsProduct_idSet()){
throw new WxPayException("统一支付接口中缺少必填参数product_idtrade_type为JSAPI时product_id为必填参数");
}
//异步通知url未设置则使用配置文件中的url
if(!$inputObj->IsNotify_urlSet() && $config->GetNotifyUrl() != ""){
$inputObj->SetNotify_url($config->GetNotifyUrl());//异步通知url
}
$inputObj->SetAppid($config->GetAppId());//公众账号ID
$inputObj->SetMch_id($config->GetMerchantId());//商户号
$inputObj->SetSpbill_create_ip($_SERVER['REMOTE_ADDR']);//终端ip
$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
//签名
$inputObj->SetSign($config);
$xml = $inputObj->ToXml();
$startTimeStamp = self::getMillisecond();//请求开始时间
$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
$result = WxPayResults::Init($config, $response);
self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
return $result;
}
/**
*
* 查询订单WxPayOrderQuery中out_trade_no、transaction_id至少填一个
* appid、mchid、spbill_create_ip、nonce_str不需要填入
* @param WxPayConfigInterface $config 配置对象
* @param WxPayOrderQuery $inputObj
* @param int $timeOut
* @throws WxPayException
* @return 成功时返回,其他抛异常
*/
public static function orderQuery($config, $inputObj, $timeOut = 6)
{
$url = "https://api.mch.weixin.qq.com/pay/orderquery";
//检测必填参数
if(!$inputObj->IsOut_trade_noSet() && !$inputObj->IsTransaction_idSet()) {
throw new WxPayException("订单查询接口中out_trade_no、transaction_id至少填一个");
}
$inputObj->SetAppid($config->GetAppId());//公众账号ID
$inputObj->SetMch_id($config->GetMerchantId());//商户号
$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
$inputObj->SetSign($config);//签名
$xml = $inputObj->ToXml();
$startTimeStamp = self::getMillisecond();//请求开始时间
$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
$result = WxPayResults::Init($config, $response);
self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
return $result;
}
/**
*
* 关闭订单WxPayCloseOrder中out_trade_no必填
* appid、mchid、spbill_create_ip、nonce_str不需要填入
* @param WxPayConfigInterface $config 配置对象
* @param WxPayCloseOrder $inputObj
* @param int $timeOut
* @throws WxPayException
* @return 成功时返回,其他抛异常
*/
public static function closeOrder($config, $inputObj, $timeOut = 6)
{
$url = "https://api.mch.weixin.qq.com/pay/closeorder";
//检测必填参数
if(!$inputObj->IsOut_trade_noSet()) {
throw new WxPayException("订单查询接口中out_trade_no必填");
}
$inputObj->SetAppid($config->GetAppId());//公众账号ID
$inputObj->SetMch_id($config->GetMerchantId());//商户号
$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
$inputObj->SetSign($config);//签名
$xml = $inputObj->ToXml();
$startTimeStamp = self::getMillisecond();//请求开始时间
$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
$result = WxPayResults::Init($config, $response);
self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
return $result;
}
/**
*
* 申请退款WxPayRefund中out_trade_no、transaction_id至少填一个且
* out_refund_no、total_fee、refund_fee、op_user_id为必填参数
* appid、mchid、spbill_create_ip、nonce_str不需要填入
* @param WxPayConfigInterface $config 配置对象
* @param WxPayRefund $inputObj
* @param int $timeOut
* @throws WxPayException
* @return 成功时返回,其他抛异常
*/
public static function refund($config, $inputObj, $timeOut = 6)
{
$url = "https://api.mch.weixin.qq.com/secapi/pay/refund";
//检测必填参数
if(!$inputObj->IsOut_trade_noSet() && !$inputObj->IsTransaction_idSet()) {
throw new WxPayException("退款申请接口中out_trade_no、transaction_id至少填一个");
}else if(!$inputObj->IsOut_refund_noSet()){
throw new WxPayException("退款申请接口中缺少必填参数out_refund_no");
}else if(!$inputObj->IsTotal_feeSet()){
throw new WxPayException("退款申请接口中缺少必填参数total_fee");
}else if(!$inputObj->IsRefund_feeSet()){
throw new WxPayException("退款申请接口中缺少必填参数refund_fee");
}
$inputObj->SetAppid($config->GetAppId());//公众账号ID
$inputObj->SetMch_id($config->GetMerchantId());//商户号
$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
$inputObj->SetSign($config);//签名
$xml = $inputObj->ToXml();
$startTimeStamp = self::getMillisecond();//请求开始时间
$response = self::postXmlCurl($config, $xml, $url, true, $timeOut);
$result = WxPayResults::Init($config, $response);
self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
return $result;
}
/**
*
* 查询退款
* 提交退款申请后,通过调用该接口查询退款状态。退款有一定延时,
* 用零钱支付的退款20分钟内到账银行卡支付的退款3个工作日后重新查询退款状态。
* WxPayRefundQuery中out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个
* appid、mchid、spbill_create_ip、nonce_str不需要填入
* @param WxPayConfigInterface $config 配置对象
* @param WxPayRefundQuery $inputObj
* @param int $timeOut
* @throws WxPayException
* @return 成功时返回,其他抛异常
*/
public static function refundQuery($config, $inputObj, $timeOut = 6)
{
$url = "https://api.mch.weixin.qq.com/pay/refundquery";
//检测必填参数
if(!$inputObj->IsOut_refund_noSet() &&
!$inputObj->IsOut_trade_noSet() &&
!$inputObj->IsTransaction_idSet() &&
!$inputObj->IsRefund_idSet()) {
throw new WxPayException("退款查询接口中out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个");
}
$inputObj->SetAppid($config->GetAppId());//公众账号ID
$inputObj->SetMch_id($config->GetMerchantId());//商户号
$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
$inputObj->SetSign($config);//签名
$xml = $inputObj->ToXml();
$startTimeStamp = self::getMillisecond();//请求开始时间
$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
$result = WxPayResults::Init($config, $response);
self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
return $result;
}
/**
* 下载对账单WxPayDownloadBill中bill_date为必填参数
* appid、mchid、spbill_create_ip、nonce_str不需要填入
* @param WxPayConfigInterface $config 配置对象
* @param WxPayDownloadBill $inputObj
* @param int $timeOut
* @throws WxPayException
* @return 成功时返回,其他抛异常
*/
public static function downloadBill($config, $inputObj, $timeOut = 6)
{
$url = "https://api.mch.weixin.qq.com/pay/downloadbill";
//检测必填参数
if(!$inputObj->IsBill_dateSet()) {
throw new WxPayException("对账单接口中缺少必填参数bill_date");
}
$inputObj->SetAppid($config->GetAppId());//公众账号ID
$inputObj->SetMch_id($config->GetMerchantId());//商户号
$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
$inputObj->SetSign($config);//签名
$xml = $inputObj->ToXml();
$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
if(substr($response, 0 , 5) == "<xml>"){
return "";
}
return $response;
}
/**
* 提交被扫支付API
* 收银员使用扫码设备读取微信用户刷卡授权码以后,二维码或条码信息传送至商户收银台,
* 由商户收银台或者商户后台调用该接口发起支付。
* WxPayWxPayMicroPay中body、out_trade_no、total_fee、auth_code参数必填
* appid、mchid、spbill_create_ip、nonce_str不需要填入
* @param WxPayConfigInterface $config 配置对象
* @param WxPayWxPayMicroPay $inputObj
* @param int $timeOut
*/
public static function micropay($config, $inputObj, $timeOut = 10)
{
$url = "https://api.mch.weixin.qq.com/pay/micropay";
//检测必填参数
if(!$inputObj->IsBodySet()) {
throw new WxPayException("提交被扫支付API接口中缺少必填参数body");
} else if(!$inputObj->IsOut_trade_noSet()) {
throw new WxPayException("提交被扫支付API接口中缺少必填参数out_trade_no");
} else if(!$inputObj->IsTotal_feeSet()) {
throw new WxPayException("提交被扫支付API接口中缺少必填参数total_fee");
} else if(!$inputObj->IsAuth_codeSet()) {
throw new WxPayException("提交被扫支付API接口中缺少必填参数auth_code");
}
$inputObj->SetSpbill_create_ip($_SERVER['REMOTE_ADDR']);//终端ip
$inputObj->SetAppid($config->GetAppId());//公众账号ID
$inputObj->SetMch_id($config->GetMerchantId());//商户号
$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
$inputObj->SetSign($config);//签名
$xml = $inputObj->ToXml();
$startTimeStamp = self::getMillisecond();//请求开始时间
$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
$result = WxPayResults::Init($config, $response);
self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
return $result;
}
/**
*
* 撤销订单API接口WxPayReverse中参数out_trade_no和transaction_id必须填写一个
* appid、mchid、spbill_create_ip、nonce_str不需要填入
* @param WxPayConfigInterface $config 配置对象
* @param WxPayReverse $inputObj
* @param int $timeOut
* @throws WxPayException
*/
public static function reverse($config, $inputObj, $timeOut = 6)
{
$url = "https://api.mch.weixin.qq.com/secapi/pay/reverse";
//检测必填参数
if(!$inputObj->IsOut_trade_noSet() && !$inputObj->IsTransaction_idSet()) {
throw new WxPayException("撤销订单API接口中参数out_trade_no和transaction_id必须填写一个");
}
$inputObj->SetAppid($config->GetAppId());//公众账号ID
$inputObj->SetMch_id($config->GetMerchantId());//商户号
$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
$inputObj->SetSign($config);//签名
$xml = $inputObj->ToXml();
$startTimeStamp = self::getMillisecond();//请求开始时间
$response = self::postXmlCurl($config, $xml, $url, true, $timeOut);
$result = WxPayResults::Init($config, $response);
self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
return $result;
}
/**
*
* 测速上报该方法内部封装在report中使用时请注意异常流程
* WxPayReport中interface_url、return_code、result_code、user_ip、execute_time_必填
* appid、mchid、spbill_create_ip、nonce_str不需要填入
* @param WxPayConfigInterface $config 配置对象
* @param WxPayReport $inputObj
* @param int $timeOut
* @throws WxPayException
* @return 成功时返回,其他抛异常
*/
public static function report($config, $inputObj, $timeOut = 1)
{
$url = "https://api.mch.weixin.qq.com/payitil/report";
//检测必填参数
if(!$inputObj->IsInterface_urlSet()) {
throw new WxPayException("接口URL缺少必填参数interface_url");
} if(!$inputObj->IsReturn_codeSet()) {
throw new WxPayException("返回状态码缺少必填参数return_code");
} if(!$inputObj->IsResult_codeSet()) {
throw new WxPayException("业务结果缺少必填参数result_code");
} if(!$inputObj->IsUser_ipSet()) {
throw new WxPayException("访问接口IP缺少必填参数user_ip");
} if(!$inputObj->IsExecute_time_Set()) {
throw new WxPayException("接口耗时缺少必填参数execute_time_");
}
$inputObj->SetAppid($config->GetAppId());//公众账号ID
$inputObj->SetMch_id($config->GetMerchantId());//商户号
$inputObj->SetUser_ip($_SERVER['REMOTE_ADDR']);//终端ip
$inputObj->SetTime(date("YmdHis"));//商户上报时间
$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
$inputObj->SetSign($config);//签名
$xml = $inputObj->ToXml();
$startTimeStamp = self::getMillisecond();//请求开始时间
$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
return $response;
}
/**
*
* 生成二维码规则,模式一生成支付二维码
* appid、mchid、spbill_create_ip、nonce_str不需要填入
* @param WxPayConfigInterface $config 配置对象
* @param WxPayBizPayUrl $inputObj
* @param int $timeOut
* @throws WxPayException
* @return 成功时返回,其他抛异常
*/
public static function bizpayurl($config, $inputObj, $timeOut = 6)
{
if(!$inputObj->IsProduct_idSet()){
throw new WxPayException("生成二维码缺少必填参数product_id");
}
$inputObj->SetAppid($config->GetAppId());//公众账号ID
$inputObj->SetMch_id($config->GetMerchantId());//商户号
$inputObj->SetTime_stamp(time());//时间戳
$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
$inputObj->SetSign($config);//签名
return $inputObj->GetValues();
}
/**
*
* 转换短链接
* 该接口主要用于扫码原生支付模式一中的二维码链接转成短链接(weixin://wxpay/s/XXXXXX)
* 减小二维码数据量,提升扫描速度和精确度。
* appid、mchid、spbill_create_ip、nonce_str不需要填入
* @param WxPayConfigInterface $config 配置对象
* @param WxPayShortUrl $inputObj
* @param int $timeOut
* @throws WxPayException
* @return 成功时返回,其他抛异常
*/
public static function shorturl($config, $inputObj, $timeOut = 6)
{
$url = "https://api.mch.weixin.qq.com/tools/shorturl";
//检测必填参数
if(!$inputObj->IsLong_urlSet()) {
throw new WxPayException("需要转换的URL签名用原串传输需URL encode");
}
$inputObj->SetAppid($config->GetAppId());//公众账号ID
$inputObj->SetMch_id($config->GetMerchantId());//商户号
$inputObj->SetNonce_str(self::getNonceStr());//随机字符串
$inputObj->SetSign($config);//签名
$xml = $inputObj->ToXml();
$startTimeStamp = self::getMillisecond();//请求开始时间
$response = self::postXmlCurl($config, $xml, $url, false, $timeOut);
$result = WxPayResults::Init($config, $response);
self::reportCostTime($config, $url, $startTimeStamp, $result);//上报请求花费时间
return $result;
}
/**
*
* 支付结果通用通知
* @param function $callback
* 直接回调函数使用方法: notify(you_function);
* 回调类成员函数方法:notify(array($this, you_function));
* $callback 原型为function function_name($data){}
*/
public static function notify($config, $callback, &$msg)
{
if (!isset($GLOBALS['HTTP_RAW_POST_DATA'])) {
# 如果没有数据,直接返回失败
return false;
}
//如果返回成功则验证签名
try {
//获取通知的数据
$xml = $GLOBALS['HTTP_RAW_POST_DATA'];
$result = WxPayNotifyResults::Init($config, $xml);
} catch (WxPayException $e){
$msg = $e->errorMessage();
return false;
}
return call_user_func($callback, $result);
}
/**
*
* 产生随机字符串不长于32位
* @param int $length
* @return 产生的随机字符串
*/
public static function getNonceStr($length = 32)
{
$chars = "abcdefghijklmnopqrstuvwxyz0123456789";
$str ="";
for ( $i = 0; $i < $length; $i++ ) {
$str .= substr($chars, mt_rand(0, strlen($chars)-1), 1);
}
return $str;
}
/**
* 直接输出xml
* @param string $xml
*/
public static function replyNotify($xml)
{
echo $xml;
}
/**
*
* 上报数据, 上报的时候将屏蔽所有异常流程
* @param WxPayConfigInterface $config 配置对象
* @param string $usrl
* @param int $startTimeStamp
* @param array $data
*/
private static function reportCostTime($config, $url, $startTimeStamp, $data)
{
//如果不需要上报数据
$reportLevenl = $config->GetReportLevenl();
if($reportLevenl == 0){
return;
}
//如果仅失败上报
if($reportLevenl == 1 &&
array_key_exists("return_code", $data) &&
$data["return_code"] == "SUCCESS" &&
array_key_exists("result_code", $data) &&
$data["result_code"] == "SUCCESS")
{
return;
}
//上报逻辑
$endTimeStamp = self::getMillisecond();
$objInput = new WxPayReport();
$objInput->SetInterface_url($url);
$objInput->SetExecute_time_($endTimeStamp - $startTimeStamp);
//返回状态码
if(array_key_exists("return_code", $data)){
$objInput->SetReturn_code($data["return_code"]);
}
//返回信息
if(array_key_exists("return_msg", $data)){
$objInput->SetReturn_msg($data["return_msg"]);
}
//业务结果
if(array_key_exists("result_code", $data)){
$objInput->SetResult_code($data["result_code"]);
}
//错误代码
if(array_key_exists("err_code", $data)){
$objInput->SetErr_code($data["err_code"]);
}
//错误代码描述
if(array_key_exists("err_code_des", $data)){
$objInput->SetErr_code_des($data["err_code_des"]);
}
//商户订单号
if(array_key_exists("out_trade_no", $data)){
$objInput->SetOut_trade_no($data["out_trade_no"]);
}
//设备号
if(array_key_exists("device_info", $data)){
$objInput->SetDevice_info($data["device_info"]);
}
try{
self::report($config, $objInput);
} catch (WxPayException $e){
//不做任何处理
}
}
/**
* 以post方式提交xml到对应的接口url
*
* @param WxPayConfigInterface $config 配置对象
* @param string $xml 需要post的xml数据
* @param string $url url
* @param bool $useCert 是否需要证书,默认不需要
* @param int $second url执行超时时间默认30s
* @throws WxPayException
*/
private static function postXmlCurl($config, $xml, $url, $useCert = false, $second = 30)
{
$ch = curl_init();
$curlVersion = curl_version();
$ua = "WXPaySDK/3.0.9 (".PHP_OS.") PHP/".PHP_VERSION." CURL/".$curlVersion['version']." "
.$config->GetMerchantId();
//设置超时
curl_setopt($ch, CURLOPT_TIMEOUT, $second);
$proxyHost = "0.0.0.0";
$proxyPort = 0;
$config->GetProxy($proxyHost, $proxyPort);
//如果有配置代理这里就设置代理
if($proxyHost != "0.0.0.0" && $proxyPort != 0){
curl_setopt($ch,CURLOPT_PROXY, $proxyHost);
curl_setopt($ch,CURLOPT_PROXYPORT, $proxyPort);
}
curl_setopt($ch,CURLOPT_URL, $url);
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE);
curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验
curl_setopt($ch,CURLOPT_USERAGENT, $ua);
//设置header
curl_setopt($ch, CURLOPT_HEADER, FALSE);
//要求结果为字符串且输出到屏幕上
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
if($useCert == true){
//设置证书
//使用证书cert 与 key 分别属于两个.pem文件
//证书文件请放入服务器的非web目录下
$sslCertPath = "";
$sslKeyPath = "";
$config->GetSSLCertPath($sslCertPath, $sslKeyPath);
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
$data = curl_exec($ch);
//返回结果
if($data){
curl_close($ch);
return $data;
} else {
$error = curl_errno($ch);
curl_close($ch);
throw new WxPayException("curl出错错误码:$error");
}
}
/**
* 获取毫秒级别的时间戳
*/
private static function getMillisecond()
{
//获取毫秒的时间戳
$time = explode ( " ", microtime () );
$time = $time[1] . ($time[0] * 1000);
$time2 = explode( ".", $time );
$time = $time2[0];
return $time;
}
}

View File

@ -0,0 +1,139 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/2/24
* Time: 7:24 PM
*/
namespace app\common\wxpay\lib;
class WxPayBizPayUrl extends WxPayDataBaseSignMd5
{
/**
* 设置微信分配的公众账号ID
* @param string $value
**/
public function SetAppid($value)
{
$this->values['appid'] = $value;
}
/**
* 获取微信分配的公众账号ID的值
* @return
**/
public function GetAppid()
{
return $this->values['appid'];
}
/**
* 判断微信分配的公众账号ID是否存在
* @return true false
**/
public function IsAppidSet()
{
return array_key_exists('appid', $this->values);
}
/**
* 设置微信支付分配的商户号
* @param string $value
**/
public function SetMch_id($value)
{
$this->values['mch_id'] = $value;
}
/**
* 获取微信支付分配的商户号的值
* @return
**/
public function GetMch_id()
{
return $this->values['mch_id'];
}
/**
* 判断微信支付分配的商户号是否存在
* @return true false
**/
public function IsMch_idSet()
{
return array_key_exists('mch_id', $this->values);
}
/**
* 设置支付时间戳
* @param string $value
**/
public function SetTime_stamp($value)
{
$this->values['time_stamp'] = $value;
}
/**
* 获取支付时间戳的值
* @return
**/
public function GetTime_stamp()
{
return $this->values['time_stamp'];
}
/**
* 判断支付时间戳是否存在
* @return true false
**/
public function IsTime_stampSet()
{
return array_key_exists('time_stamp', $this->values);
}
/**
* 设置随机字符串
* @param string $value
**/
public function SetNonce_str($value)
{
$this->values['nonce_str'] = $value;
}
/**
* 获取随机字符串的值
* @return
**/
public function GetNonce_str()
{
return $this->values['nonce_str'];
}
/**
* 判断随机字符串是否存在
* @return true false
**/
public function IsNonce_strSet()
{
return array_key_exists('nonce_str', $this->values);
}
/**
* 设置商品ID
* @param string $value
**/
public function SetProduct_id($value)
{
$this->values['product_id'] = $value;
}
/**
* 获取商品ID的值
* @return
**/
public function GetProduct_id()
{
return $this->values['product_id'];
}
/**
* 判断商品ID是否存在
* @return true false
**/
public function IsProduct_idSet()
{
return array_key_exists('product_id', $this->values);
}
}

View File

@ -0,0 +1,116 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/2/24
* Time: 7:09 PM
*/
namespace app\common\wxpay\lib;
class WxPayCloseOrder extends WxPayDataBase
{
/**
* 设置微信分配的公众账号ID
* @param string $value
**/
public function SetAppid($value)
{
$this->values['appid'] = $value;
}
/**
* 获取微信分配的公众账号ID的值
* @return
**/
public function GetAppid()
{
return $this->values['appid'];
}
/**
* 判断微信分配的公众账号ID是否存在
* @return true false
**/
public function IsAppidSet()
{
return array_key_exists('appid', $this->values);
}
/**
* 设置微信支付分配的商户号
* @param string $value
**/
public function SetMch_id($value)
{
$this->values['mch_id'] = $value;
}
/**
* 获取微信支付分配的商户号的值
* @return
**/
public function GetMch_id()
{
return $this->values['mch_id'];
}
/**
* 判断微信支付分配的商户号是否存在
* @return true false
**/
public function IsMch_idSet()
{
return array_key_exists('mch_id', $this->values);
}
/**
* 设置商户系统内部的订单号
* @param string $value
**/
public function SetOut_trade_no($value)
{
$this->values['out_trade_no'] = $value;
}
/**
* 获取商户系统内部的订单号的值
* @return
**/
public function GetOut_trade_no()
{
return $this->values['out_trade_no'];
}
/**
* 判断商户系统内部的订单号是否存在
* @return true false
**/
public function IsOut_trade_noSet()
{
return array_key_exists('out_trade_no', $this->values);
}
/**
* 设置商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号
* @param string $value
**/
public function SetNonce_str($value)
{
$this->values['nonce_str'] = $value;
}
/**
* 获取商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号的值
* @return
**/
public function GetNonce_str()
{
return $this->values['nonce_str'];
}
/**
* 判断商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号是否存在
* @return true false
**/
public function IsNonce_strSet()
{
return array_key_exists('nonce_str', $this->values);
}
}

View File

@ -0,0 +1,82 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/2/23
* Time: 6:31 PM
*/
namespace app\common\wxpay\lib;
abstract class WxPayConfigInterface
{
//=======【基本信息设置】=====================================
/**
* TODO: 修改这里配置为您自己申请的商户信息
* 微信公众号信息配置
*
* APPID绑定支付的APPID必须配置开户邮件中可查看
*
* MCHID商户号必须配置开户邮件中可查看
*
*/
public abstract function GetAppId();
public abstract function GetMerchantId();
//=======【支付相关配置:支付成功回调地址/签名方式】===================================
/**
* TODO:支付回调url
* 签名和验证签名方式, 支持md5和sha256方式
**/
public abstract function GetNotifyUrl();
public abstract function GetSignType();
//=======【curl代理设置】===================================
/**
* TODO这里设置代理机器只有需要代理的时候才设置不需要代理请设置为0.0.0.0和0
* 本例程通过curl使用HTTP POST方法此处可修改代理服务器
* 默认CURL_PROXY_HOST=0.0.0.0和CURL_PROXY_PORT=0,此时不开启代理(如有需要才设置)
* @var unknown_type
*/
public abstract function GetProxy(&$proxyHost, &$proxyPort);
//=======【上报信息配置】===================================
/**
* TODO接口调用上报等级默认紧错误上报注意上报超时间为【1s】上报无论成败【永不抛出异常】
* 不会影响接口调用流程),开启上报之后,方便微信监控请求调用的质量,建议至少
* 开启错误上报。
* 上报等级0.关闭上报; 1.仅错误出错上报; 2.全量上报
* @var int
*/
public abstract function GetReportLevenl();
//=======【商户密钥信息-需要业务方继承】===================================
/*
* KEY商户支付密钥参考开户邮件设置必须配置登录商户平台自行设置, 请妥善保管, 避免密钥泄露
* 设置地址https://pay.weixin.qq.com/index.php/account/api_cert
*
* APPSECRET公众帐号secert仅JSAPI支付的时候需要配置 登录公众平台,进入开发者中心可设置), 请妥善保管, 避免密钥泄露
* 获取地址https://mp.weixin.qq.com/advanced/advanced?action=dev&t=advanced/dev&token=2005451881&lang=zh_CN
* @var string
*/
public abstract function GetKey();
public abstract function GetAppSecret();
//=======【证书路径设置-需要业务方继承】=====================================
/**
* TODO设置商户证书路径
* 证书路径,注意应该填写绝对路径(仅退款、撤销订单时需要,可登录商户平台下载,
* API证书下载地址https://pay.weixin.qq.com/index.php/account/api_cert下载之前需要安装商户操作证书
* 注意:
* 1.证书文件不能放在web服务器虚拟目录应放在有访问权限控制的目录中防止被他人下载
* 2.建议将证书文件名改为复杂且不容易猜测的文件名;
* 3.商户服务器要做好病毒和木马防护工作,不被非法侵入者窃取证书文件。
* @var path
*/
public abstract function GetSSLCertPath(&$sslCertPath, &$sslKeyPath);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,150 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/2/24
* Time: 6:09 PM
*/
namespace app\common\wxpay\lib;
class WxPayDataBase
{
protected $values = array();
/**
* 设置签名,详见签名生成算法类型
* @param string $value
**/
public function SetSignType($sign_type)
{
$this->values['sign_type'] = $sign_type;
return $sign_type;
}
/**
* 设置签名,详见签名生成算法
* @param string $value
**/
public function SetSign($config)
{
$sign = $this->MakeSign($config);
$this->values['sign'] = $sign;
return $sign;
}
/**
* 获取签名,详见签名生成算法的值
* @return
**/
public function GetSign()
{
return $this->values['sign'];
}
/**
* 判断签名,详见签名生成算法是否存在
* @return true false
**/
public function IsSignSet()
{
return array_key_exists('sign', $this->values);
}
/**
* 输出xml字符
* @throws WxPayException
**/
public function ToXml()
{
if(!is_array($this->values) || count($this->values) <= 0)
{
throw new WxPayException("数组数据异常!");
}
$xml = "<xml>";
foreach ($this->values as $key=>$val)
{
if (is_numeric($val)){
$xml.="<".$key.">".$val."</".$key.">";
}else{
$xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
}
}
$xml.="</xml>";
return $xml;
}
/**
* 将xml转为array
* @param string $xml
* @throws WxPayException
*/
public function FromXml($xml)
{
if(!$xml){
throw new WxPayException("xml数据异常");
}
//将XML转为array
//禁止引用外部xml实体
libxml_disable_entity_loader(true);
$this->values = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
return $this->values;
}
/**
* 格式化参数格式化成url参数
*/
public function ToUrlParams()
{
$buff = "";
foreach ($this->values as $k => $v)
{
if($k != "sign" && $v != "" && !is_array($v)){
$buff .= $k . "=" . $v . "&";
}
}
$buff = trim($buff, "&");
return $buff;
}
/**
* 生成签名
* @param WxPayConfigInterface $config 配置对象
* @param bool $needSignType 是否需要补signtype
* @return 签名本函数不覆盖sign成员变量如要设置签名需要调用SetSign方法赋值
*/
public function MakeSign($config, $needSignType = true)
{
if($needSignType) {
$this->SetSignType($config->GetSignType());
}
//签名步骤一:按字典序排序参数
ksort($this->values);
$string = $this->ToUrlParams();
//签名步骤二在string后加入KEY
$string = $string . "&key=".$config->GetKey();
//签名步骤三MD5加密或者HMAC-SHA256
if($config->GetSignType() == "MD5"){
$string = md5($string);
} else if($config->GetSignType() == "HMAC-SHA256") {
$string = hash_hmac("sha256",$string ,$config->GetKey());
} else {
throw new WxPayException("签名类型不支持!");
}
//签名步骤四:所有字符转为大写
$result = strtoupper($string);
return $result;
}
/**
* 获取设置的值
*/
public function GetValues()
{
return $this->values;
}
}

View File

@ -0,0 +1,36 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/2/24
* Time: 7:16 PM
*/
namespace app\common\wxpay\lib;
class WxPayDataBaseSignMd5 extends WxPayDataBase
{
/**
* 生成签名 - 重写该方法
* @param WxPayConfigInterface $config 配置对象
* @param bool $needSignType 是否需要补signtype
* @return 签名本函数不覆盖sign成员变量如要设置签名需要调用SetSign方法赋值
*/
public function MakeSign($config, $needSignType = false)
{
if($needSignType) {
$this->SetSignType($config->GetSignType());
}
//签名步骤一:按字典序排序参数
ksort($this->values);
$string = $this->ToUrlParams();
//签名步骤二在string后加入KEY
$string = $string . "&key=".$config->GetKey();
//签名步骤三MD5加密
$string = md5($string);
//签名步骤四:所有字符转为大写
$result = strtoupper($string);
return $result;
}
}

View File

@ -0,0 +1,167 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/2/24
* Time: 7:07 PM
*/
namespace app\common\wxpay\lib;
class WxPayDownloadBill extends WxPayDataBase
{
/**
* 设置微信分配的公众账号ID
* @param string $value
**/
public function SetAppid($value)
{
$this->values['appid'] = $value;
}
/**
* 获取微信分配的公众账号ID的值
* @return
**/
public function GetAppid()
{
return $this->values['appid'];
}
/**
* 判断微信分配的公众账号ID是否存在
* @return true false
**/
public function IsAppidSet()
{
return array_key_exists('appid', $this->values);
}
/**
* 设置微信支付分配的商户号
* @param string $value
**/
public function SetMch_id($value)
{
$this->values['mch_id'] = $value;
}
/**
* 获取微信支付分配的商户号的值
* @return
**/
public function GetMch_id()
{
return $this->values['mch_id'];
}
/**
* 判断微信支付分配的商户号是否存在
* @return true false
**/
public function IsMch_idSet()
{
return array_key_exists('mch_id', $this->values);
}
/**
* 设置微信支付分配的终端设备号,填写此字段,只下载该设备号的对账单
* @param string $value
**/
public function SetDevice_info($value)
{
$this->values['device_info'] = $value;
}
/**
* 获取微信支付分配的终端设备号,填写此字段,只下载该设备号的对账单的值
* @return
**/
public function GetDevice_info()
{
return $this->values['device_info'];
}
/**
* 判断微信支付分配的终端设备号,填写此字段,只下载该设备号的对账单是否存在
* @return true false
**/
public function IsDevice_infoSet()
{
return array_key_exists('device_info', $this->values);
}
/**
* 设置随机字符串不长于32位。推荐随机数生成算法
* @param string $value
**/
public function SetNonce_str($value)
{
$this->values['nonce_str'] = $value;
}
/**
* 获取随机字符串不长于32位。推荐随机数生成算法的值
* @return
**/
public function GetNonce_str()
{
return $this->values['nonce_str'];
}
/**
* 判断随机字符串不长于32位。推荐随机数生成算法是否存在
* @return true false
**/
public function IsNonce_strSet()
{
return array_key_exists('nonce_str', $this->values);
}
/**
* 设置下载对账单的日期格式20140603
* @param string $value
**/
public function SetBill_date($value)
{
$this->values['bill_date'] = $value;
}
/**
* 获取下载对账单的日期格式20140603的值
* @return
**/
public function GetBill_date()
{
return $this->values['bill_date'];
}
/**
* 判断下载对账单的日期格式20140603是否存在
* @return true false
**/
public function IsBill_dateSet()
{
return array_key_exists('bill_date', $this->values);
}
/**
* 设置ALL返回当日所有订单信息默认值SUCCESS返回当日成功支付的订单REFUND返回当日退款订单REVOKED已撤销的订单
* @param string $value
**/
public function SetBill_type($value)
{
$this->values['bill_type'] = $value;
}
/**
* 获取ALL返回当日所有订单信息默认值SUCCESS返回当日成功支付的订单REFUND返回当日退款订单REVOKED已撤销的订单的值
* @return
**/
public function GetBill_type()
{
return $this->values['bill_type'];
}
/**
* 判断ALL返回当日所有订单信息默认值SUCCESS返回当日成功支付的订单REFUND返回当日退款订单REVOKED已撤销的订单是否存在
* @return true false
**/
public function IsBill_typeSet()
{
return array_key_exists('bill_type', $this->values);
}
}

View File

@ -0,0 +1,20 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/2/23
* Time: 6:35 PM
*/
namespace app\common\wxpay\lib;
use think\Exception;
class WxPayException extends Exception
{
public function errorMessage()
{
return $this->getMessage();
}
}

View File

@ -0,0 +1,165 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/2/24
* Time: 7:04 PM
*/
namespace app\common\wxpay\lib;
class WxPayJsApiPay extends WxPayDataBase
{
/**
* 设置微信分配的公众账号ID
* @param string $value
**/
public function SetAppid($value)
{
$this->values['appId'] = $value;
}
/**
* 获取微信分配的公众账号ID的值
* @return
**/
public function GetAppid()
{
return $this->values['appId'];
}
/**
* 判断微信分配的公众账号ID是否存在
* @return true false
**/
public function IsAppidSet()
{
return array_key_exists('appId', $this->values);
}
/**
* 设置支付时间戳
* @param string $value
**/
public function SetTimeStamp($value)
{
$this->values['timeStamp'] = $value;
}
/**
* 获取支付时间戳的值
* @return
**/
public function GetTimeStamp()
{
return $this->values['timeStamp'];
}
/**
* 判断支付时间戳是否存在
* @return true false
**/
public function IsTimeStampSet()
{
return array_key_exists('timeStamp', $this->values);
}
/**
* 随机字符串
* @param string $value
**/
public function SetNonceStr($value)
{
$this->values['nonceStr'] = $value;
}
/**
* 获取notify随机字符串值
* @return
**/
public function GetReturn_code()
{
return $this->values['nonceStr'];
}
/**
* 判断随机字符串是否存在
* @return true false
**/
public function IsReturn_codeSet()
{
return array_key_exists('nonceStr', $this->values);
}
/**
* 设置订单详情扩展字符串
* @param string $value
**/
public function SetPackage($value)
{
$this->values['package'] = $value;
}
/**
* 获取订单详情扩展字符串的值
* @return
**/
public function GetPackage()
{
return $this->values['package'];
}
/**
* 判断订单详情扩展字符串是否存在
* @return true false
**/
public function IsPackageSet()
{
return array_key_exists('package', $this->values);
}
/**
* 设置签名方式
* @param string $value
**/
public function SetSignType($value)
{
$this->values['signType'] = $value;
}
/**
* 获取签名方式
* @return
**/
public function GetSignType()
{
return $this->values['signType'];
}
/**
* 判断签名方式是否存在
* @return true false
**/
public function IsSignTypeSet()
{
return array_key_exists('signType', $this->values);
}
/**
* 设置签名方式
* @param string $value
**/
public function SetPaySign($value)
{
$this->values['paySign'] = $value;
}
/**
* 获取签名方式
* @return
**/
public function GetPaySign()
{
return $this->values['paySign'];
}
/**
* 判断签名方式是否存在
* @return true false
**/
public function IsPaySignSet()
{
return array_key_exists('paySign', $this->values);
}
}

View File

@ -0,0 +1,425 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/2/24
* Time: 6:11 PM
*/
namespace app\common\wxpay\lib;
class WxPayMicroPay extends WxPayDataBase
{
/**
* 设置微信分配的公众账号ID
* @param string $value
**/
public function SetAppid($value)
{
$this->values['appid'] = $value;
}
/**
* 获取微信分配的公众账号ID的值
* @return
**/
public function GetAppid()
{
return $this->values['appid'];
}
/**
* 判断微信分配的公众账号ID是否存在
* @return true false
**/
public function IsAppidSet()
{
return array_key_exists('appid', $this->values);
}
/**
* 设置微信支付分配的商户号
* @param string $value
**/
public function SetMch_id($value)
{
$this->values['mch_id'] = $value;
}
/**
* 获取微信支付分配的商户号的值
* @return
**/
public function GetMch_id()
{
return $this->values['mch_id'];
}
/**
* 判断微信支付分配的商户号是否存在
* @return true false
**/
public function IsMch_idSet()
{
return array_key_exists('mch_id', $this->values);
}
/**
* 设置微信支付分配的子商户号
* @param string $value
**/
public function SetSubMch_id($value)
{
$this->values['sub_mch_id'] = $value;
}
/**
* 获取微信支付分配的子商户号的值
* @return
**/
public function GetSubMch_id()
{
return $this->values['sub_mch_id'];
}
/**
* 判断微信支付分配的子商户号是否存在
* @return true false
**/
public function IsSubMch_idSet()
{
return array_key_exists('sub_mch_id', $this->values);
}
/**
* 设置终端设备号(商户自定义,如门店编号)
* @param string $value
**/
public function SetDevice_info($value)
{
$this->values['device_info'] = $value;
}
/**
* 获取终端设备号(商户自定义,如门店编号)的值
* @return
**/
public function GetDevice_info()
{
return $this->values['device_info'];
}
/**
* 判断终端设备号(商户自定义,如门店编号)是否存在
* @return true false
**/
public function IsDevice_infoSet()
{
return array_key_exists('device_info', $this->values);
}
/**
* 设置随机字符串不长于32位。推荐随机数生成算法
* @param string $value
**/
public function SetNonce_str($value)
{
$this->values['nonce_str'] = $value;
}
/**
* 获取随机字符串不长于32位。推荐随机数生成算法的值
* @return
**/
public function GetNonce_str()
{
return $this->values['nonce_str'];
}
/**
* 判断随机字符串不长于32位。推荐随机数生成算法是否存在
* @return true false
**/
public function IsNonce_strSet()
{
return array_key_exists('nonce_str', $this->values);
}
/**
* 设置商品或支付单简要描述
* @param string $value
**/
public function SetBody($value)
{
$this->values['body'] = $value;
}
/**
* 获取商品或支付单简要描述的值
* @return
**/
public function GetBody()
{
return $this->values['body'];
}
/**
* 判断商品或支付单简要描述是否存在
* @return true false
**/
public function IsBodySet()
{
return array_key_exists('body', $this->values);
}
/**
* 设置商品名称明细列表
* @param string $value
**/
public function SetDetail($value)
{
$this->values['detail'] = $value;
}
/**
* 获取商品名称明细列表的值
* @return
**/
public function GetDetail()
{
return $this->values['detail'];
}
/**
* 判断商品名称明细列表是否存在
* @return true false
**/
public function IsDetailSet()
{
return array_key_exists('detail', $this->values);
}
/**
* 设置附加数据在查询API和支付通知中原样返回该字段主要用于商户携带订单的自定义数据
* @param string $value
**/
public function SetAttach($value)
{
$this->values['attach'] = $value;
}
/**
* 获取附加数据在查询API和支付通知中原样返回该字段主要用于商户携带订单的自定义数据的值
* @return
**/
public function GetAttach()
{
return $this->values['attach'];
}
/**
* 判断附加数据在查询API和支付通知中原样返回该字段主要用于商户携带订单的自定义数据是否存在
* @return true false
**/
public function IsAttachSet()
{
return array_key_exists('attach', $this->values);
}
/**
* 设置商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号
* @param string $value
**/
public function SetOut_trade_no($value)
{
$this->values['out_trade_no'] = $value;
}
/**
* 获取商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号的值
* @return
**/
public function GetOut_trade_no()
{
return $this->values['out_trade_no'];
}
/**
* 判断商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号是否存在
* @return true false
**/
public function IsOut_trade_noSet()
{
return array_key_exists('out_trade_no', $this->values);
}
/**
* 设置订单总金额,单位为分,只能为整数,详见支付金额
* @param string $value
**/
public function SetTotal_fee($value)
{
$this->values['total_fee'] = $value;
}
/**
* 获取订单总金额,单位为分,只能为整数,详见支付金额的值
* @return
**/
public function GetTotal_fee()
{
return $this->values['total_fee'];
}
/**
* 判断订单总金额,单位为分,只能为整数,详见支付金额是否存在
* @return true false
**/
public function IsTotal_feeSet()
{
return array_key_exists('total_fee', $this->values);
}
/**
* 设置符合ISO 4217标准的三位字母代码默认人民币CNY其他值列表详见货币类型
* @param string $value
**/
public function SetFee_type($value)
{
$this->values['fee_type'] = $value;
}
/**
* 获取符合ISO 4217标准的三位字母代码默认人民币CNY其他值列表详见货币类型的值
* @return
**/
public function GetFee_type()
{
return $this->values['fee_type'];
}
/**
* 判断符合ISO 4217标准的三位字母代码默认人民币CNY其他值列表详见货币类型是否存在
* @return true false
**/
public function IsFee_typeSet()
{
return array_key_exists('fee_type', $this->values);
}
/**
* 设置调用微信支付API的机器IP
* @param string $value
**/
public function SetSpbill_create_ip($value)
{
$this->values['spbill_create_ip'] = $value;
}
/**
* 获取调用微信支付API的机器IP 的值
* @return
**/
public function GetSpbill_create_ip()
{
return $this->values['spbill_create_ip'];
}
/**
* 判断调用微信支付API的机器IP 是否存在
* @return true false
**/
public function IsSpbill_create_ipSet()
{
return array_key_exists('spbill_create_ip', $this->values);
}
/**
* 设置订单生成时间格式为yyyyMMddHHmmss如2009年12月25日9点10分10秒表示为20091225091010。详见时间规则
* @param string $value
**/
public function SetTime_start($value)
{
$this->values['time_start'] = $value;
}
/**
* 获取订单生成时间格式为yyyyMMddHHmmss如2009年12月25日9点10分10秒表示为20091225091010。详见时间规则的值
* @return
**/
public function GetTime_start()
{
return $this->values['time_start'];
}
/**
* 判断订单生成时间格式为yyyyMMddHHmmss如2009年12月25日9点10分10秒表示为20091225091010。详见时间规则是否存在
* @return true false
**/
public function IsTime_startSet()
{
return array_key_exists('time_start', $this->values);
}
/**
* 设置订单失效时间格式为yyyyMMddHHmmss如2009年12月27日9点10分10秒表示为20091227091010。详见时间规则
* @param string $value
**/
public function SetTime_expire($value)
{
$this->values['time_expire'] = $value;
}
/**
* 获取订单失效时间格式为yyyyMMddHHmmss如2009年12月27日9点10分10秒表示为20091227091010。详见时间规则的值
* @return
**/
public function GetTime_expire()
{
return $this->values['time_expire'];
}
/**
* 判断订单失效时间格式为yyyyMMddHHmmss如2009年12月27日9点10分10秒表示为20091227091010。详见时间规则是否存在
* @return true false
**/
public function IsTime_expireSet()
{
return array_key_exists('time_expire', $this->values);
}
/**
* 设置商品标记,代金券或立减优惠功能的参数,说明详见代金券或立减优惠
* @param string $value
**/
public function SetGoods_tag($value)
{
$this->values['goods_tag'] = $value;
}
/**
* 获取商品标记,代金券或立减优惠功能的参数,说明详见代金券或立减优惠的值
* @return
**/
public function GetGoods_tag()
{
return $this->values['goods_tag'];
}
/**
* 判断商品标记,代金券或立减优惠功能的参数,说明详见代金券或立减优惠是否存在
* @return true false
**/
public function IsGoods_tagSet()
{
return array_key_exists('goods_tag', $this->values);
}
/**
* 设置扫码支付授权码,设备读取用户微信中的条码或者二维码信息
* @param string $value
**/
public function SetAuth_code($value)
{
$this->values['auth_code'] = $value;
}
/**
* 获取扫码支付授权码,设备读取用户微信中的条码或者二维码信息的值
* @return
**/
public function GetAuth_code()
{
return $this->values['auth_code'];
}
/**
* 判断扫码支付授权码,设备读取用户微信中的条码或者二维码信息是否存在
* @return true false
**/
public function IsAuth_codeSet()
{
return array_key_exists('auth_code', $this->values);
}
}

View File

@ -0,0 +1,109 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/2/23
* Time: 6:37 PM
*/
namespace app\common\wxpay\lib;
class WxPayNotify extends WxPayNotifyReply
{
private $config = null;
/**
*
* 回调入口
* @param bool $needSign 是否需要签名返回
*/
final public function Handle($config, $needSign = true)
{
$this->config = $config;
$msg = "OK";
//当返回false的时候表示notify中调用NotifyCallBack回调失败获取签名校验失败此时直接回复失败
$result = WxpayApi::notify($config, array($this, 'NotifyCallBack'), $msg);
if($result == false){
$this->SetReturn_code("FAIL");
$this->SetReturn_msg($msg);
$this->ReplyNotify(false);
return;
} else {
//该分支在成功回调到NotifyCallBack方法处理完成之后流程
$this->SetReturn_code("SUCCESS");
$this->SetReturn_msg("OK");
}
$this->ReplyNotify($needSign);
}
/**
*
* 回调方法入口,子类可重写该方法
//TODO 1、进行参数校验
//TODO 2、进行签名验证
//TODO 3、处理业务逻辑
* 注意:
* 1、微信回调超时时间为2s建议用户使用异步处理流程确认成功之后立刻回复微信服务器
* 2、微信服务器在调用失败或者接到回包为非确认包的时候,会发起重试,需确保你的回调是可以重入
* @param WxPayNotifyResults $objData 回调解释出的参数
* @param WxPayConfigInterface $config
* @param string $msg 如果回调处理失败,可以将错误信息输出到该方法
* @return true回调出来完成不需要继续回调false回调处理未完成需要继续回调
*/
public function NotifyProcess($objData, $config, &$msg)
{
//TODO 用户基础该类之后需要重写该方法成功的时候返回true失败返回false
return false;
}
/**
*
* 业务可以继承该方法打印XML方便定位.
* @param string $xmlData 返回的xml参数
*
**/
public function LogAfterProcess($xmlData)
{
return;
}
/**
*
* notify回调方法该方法中需要赋值需要输出的参数,不可重写
* @param array $data
* @return true回调出来完成不需要继续回调false回调处理未完成需要继续回调
*/
final public function NotifyCallBack($data)
{
$msg = "OK";
$result = $this->NotifyProcess($data, $this->config, $msg);
if($result == true){
$this->SetReturn_code("SUCCESS");
$this->SetReturn_msg("OK");
} else {
$this->SetReturn_code("FAIL");
$this->SetReturn_msg($msg);
}
return $result;
}
/**
*
* 回复通知
* @param bool $needSign 是否需要签名输出
*/
final private function ReplyNotify($needSign = true)
{
//如果需要签名
if($needSign == true &&
$this->GetReturn_code() == "SUCCESS")
{
$this->SetSign($this->config);
}
$xml = $this->ToXml();
$this->LogAfterProcess($xml);
WxpayApi::replyNotify($xml);
}
}

View File

@ -0,0 +1,64 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/2/24
* Time: 7:13 PM
*/
namespace app\common\wxpay\lib;
class WxPayNotifyReply extends WxPayDataBaseSignMd5
{
/**
*
* 设置错误码 FAIL 或者 SUCCESS
* @param string
*/
public function SetReturn_code($return_code)
{
$this->values['return_code'] = $return_code;
}
/**
*
* 获取错误码 FAIL 或者 SUCCESS
* @return string $return_code
*/
public function GetReturn_code()
{
return $this->values['return_code'];
}
/**
*
* 设置错误信息
* @param string $return_code
*/
public function SetReturn_msg($return_msg)
{
$this->values['return_msg'] = $return_msg;
}
/**
*
* 获取错误信息
* @return string
*/
public function GetReturn_msg()
{
return $this->values['return_msg'];
}
/**
*
* 设置返回参数
* @param string $key
* @param string $value
*/
public function SetData($key, $value)
{
$this->values[$key] = $value;
}
}

View File

@ -0,0 +1,29 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/2/24
* Time: 7:19 PM
*/
namespace app\common\wxpay\lib;
class WxPayNotifyResults extends WxPayResults
{
/**
* 将xml转为array
* @param WxPayConfigInterface $config
* @param string $xml
* @return WxPayNotifyResults
* @throws WxPayException
*/
public static function Init($config, $xml)
{
$obj = new self();
$obj->FromXml($xml);
//失败则直接返回失败
$obj->CheckSign($config);
return $obj;
}
}

View File

@ -0,0 +1,167 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/2/24
* Time: 7:10 PM
*/
namespace app\common\wxpay\lib;
class WxPayOrderQuery extends WxPayDataBase
{
/**
* 设置微信分配的公众账号ID
* @param string $value
**/
public function SetAppid($value)
{
$this->values['appid'] = $value;
}
/**
* 获取微信分配的公众账号ID的值
* @return
**/
public function GetAppid()
{
return $this->values['appid'];
}
/**
* 判断微信分配的公众账号ID是否存在
* @return true false
**/
public function IsAppidSet()
{
return array_key_exists('appid', $this->values);
}
/**
* 设置微信支付分配的商户号
* @param string $value
**/
public function SetMch_id($value)
{
$this->values['mch_id'] = $value;
}
/**
* 获取微信支付分配的商户号的值
* @return
**/
public function GetMch_id()
{
return $this->values['mch_id'];
}
/**
* 判断微信支付分配的商户号是否存在
* @return true false
**/
public function IsMch_idSet()
{
return array_key_exists('mch_id', $this->values);
}
/**
* 设置微信支付分配的子商户号
* @param string $value
**/
public function SetSubMch_id($value)
{
$this->values['sub_mch_id'] = $value;
}
/**
* 获取微信支付分配的子商户号的值
* @return
**/
public function GetSubMch_id()
{
return $this->values['sub_mch_id'];
}
/**
* 判断微信支付分配的子商户号是否存在
* @return true false
**/
public function IsSubMch_idSet()
{
return array_key_exists('sub_mch_id', $this->values);
}
/**
* 设置微信的订单号,优先使用
* @param string $value
**/
public function SetTransaction_id($value)
{
$this->values['transaction_id'] = $value;
}
/**
* 获取微信的订单号,优先使用的值
* @return
**/
public function GetTransaction_id()
{
return $this->values['transaction_id'];
}
/**
* 判断微信的订单号,优先使用是否存在
* @return true false
**/
public function IsTransaction_idSet()
{
return array_key_exists('transaction_id', $this->values);
}
/**
* 设置商户系统内部的订单号当没提供transaction_id时需要传这个。
* @param string $value
**/
public function SetOut_trade_no($value)
{
$this->values['out_trade_no'] = $value;
}
/**
* 获取商户系统内部的订单号当没提供transaction_id时需要传这个。的值
* @return
**/
public function GetOut_trade_no()
{
return $this->values['out_trade_no'];
}
/**
* 判断商户系统内部的订单号当没提供transaction_id时需要传这个。是否存在
* @return true false
**/
public function IsOut_trade_noSet()
{
return array_key_exists('out_trade_no', $this->values);
}
/**
* 设置随机字符串不长于32位。推荐随机数生成算法
* @param string $value
**/
public function SetNonce_str($value)
{
$this->values['nonce_str'] = $value;
}
/**
* 获取随机字符串不长于32位。推荐随机数生成算法的值
* @return
**/
public function GetNonce_str()
{
return $this->values['nonce_str'];
}
/**
* 判断随机字符串不长于32位。推荐随机数生成算法是否存在
* @return true false
**/
public function IsNonce_strSet()
{
return array_key_exists('nonce_str', $this->values);
}
}

View File

@ -0,0 +1,322 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/2/24
* Time: 7:08 PM
*/
namespace app\common\wxpay\lib;
class WxPayRefund extends WxPayDataBase
{
/**
* 设置微信分配的公众账号ID
* @param string $value
**/
public function SetAppid($value)
{
$this->values['appid'] = $value;
}
/**
* 获取微信分配的公众账号ID的值
* @return
**/
public function GetAppid()
{
return $this->values['appid'];
}
/**
* 判断微信分配的公众账号ID是否存在
* @return true false
**/
public function IsAppidSet()
{
return array_key_exists('appid', $this->values);
}
/**
* 设置微信支付分配的商户号
* @param string $value
**/
public function SetMch_id($value)
{
$this->values['mch_id'] = $value;
}
/**
* 获取微信支付分配的商户号的值
* @return
**/
public function GetMch_id()
{
return $this->values['mch_id'];
}
/**
* 判断微信支付分配的商户号是否存在
* @return true false
**/
public function IsMch_idSet()
{
return array_key_exists('mch_id', $this->values);
}
/**
* 设置微信支付分配的子商户号
* @param string $value
**/
public function SetSubMch_id($value)
{
$this->values['sub_mch_id'] = $value;
}
/**
* 获取微信支付分配的子商户号的值
* @return
**/
public function GetSubMch_id()
{
return $this->values['sub_mch_id'];
}
/**
* 判断微信支付分配的子商户号是否存在
* @return true false
**/
public function IsSubMch_idSet()
{
return array_key_exists('sub_mch_id', $this->values);
}
/**
* 设置微信支付分配的终端设备号,与下单一致
* @param string $value
**/
public function SetDevice_info($value)
{
$this->values['device_info'] = $value;
}
/**
* 获取微信支付分配的终端设备号,与下单一致的值
* @return
**/
public function GetDevice_info()
{
return $this->values['device_info'];
}
/**
* 判断微信支付分配的终端设备号,与下单一致是否存在
* @return true false
**/
public function IsDevice_infoSet()
{
return array_key_exists('device_info', $this->values);
}
/**
* 设置随机字符串不长于32位。推荐随机数生成算法
* @param string $value
**/
public function SetNonce_str($value)
{
$this->values['nonce_str'] = $value;
}
/**
* 获取随机字符串不长于32位。推荐随机数生成算法的值
* @return
**/
public function GetNonce_str()
{
return $this->values['nonce_str'];
}
/**
* 判断随机字符串不长于32位。推荐随机数生成算法是否存在
* @return true false
**/
public function IsNonce_strSet()
{
return array_key_exists('nonce_str', $this->values);
}
/**
* 设置微信订单号
* @param string $value
**/
public function SetTransaction_id($value)
{
$this->values['transaction_id'] = $value;
}
/**
* 获取微信订单号的值
* @return
**/
public function GetTransaction_id()
{
return $this->values['transaction_id'];
}
/**
* 判断微信订单号是否存在
* @return true false
**/
public function IsTransaction_idSet()
{
return array_key_exists('transaction_id', $this->values);
}
/**
* 设置商户系统内部的订单号,transaction_id、out_trade_no二选一如果同时存在优先级transaction_id> out_trade_no
* @param string $value
**/
public function SetOut_trade_no($value)
{
$this->values['out_trade_no'] = $value;
}
/**
* 获取商户系统内部的订单号,transaction_id、out_trade_no二选一如果同时存在优先级transaction_id> out_trade_no的值
* @return
**/
public function GetOut_trade_no()
{
return $this->values['out_trade_no'];
}
/**
* 判断商户系统内部的订单号,transaction_id、out_trade_no二选一如果同时存在优先级transaction_id> out_trade_no是否存在
* @return true false
**/
public function IsOut_trade_noSet()
{
return array_key_exists('out_trade_no', $this->values);
}
/**
* 设置商户系统内部的退款单号,商户系统内部唯一,同一退款单号多次请求只退一笔
* @param string $value
**/
public function SetOut_refund_no($value)
{
$this->values['out_refund_no'] = $value;
}
/**
* 获取商户系统内部的退款单号,商户系统内部唯一,同一退款单号多次请求只退一笔的值
* @return
**/
public function GetOut_refund_no()
{
return $this->values['out_refund_no'];
}
/**
* 判断商户系统内部的退款单号,商户系统内部唯一,同一退款单号多次请求只退一笔是否存在
* @return true false
**/
public function IsOut_refund_noSet()
{
return array_key_exists('out_refund_no', $this->values);
}
/**
* 设置订单总金额,单位为分,只能为整数,详见支付金额
* @param string $value
**/
public function SetTotal_fee($value)
{
$this->values['total_fee'] = $value;
}
/**
* 获取订单总金额,单位为分,只能为整数,详见支付金额的值
* @return
**/
public function GetTotal_fee()
{
return $this->values['total_fee'];
}
/**
* 判断订单总金额,单位为分,只能为整数,详见支付金额是否存在
* @return true false
**/
public function IsTotal_feeSet()
{
return array_key_exists('total_fee', $this->values);
}
/**
* 设置退款总金额,订单总金额,单位为分,只能为整数,详见支付金额
* @param string $value
**/
public function SetRefund_fee($value)
{
$this->values['refund_fee'] = $value;
}
/**
* 获取退款总金额,订单总金额,单位为分,只能为整数,详见支付金额的值
* @return
**/
public function GetRefund_fee()
{
return $this->values['refund_fee'];
}
/**
* 判断退款总金额,订单总金额,单位为分,只能为整数,详见支付金额是否存在
* @return true false
**/
public function IsRefund_feeSet()
{
return array_key_exists('refund_fee', $this->values);
}
/**
* 设置货币类型符合ISO 4217标准的三位字母代码默认人民币CNY其他值列表详见货币类型
* @param string $value
**/
public function SetRefund_fee_type($value)
{
$this->values['refund_fee_type'] = $value;
}
/**
* 获取货币类型符合ISO 4217标准的三位字母代码默认人民币CNY其他值列表详见货币类型的值
* @return
**/
public function GetRefund_fee_type()
{
return $this->values['refund_fee_type'];
}
/**
* 判断货币类型符合ISO 4217标准的三位字母代码默认人民币CNY其他值列表详见货币类型是否存在
* @return true false
**/
public function IsRefund_fee_typeSet()
{
return array_key_exists('refund_fee_type', $this->values);
}
/**
* 设置操作员帐号, 默认为商户号
* @param string $value
**/
public function SetOp_user_id($value)
{
$this->values['op_user_id'] = $value;
}
/**
* 获取操作员帐号, 默认为商户号的值
* @return
**/
public function GetOp_user_id()
{
return $this->values['op_user_id'];
}
/**
* 判断操作员帐号, 默认为商户号是否存在
* @return true false
**/
public function IsOp_user_idSet()
{
return array_key_exists('op_user_id', $this->values);
}
}

View File

@ -0,0 +1,219 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/2/24
* Time: 7:08 PM
*/
namespace app\common\wxpay\lib;
class WxPayRefundQuery extends WxPayDataBase
{
/**
* 设置微信分配的公众账号ID
* @param string $value
**/
public function SetAppid($value)
{
$this->values['appid'] = $value;
}
/**
* 获取微信分配的公众账号ID的值
* @return
**/
public function GetAppid()
{
return $this->values['appid'];
}
/**
* 判断微信分配的公众账号ID是否存在
* @return true false
**/
public function IsAppidSet()
{
return array_key_exists('appid', $this->values);
}
/**
* 设置微信支付分配的商户号
* @param string $value
**/
public function SetMch_id($value)
{
$this->values['mch_id'] = $value;
}
/**
* 获取微信支付分配的商户号的值
* @return
**/
public function GetMch_id()
{
return $this->values['mch_id'];
}
/**
* 判断微信支付分配的商户号是否存在
* @return true false
**/
public function IsMch_idSet()
{
return array_key_exists('mch_id', $this->values);
}
/**
* 设置微信支付分配的终端设备号
* @param string $value
**/
public function SetDevice_info($value)
{
$this->values['device_info'] = $value;
}
/**
* 获取微信支付分配的终端设备号的值
* @return
**/
public function GetDevice_info()
{
return $this->values['device_info'];
}
/**
* 判断微信支付分配的终端设备号是否存在
* @return true false
**/
public function IsDevice_infoSet()
{
return array_key_exists('device_info', $this->values);
}
/**
* 设置随机字符串不长于32位。推荐随机数生成算法
* @param string $value
**/
public function SetNonce_str($value)
{
$this->values['nonce_str'] = $value;
}
/**
* 获取随机字符串不长于32位。推荐随机数生成算法的值
* @return
**/
public function GetNonce_str()
{
return $this->values['nonce_str'];
}
/**
* 判断随机字符串不长于32位。推荐随机数生成算法是否存在
* @return true false
**/
public function IsNonce_strSet()
{
return array_key_exists('nonce_str', $this->values);
}
/**
* 设置微信订单号
* @param string $value
**/
public function SetTransaction_id($value)
{
$this->values['transaction_id'] = $value;
}
/**
* 获取微信订单号的值
* @return
**/
public function GetTransaction_id()
{
return $this->values['transaction_id'];
}
/**
* 判断微信订单号是否存在
* @return true false
**/
public function IsTransaction_idSet()
{
return array_key_exists('transaction_id', $this->values);
}
/**
* 设置商户系统内部的订单号
* @param string $value
**/
public function SetOut_trade_no($value)
{
$this->values['out_trade_no'] = $value;
}
/**
* 获取商户系统内部的订单号的值
* @return
**/
public function GetOut_trade_no()
{
return $this->values['out_trade_no'];
}
/**
* 判断商户系统内部的订单号是否存在
* @return true false
**/
public function IsOut_trade_noSet()
{
return array_key_exists('out_trade_no', $this->values);
}
/**
* 设置商户退款单号
* @param string $value
**/
public function SetOut_refund_no($value)
{
$this->values['out_refund_no'] = $value;
}
/**
* 获取商户退款单号的值
* @return
**/
public function GetOut_refund_no()
{
return $this->values['out_refund_no'];
}
/**
* 判断商户退款单号是否存在
* @return true false
**/
public function IsOut_refund_noSet()
{
return array_key_exists('out_refund_no', $this->values);
}
/**
* 设置微信退款单号refund_id、out_refund_no、out_trade_no、transaction_id四个参数必填一个如果同时存在优先级为refund_id>out_refund_no>transaction_id>out_trade_no
* @param string $value
**/
public function SetRefund_id($value)
{
$this->values['refund_id'] = $value;
}
/**
* 获取微信退款单号refund_id、out_refund_no、out_trade_no、transaction_id四个参数必填一个如果同时存在优先级为refund_id>out_refund_no>transaction_id>out_trade_no的值
* @return
**/
public function GetRefund_id()
{
return $this->values['refund_id'];
}
/**
* 判断微信退款单号refund_id、out_refund_no、out_trade_no、transaction_id四个参数必填一个如果同时存在优先级为refund_id>out_refund_no>transaction_id>out_trade_no是否存在
* @return true false
**/
public function IsRefund_idSet()
{
return array_key_exists('refund_id', $this->values);
}
}

View File

@ -0,0 +1,376 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/2/24
* Time: 7:06 PM
*/
namespace app\common\wxpay\lib;
class WxPayReport extends WxPayDataBase
{
/**
* 设置微信分配的公众账号ID
* @param string $value
**/
public function SetAppid($value)
{
$this->values['appid'] = $value;
}
/**
* 获取微信分配的公众账号ID的值
* @return
**/
public function GetAppid()
{
return $this->values['appid'];
}
/**
* 判断微信分配的公众账号ID是否存在
* @return true false
**/
public function IsAppidSet()
{
return array_key_exists('appid', $this->values);
}
/**
* 设置微信支付分配的商户号
* @param string $value
**/
public function SetMch_id($value)
{
$this->values['mch_id'] = $value;
}
/**
* 获取微信支付分配的商户号的值
* @return
**/
public function GetMch_id()
{
return $this->values['mch_id'];
}
/**
* 判断微信支付分配的商户号是否存在
* @return true false
**/
public function IsMch_idSet()
{
return array_key_exists('mch_id', $this->values);
}
/**
* 设置微信支付分配的终端设备号,商户自定义
* @param string $value
**/
public function SetDevice_info($value)
{
$this->values['device_info'] = $value;
}
/**
* 获取微信支付分配的终端设备号,商户自定义的值
* @return
**/
public function GetDevice_info()
{
return $this->values['device_info'];
}
/**
* 判断微信支付分配的终端设备号,商户自定义是否存在
* @return true false
**/
public function IsDevice_infoSet()
{
return array_key_exists('device_info', $this->values);
}
/**
* 设置随机字符串不长于32位。推荐随机数生成算法
* @param string $value
**/
public function SetNonce_str($value)
{
$this->values['nonce_str'] = $value;
}
/**
* 获取随机字符串不长于32位。推荐随机数生成算法的值
* @return
**/
public function GetNonce_str()
{
return $this->values['nonce_str'];
}
/**
* 判断随机字符串不长于32位。推荐随机数生成算法是否存在
* @return true false
**/
public function IsNonce_strSet()
{
return array_key_exists('nonce_str', $this->values);
}
/**
* 设置上报对应的接口的完整URL类似https://api.mch.weixin.qq.com/pay/unifiedorder对于被扫支付为更好的和商户共同分析一次业务行为的整体耗时情况对于两种接入模式请都在门店侧对一次被扫行为进行一次单独的整体上报上报URL指定为https://api.mch.weixin.qq.com/pay/micropay/total关于两种接入模式具体可参考本文档章节被扫支付商户接入模式其它接口调用仍然按照调用一次上报一次来进行。
* @param string $value
**/
public function SetInterface_url($value)
{
$this->values['interface_url'] = $value;
}
/**
* 获取上报对应的接口的完整URL类似https://api.mch.weixin.qq.com/pay/unifiedorder对于被扫支付为更好的和商户共同分析一次业务行为的整体耗时情况对于两种接入模式请都在门店侧对一次被扫行为进行一次单独的整体上报上报URL指定为https://api.mch.weixin.qq.com/pay/micropay/total关于两种接入模式具体可参考本文档章节被扫支付商户接入模式其它接口调用仍然按照调用一次上报一次来进行。的值
* @return
**/
public function GetInterface_url()
{
return $this->values['interface_url'];
}
/**
* 判断上报对应的接口的完整URL类似https://api.mch.weixin.qq.com/pay/unifiedorder对于被扫支付为更好的和商户共同分析一次业务行为的整体耗时情况对于两种接入模式请都在门店侧对一次被扫行为进行一次单独的整体上报上报URL指定为https://api.mch.weixin.qq.com/pay/micropay/total关于两种接入模式具体可参考本文档章节被扫支付商户接入模式其它接口调用仍然按照调用一次上报一次来进行。是否存在
* @return true false
**/
public function IsInterface_urlSet()
{
return array_key_exists('interface_url', $this->values);
}
/**
* 设置接口耗时情况,单位为毫秒
* @param string $value
**/
public function SetExecute_time_($value)
{
$this->values['execute_time_'] = $value;
}
/**
* 获取接口耗时情况,单位为毫秒的值
* @return
**/
public function GetExecute_time_()
{
return $this->values['execute_time_'];
}
/**
* 判断接口耗时情况,单位为毫秒是否存在
* @return true false
**/
public function IsExecute_time_Set()
{
return array_key_exists('execute_time_', $this->values);
}
/**
* 设置SUCCESS/FAIL此字段是通信标识非交易标识交易是否成功需要查看trade_state来判断
* @param string $value
**/
public function SetReturn_code($value)
{
$this->values['return_code'] = $value;
}
/**
* 获取SUCCESS/FAIL此字段是通信标识非交易标识交易是否成功需要查看trade_state来判断的值
* @return
**/
public function GetReturn_code()
{
return $this->values['return_code'];
}
/**
* 判断SUCCESS/FAIL此字段是通信标识非交易标识交易是否成功需要查看trade_state来判断是否存在
* @return true false
**/
public function IsReturn_codeSet()
{
return array_key_exists('return_code', $this->values);
}
/**
* 设置返回信息,如非空,为错误原因签名失败参数格式校验错误
* @param string $value
**/
public function SetReturn_msg($value)
{
$this->values['return_msg'] = $value;
}
/**
* 获取返回信息,如非空,为错误原因签名失败参数格式校验错误的值
* @return
**/
public function GetReturn_msg()
{
return $this->values['return_msg'];
}
/**
* 判断返回信息,如非空,为错误原因签名失败参数格式校验错误是否存在
* @return true false
**/
public function IsReturn_msgSet()
{
return array_key_exists('return_msg', $this->values);
}
/**
* 设置SUCCESS/FAIL
* @param string $value
**/
public function SetResult_code($value)
{
$this->values['result_code'] = $value;
}
/**
* 获取SUCCESS/FAIL的值
* @return
**/
public function GetResult_code()
{
return $this->values['result_code'];
}
/**
* 判断SUCCESS/FAIL是否存在
* @return true false
**/
public function IsResult_codeSet()
{
return array_key_exists('result_code', $this->values);
}
/**
* 设置ORDERNOTEXIST—订单不存在SYSTEMERROR—系统错误
* @param string $value
**/
public function SetErr_code($value)
{
$this->values['err_code'] = $value;
}
/**
* 获取ORDERNOTEXIST—订单不存在SYSTEMERROR—系统错误的值
* @return
**/
public function GetErr_code()
{
return $this->values['err_code'];
}
/**
* 判断ORDERNOTEXIST—订单不存在SYSTEMERROR—系统错误是否存在
* @return true false
**/
public function IsErr_codeSet()
{
return array_key_exists('err_code', $this->values);
}
/**
* 设置结果信息描述
* @param string $value
**/
public function SetErr_code_des($value)
{
$this->values['err_code_des'] = $value;
}
/**
* 获取结果信息描述的值
* @return
**/
public function GetErr_code_des()
{
return $this->values['err_code_des'];
}
/**
* 判断结果信息描述是否存在
* @return true false
**/
public function IsErr_code_desSet()
{
return array_key_exists('err_code_des', $this->values);
}
/**
* 设置商户系统内部的订单号,商户可以在上报时提供相关商户订单号方便微信支付更好的提高服务质量。
* @param string $value
**/
public function SetOut_trade_no($value)
{
$this->values['out_trade_no'] = $value;
}
/**
* 获取商户系统内部的订单号,商户可以在上报时提供相关商户订单号方便微信支付更好的提高服务质量。 的值
* @return
**/
public function GetOut_trade_no()
{
return $this->values['out_trade_no'];
}
/**
* 判断商户系统内部的订单号,商户可以在上报时提供相关商户订单号方便微信支付更好的提高服务质量。 是否存在
* @return true false
**/
public function IsOut_trade_noSet()
{
return array_key_exists('out_trade_no', $this->values);
}
/**
* 设置发起接口调用时的机器IP
* @param string $value
**/
public function SetUser_ip($value)
{
$this->values['user_ip'] = $value;
}
/**
* 获取发起接口调用时的机器IP 的值
* @return
**/
public function GetUser_ip()
{
return $this->values['user_ip'];
}
/**
* 判断发起接口调用时的机器IP 是否存在
* @return true false
**/
public function IsUser_ipSet()
{
return array_key_exists('user_ip', $this->values);
}
/**
* 设置系统时间格式为yyyyMMddHHmmss如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则
* @param string $value
**/
public function SetTime($value)
{
$this->values['time'] = $value;
}
/**
* 获取系统时间格式为yyyyMMddHHmmss如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则的值
* @return
**/
public function GetTime()
{
return $this->values['time'];
}
/**
* 判断系统时间格式为yyyyMMddHHmmss如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则是否存在
* @return true false
**/
public function IsTimeSet()
{
return array_key_exists('time', $this->values);
}
}

View File

@ -0,0 +1,119 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/2/24
* Time: 7:17 PM
*/
namespace app\common\wxpay\lib;
class WxPayResults extends WxPayDataBase
{
/**
* 生成签名 - 重写该方法
* @param WxPayConfigInterface $config 配置对象
* @param bool $needSignType 是否需要补signtype
* @return 签名本函数不覆盖sign成员变量如要设置签名需要调用SetSign方法赋值
*/
public function MakeSign($config, $needSignType = false)
{
//签名步骤一:按字典序排序参数
ksort($this->values);
$string = $this->ToUrlParams();
//签名步骤二在string后加入KEY
$string = $string . "&key=".$config->GetKey();
//签名步骤三MD5加密或者HMAC-SHA256
if(strlen($this->GetSign()) <= 32){
//如果签名小于等于32个,则使用md5验证
$string = md5($string);
} else {
//是用sha256校验
$string = hash_hmac("sha256",$string ,$config->GetKey());
}
//签名步骤四:所有字符转为大写
$result = strtoupper($string);
return $result;
}
/**
* @param WxPayConfigInterface $config 配置对象
* 检测签名
*/
public function CheckSign($config)
{
if(!$this->IsSignSet()){
throw new WxPayException("签名错误!");
}
$sign = $this->MakeSign($config, false);
if($this->GetSign() == $sign){
//签名正确
return true;
}
throw new WxPayException("签名错误!");
}
/**
*
* 使用数组初始化
* @param array $array
*/
public function FromArray($array)
{
$this->values = $array;
}
/**
*
* 使用数组初始化对象
* @param array $array
* @param 是否检测签名 $noCheckSign
*/
public static function InitFromArray($config, $array, $noCheckSign = false)
{
$obj = new self();
$obj->FromArray($array);
if($noCheckSign == false){
$obj->CheckSign($config);
}
return $obj;
}
/**
*
* 设置参数
* @param string $key
* @param string $value
*/
public function SetData($key, $value)
{
$this->values[$key] = $value;
}
/**
* 将xml转为array
* @param WxPayConfigInterface $config 配置对象
* @param string $xml
* @throws WxPayException
*/
public static function Init($config, $xml)
{
$obj = new self();
$obj->FromXml($xml);
//失败则直接返回失败
if($obj->values['return_code'] != 'SUCCESS') {
foreach ($obj->values as $key => $value) {
#除了return_code和return_msg之外其他的参数存在则报错
if($key != "return_code" && $key != "return_msg"){
throw new WxPayException("输入数据存在异常!");
return false;
}
}
return $obj->GetValues();
}
$obj->CheckSign($config);
return $obj->GetValues();
}
}

View File

@ -0,0 +1,166 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/2/24
* Time: 7:03 PM
*/
namespace app\common\wxpay\lib;
class WxPayReverse extends WxPayDataBase
{
/**
* 设置微信分配的公众账号ID
* @param string $value
**/
public function SetAppid($value)
{
$this->values['appid'] = $value;
}
/**
* 获取微信分配的公众账号ID的值
* @return
**/
public function GetAppid()
{
return $this->values['appid'];
}
/**
* 判断微信分配的公众账号ID是否存在
* @return true false
**/
public function IsAppidSet()
{
return array_key_exists('appid', $this->values);
}
/**
* 设置微信支付分配的商户号
* @param string $value
**/
public function SetMch_id($value)
{
$this->values['mch_id'] = $value;
}
/**
* 获取微信支付分配的商户号的值
* @return
**/
public function GetMch_id()
{
return $this->values['mch_id'];
}
/**
* 判断微信支付分配的商户号是否存在
* @return true false
**/
public function IsMch_idSet()
{
return array_key_exists('mch_id', $this->values);
}
/**
* 设置微信支付分配的子商户号
* @param string $value
**/
public function SetSubMch_id($value)
{
$this->values['sub_mch_id'] = $value;
}
/**
* 获取微信支付分配的子商户号的值
* @return
**/
public function GetSubMch_id()
{
return $this->values['sub_mch_id'];
}
/**
* 判断微信支付分配的子商户号是否存在
* @return true false
**/
public function IsSubMch_idSet()
{
return array_key_exists('sub_mch_id', $this->values);
}
/**
* 设置微信的订单号,优先使用
* @param string $value
**/
public function SetTransaction_id($value)
{
$this->values['transaction_id'] = $value;
}
/**
* 获取微信的订单号,优先使用的值
* @return
**/
public function GetTransaction_id()
{
return $this->values['transaction_id'];
}
/**
* 判断微信的订单号,优先使用是否存在
* @return true false
**/
public function IsTransaction_idSet()
{
return array_key_exists('transaction_id', $this->values);
}
/**
* 设置商户系统内部的订单号,transaction_id、out_trade_no二选一如果同时存在优先级transaction_id> out_trade_no
* @param string $value
**/
public function SetOut_trade_no($value)
{
$this->values['out_trade_no'] = $value;
}
/**
* 获取商户系统内部的订单号,transaction_id、out_trade_no二选一如果同时存在优先级transaction_id> out_trade_no的值
* @return
**/
public function GetOut_trade_no()
{
return $this->values['out_trade_no'];
}
/**
* 判断商户系统内部的订单号,transaction_id、out_trade_no二选一如果同时存在优先级transaction_id> out_trade_no是否存在
* @return true false
**/
public function IsOut_trade_noSet()
{
return array_key_exists('out_trade_no', $this->values);
}
/**
* 设置随机字符串不长于32位。推荐随机数生成算法
* @param string $value
**/
public function SetNonce_str($value)
{
$this->values['nonce_str'] = $value;
}
/**
* 获取随机字符串不长于32位。推荐随机数生成算法的值
* @return
**/
public function GetNonce_str()
{
return $this->values['nonce_str'];
}
/**
* 判断随机字符串不长于32位。推荐随机数生成算法是否存在
* @return true false
**/
public function IsNonce_strSet()
{
return array_key_exists('nonce_str', $this->values);
}
}

View File

@ -0,0 +1,116 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/2/24
* Time: 7:06 PM
*/
namespace app\common\wxpay\lib;
class WxPayShortUrl extends WxPayDataBase
{
/**
* 设置微信分配的公众账号ID
* @param string $value
**/
public function SetAppid($value)
{
$this->values['appid'] = $value;
}
/**
* 获取微信分配的公众账号ID的值
* @return
**/
public function GetAppid()
{
return $this->values['appid'];
}
/**
* 判断微信分配的公众账号ID是否存在
* @return true false
**/
public function IsAppidSet()
{
return array_key_exists('appid', $this->values);
}
/**
* 设置微信支付分配的商户号
* @param string $value
**/
public function SetMch_id($value)
{
$this->values['mch_id'] = $value;
}
/**
* 获取微信支付分配的商户号的值
* @return
**/
public function GetMch_id()
{
return $this->values['mch_id'];
}
/**
* 判断微信支付分配的商户号是否存在
* @return true false
**/
public function IsMch_idSet()
{
return array_key_exists('mch_id', $this->values);
}
/**
* 设置需要转换的URL签名用原串传输需URL encode
* @param string $value
**/
public function SetLong_url($value)
{
$this->values['long_url'] = $value;
}
/**
* 获取需要转换的URL签名用原串传输需URL encode的值
* @return
**/
public function GetLong_url()
{
return $this->values['long_url'];
}
/**
* 判断需要转换的URL签名用原串传输需URL encode是否存在
* @return true false
**/
public function IsLong_urlSet()
{
return array_key_exists('long_url', $this->values);
}
/**
* 设置随机字符串不长于32位。推荐随机数生成算法
* @param string $value
**/
public function SetNonce_str($value)
{
$this->values['nonce_str'] = $value;
}
/**
* 获取随机字符串不长于32位。推荐随机数生成算法的值
* @return
**/
public function GetNonce_str()
{
return $this->values['nonce_str'];
}
/**
* 判断随机字符串不长于32位。推荐随机数生成算法是否存在
* @return true false
**/
public function IsNonce_strSet()
{
return array_key_exists('nonce_str', $this->values);
}
}

View File

@ -0,0 +1,479 @@
<?php
/**
* Created by PhpStorm.
* User: lock
* Date: 2019/2/24
* Time: 7:11 PM
*/
namespace app\common\wxpay\lib;
class WxPayUnifiedOrder extends WxPayDataBase
{
/**
* 设置微信分配的公众账号ID
* @param string $value
**/
public function SetAppid($value)
{
$this->values['appid'] = $value;
}
/**
* 获取微信分配的公众账号ID的值
* @return
**/
public function GetAppid()
{
return $this->values['appid'];
}
/**
* 判断微信分配的公众账号ID是否存在
* @return true false
**/
public function IsAppidSet()
{
return array_key_exists('appid', $this->values);
}
/**
* 设置微信支付分配的商户号
* @param string $value
**/
public function SetMch_id($value)
{
$this->values['mch_id'] = $value;
}
/**
* 获取微信支付分配的商户号的值
* @return
**/
public function GetMch_id()
{
return $this->values['mch_id'];
}
/**
* 判断微信支付分配的商户号是否存在
* @return true false
**/
public function IsMch_idSet()
{
return array_key_exists('mch_id', $this->values);
}
/**
* 设置微信支付分配的终端设备号,商户自定义
* @param string $value
**/
public function SetDevice_info($value)
{
$this->values['device_info'] = $value;
}
/**
* 获取微信支付分配的终端设备号,商户自定义的值
* @return
**/
public function GetDevice_info()
{
return $this->values['device_info'];
}
/**
* 判断微信支付分配的终端设备号,商户自定义是否存在
* @return true false
**/
public function IsDevice_infoSet()
{
return array_key_exists('device_info', $this->values);
}
/**
* 设置随机字符串不长于32位。推荐随机数生成算法
* @param string $value
**/
public function SetNonce_str($value)
{
$this->values['nonce_str'] = $value;
}
/**
* 获取随机字符串不长于32位。推荐随机数生成算法的值
* @return
**/
public function GetNonce_str()
{
return $this->values['nonce_str'];
}
/**
* 判断随机字符串不长于32位。推荐随机数生成算法是否存在
* @return true false
**/
public function IsNonce_strSet()
{
return array_key_exists('nonce_str', $this->values);
}
/**
* 设置商品或支付单简要描述
* @param string $value
**/
public function SetBody($value)
{
$this->values['body'] = $value;
}
/**
* 获取商品或支付单简要描述的值
* @return
**/
public function GetBody()
{
return $this->values['body'];
}
/**
* 判断商品或支付单简要描述是否存在
* @return true false
**/
public function IsBodySet()
{
return array_key_exists('body', $this->values);
}
/**
* 设置商品名称明细列表
* @param string $value
**/
public function SetDetail($value)
{
$this->values['detail'] = $value;
}
/**
* 获取商品名称明细列表的值
* @return
**/
public function GetDetail()
{
return $this->values['detail'];
}
/**
* 判断商品名称明细列表是否存在
* @return true false
**/
public function IsDetailSet()
{
return array_key_exists('detail', $this->values);
}
/**
* 设置附加数据在查询API和支付通知中原样返回该字段主要用于商户携带订单的自定义数据
* @param string $value
**/
public function SetAttach($value)
{
$this->values['attach'] = $value;
}
/**
* 获取附加数据在查询API和支付通知中原样返回该字段主要用于商户携带订单的自定义数据的值
* @return
**/
public function GetAttach()
{
return $this->values['attach'];
}
/**
* 判断附加数据在查询API和支付通知中原样返回该字段主要用于商户携带订单的自定义数据是否存在
* @return true false
**/
public function IsAttachSet()
{
return array_key_exists('attach', $this->values);
}
/**
* 设置商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号
* @param string $value
**/
public function SetOut_trade_no($value)
{
$this->values['out_trade_no'] = $value;
}
/**
* 获取商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号的值
* @return
**/
public function GetOut_trade_no()
{
return $this->values['out_trade_no'];
}
/**
* 判断商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号是否存在
* @return true false
**/
public function IsOut_trade_noSet()
{
return array_key_exists('out_trade_no', $this->values);
}
/**
* 设置符合ISO 4217标准的三位字母代码默认人民币CNY其他值列表详见货币类型
* @param string $value
**/
public function SetFee_type($value)
{
$this->values['fee_type'] = $value;
}
/**
* 获取符合ISO 4217标准的三位字母代码默认人民币CNY其他值列表详见货币类型的值
* @return
**/
public function GetFee_type()
{
return $this->values['fee_type'];
}
/**
* 判断符合ISO 4217标准的三位字母代码默认人民币CNY其他值列表详见货币类型是否存在
* @return true false
**/
public function IsFee_typeSet()
{
return array_key_exists('fee_type', $this->values);
}
/**
* 设置订单总金额,只能为整数,详见支付金额
* @param string $value
**/
public function SetTotal_fee($value)
{
$this->values['total_fee'] = $value;
}
/**
* 获取订单总金额,只能为整数,详见支付金额的值
* @return
**/
public function GetTotal_fee()
{
return $this->values['total_fee'];
}
/**
* 判断订单总金额,只能为整数,详见支付金额是否存在
* @return true false
**/
public function IsTotal_feeSet()
{
return array_key_exists('total_fee', $this->values);
}
/**
* 设置APP和网页支付提交用户端ipNative支付填调用微信支付API的机器IP。
* @param string $value
**/
public function SetSpbill_create_ip($value)
{
$this->values['spbill_create_ip'] = $value;
}
/**
* 获取APP和网页支付提交用户端ipNative支付填调用微信支付API的机器IP。的值
* @return
**/
public function GetSpbill_create_ip()
{
return $this->values['spbill_create_ip'];
}
/**
* 判断APP和网页支付提交用户端ipNative支付填调用微信支付API的机器IP。是否存在
* @return true false
**/
public function IsSpbill_create_ipSet()
{
return array_key_exists('spbill_create_ip', $this->values);
}
/**
* 设置订单生成时间格式为yyyyMMddHHmmss如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则
* @param string $value
**/
public function SetTime_start($value)
{
$this->values['time_start'] = $value;
}
/**
* 获取订单生成时间格式为yyyyMMddHHmmss如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则的值
* @return
**/
public function GetTime_start()
{
return $this->values['time_start'];
}
/**
* 判断订单生成时间格式为yyyyMMddHHmmss如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则是否存在
* @return true false
**/
public function IsTime_startSet()
{
return array_key_exists('time_start', $this->values);
}
/**
* 设置订单失效时间格式为yyyyMMddHHmmss如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则
* @param string $value
**/
public function SetTime_expire($value)
{
$this->values['time_expire'] = $value;
}
/**
* 获取订单失效时间格式为yyyyMMddHHmmss如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则的值
* @return
**/
public function GetTime_expire()
{
return $this->values['time_expire'];
}
/**
* 判断订单失效时间格式为yyyyMMddHHmmss如2009年12月27日9点10分10秒表示为20091227091010。其他详见时间规则是否存在
* @return true false
**/
public function IsTime_expireSet()
{
return array_key_exists('time_expire', $this->values);
}
/**
* 设置商品标记,代金券或立减优惠功能的参数,说明详见代金券或立减优惠
* @param string $value
**/
public function SetGoods_tag($value)
{
$this->values['goods_tag'] = $value;
}
/**
* 获取商品标记,代金券或立减优惠功能的参数,说明详见代金券或立减优惠的值
* @return
**/
public function GetGoods_tag()
{
return $this->values['goods_tag'];
}
/**
* 判断商品标记,代金券或立减优惠功能的参数,说明详见代金券或立减优惠是否存在
* @return true false
**/
public function IsGoods_tagSet()
{
return array_key_exists('goods_tag', $this->values);
}
/**
* 设置接收微信支付异步通知回调地址
* @param string $value
**/
public function SetNotify_url($value)
{
$this->values['notify_url'] = $value;
}
/**
* 获取接收微信支付异步通知回调地址的值
* @return
**/
public function GetNotify_url()
{
return $this->values['notify_url'];
}
/**
* 判断接收微信支付异步通知回调地址是否存在
* @return true false
**/
public function IsNotify_urlSet()
{
return array_key_exists('notify_url', $this->values);
}
/**
* 设置取值如下JSAPINATIVEAPP详细说明见参数规定
* @param string $value
**/
public function SetTrade_type($value)
{
$this->values['trade_type'] = $value;
}
/**
* 获取取值如下JSAPINATIVEAPP详细说明见参数规定的值
* @return
**/
public function GetTrade_type()
{
return $this->values['trade_type'];
}
/**
* 判断取值如下JSAPINATIVEAPP详细说明见参数规定是否存在
* @return true false
**/
public function IsTrade_typeSet()
{
return array_key_exists('trade_type', $this->values);
}
/**
* 设置trade_type=NATIVE此参数必传。此id为二维码中包含的商品ID商户自行定义。
* @param string $value
**/
public function SetProduct_id($value)
{
$this->values['product_id'] = $value;
}
/**
* 获取trade_type=NATIVE此参数必传。此id为二维码中包含的商品ID商户自行定义。的值
* @return
**/
public function GetProduct_id()
{
return $this->values['product_id'];
}
/**
* 判断trade_type=NATIVE此参数必传。此id为二维码中包含的商品ID商户自行定义。是否存在
* @return true false
**/
public function IsProduct_idSet()
{
return array_key_exists('product_id', $this->values);
}
/**
* 设置trade_type=JSAPI此参数必传用户在商户appid下的唯一标识。下单前需要调用【网页授权获取用户信息】接口获取到用户的Openid。
* @param string $value
**/
public function SetOpenid($value)
{
$this->values['openid'] = $value;
}
/**
* 获取trade_type=JSAPI此参数必传用户在商户appid下的唯一标识。下单前需要调用【网页授权获取用户信息】接口获取到用户的Openid。 的值
* @return
**/
public function GetOpenid()
{
return $this->values['openid'];
}
/**
* 判断trade_type=JSAPI此参数必传用户在商户appid下的唯一标识。下单前需要调用【网页授权获取用户信息】接口获取到用户的Openid。 是否存在
* @return true false
**/
public function IsOpenidSet()
{
return array_key_exists('openid', $this->values);
}
}

View File

@ -0,0 +1,253 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
return [
// +----------------------------------------------------------------------
// | 应用设置
// +----------------------------------------------------------------------
// 应用调试模式
'app_debug' => true,
// 应用Trace
'app_trace' => false,
// 应用模式状态
'app_status' => '',
// 是否支持多模块
'app_multi_module' => true,
// 入口自动绑定模块
'auto_bind_module' => false,
// 注册的根命名空间
'root_namespace' => [],
// 扩展函数文件
'extra_file_list' => [THINK_PATH . 'helper' . EXT],
// 默认输出类型
'default_return_type' => 'html',
// 默认AJAX 数据返回格式,可选json xml ...
'default_ajax_return' => 'json',
// 默认JSONP格式返回的处理方法
'default_jsonp_handler' => 'jsonpReturn',
// 默认JSONP处理方法
'var_jsonp_handler' => 'callback',
// 默认时区
'default_timezone' => 'PRC',
// 是否开启多语言
'lang_switch_on' => false,
// 默认全局过滤方法 用逗号分隔多个
'default_filter' => '',
// 默认语言
'default_lang' => 'zh-cn',
// 应用类库后缀
'class_suffix' => false,
// 控制器类后缀
'controller_suffix' => false,
// +----------------------------------------------------------------------
// | 模块设置
// +----------------------------------------------------------------------
// 默认模块名
'default_module' => 'index',
// 禁止访问模块
'deny_module_list' => ['common'],
// 默认控制器名
'default_controller' => 'Index',
// 默认操作名
'default_action' => 'index',
// 默认验证器
'default_validate' => '',
// 默认的空控制器名
'empty_controller' => 'Error',
// 操作方法后缀
'action_suffix' => '',
// 自动搜索控制器
'controller_auto_search' => false,
// +----------------------------------------------------------------------
// | URL设置
// +----------------------------------------------------------------------
// PATHINFO变量名 用于兼容模式
'var_pathinfo' => 's',
// 兼容PATH_INFO获取
'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
// pathinfo分隔符
'pathinfo_depr' => '/',
// URL伪静态后缀
'url_html_suffix' => 'html',
// URL普通方式参数 用于自动生成
'url_common_param' => false,
// URL参数方式 0 按名称成对解析 1 按顺序解析
'url_param_type' => 0,
// 是否开启路由
'url_route_on' => true,
// 路由使用完整匹配
'route_complete_match' => false,
// 路由配置文件(支持配置多个)
'route_config_file' => ['route'],
// 是否开启路由解析缓存
'route_check_cache' => false,
// 是否强制使用路由
'url_route_must' => true,
// 域名部署
'url_domain_deploy' => false,
// 域名根如thinkphp.cn
'url_domain_root' => '',
// 是否自动转换URL中的控制器和操作名
'url_convert' => true,
// 默认的访问控制器层
'url_controller_layer' => 'controller',
// 表单请求类型伪装变量
'var_method' => '_method',
// 表单ajax伪装变量
'var_ajax' => '_ajax',
// 表单pjax伪装变量
'var_pjax' => '_pjax',
// 是否开启请求缓存 true自动缓存 支持设置请求缓存规则
'request_cache' => false,
// 请求缓存有效期
'request_cache_expire' => null,
// 全局请求缓存排除规则
'request_cache_except' => [],
// +----------------------------------------------------------------------
// | 模板设置
// +----------------------------------------------------------------------
'template' => [
// 模板引擎类型 支持 php think 支持扩展
'type' => 'Think',
// 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写
'auto_rule' => 1,
// 模板路径
'view_path' => '',
// 模板后缀
'view_suffix' => 'html',
// 模板文件名分隔符
'view_depr' => DS,
// 模板引擎普通标签开始标记
'tpl_begin' => '{',
// 模板引擎普通标签结束标记
'tpl_end' => '}',
// 标签库标签开始标记
'taglib_begin' => '{',
// 标签库标签结束标记
'taglib_end' => '}',
],
// 视图输出字符串内容替换
'view_replace_str' => [
'__PUBLIC__' => '',
'__ROOT__' => '',
'__CDN__' => '',
],
// 默认跳转页面对应的模板文件
'dispatch_success_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl',
'dispatch_error_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl',
// +----------------------------------------------------------------------
// | 异常及错误设置
// +----------------------------------------------------------------------
// 异常页面的模板文件
'exception_tmpl' => THINK_PATH . 'tpl' . DS . 'think_exception.tpl',
// 错误显示信息,非调试模式有效
'error_message' => '页面错误!请稍后再试~',
// 显示错误信息
'show_error_msg' => false,
// 保存错误信息日志
'save_error_msg' => true,
// 异常处理handle类 留空使用 \think\exception\Handle
'exception_handle' => '',
// +----------------------------------------------------------------------
// | 日志设置
// +----------------------------------------------------------------------
'log' => [
// 日志记录方式,内置 file socket 支持扩展
'type' => 'File',
// 日志保存目录
'path' => LOG_PATH,
// 日志记录级别
'level' => ['error'],
],
// +----------------------------------------------------------------------
// | Trace设置 开启 app_trace 后 有效
// +----------------------------------------------------------------------
'trace' => [
// 内置Html Console 支持扩展
'type' => 'Html',
],
// +----------------------------------------------------------------------
// | 缓存设置
// +----------------------------------------------------------------------
'cache' => [
// 驱动方式
'type' => 'File',
// 缓存保存目录
'path' => CACHE_PATH,
// 缓存前缀
'prefix' => '',
// 缓存有效期 0表示永久缓存
'expire' => 0,
],
// +----------------------------------------------------------------------
// | 会话设置
// +----------------------------------------------------------------------
'session' => [
'id' => '',
// SESSION_ID的提交变量,解决flash上传跨域
'var_session_id' => '',
// SESSION 前缀
'prefix' => 'think',
// 驱动方式 支持redis memcache memcached
'type' => '',
// 是否自动开启 SESSION
'auto_start' => true,
],
// +----------------------------------------------------------------------
// | Cookie设置
// +----------------------------------------------------------------------
'cookie' => [
// cookie 名称前缀
'prefix' => '',
// cookie 保存时间
'expire' => 0,
// cookie 保存路径
'path' => '/',
// cookie 有效域名
'domain' => '',
// cookie 启用安全传输
'secure' => false,
// httponly设置
'httponly' => '',
// 是否使用 setcookie
'setcookie' => true,
],
//分页配置
'paginate' => [
'type' => 'bootstrap',
'var_page' => 'page',
'list_rows' => 15,
],
'error_code_field' => 'error_code',
'api_domain' => 'http://api.hetatech.cn'
];

View File

@ -0,0 +1,55 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
return [
// 数据库类型
'type' => 'Mysql',
// 服务器地址
'hostname' => '127.0.0.1',
// 数据库名
'database' => 'board',
// 用户名
'username' => 'root',
// 密码
'password' => 'root',
// 端口
'hostport' => '3306',
// 连接dsn
'dsn' => '',
// 数据库连接参数
'params' => [],
// 数据库编码默认采用utf8
'charset' => 'utf8',
// 数据库表前缀
'prefix' => 'board_',
// 数据库调试模式
'debug' => false,
// 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器)
'deploy' => 0,
// 数据库读写是否分离 主从式有效
'rw_separate' => false,
// 读写分离后 主服务器数量
'master_num' => 1,
// 指定从服务器序号
'slave_no' => '',
// 自动读取主库数据
'read_master' => false,
// 是否严格检查字段是否存在
'fields_strict' => true,
// 数据集返回类型
'resultset_type' => 'array',
// 自动写入时间戳字段
'auto_timestamp' => false,
// 时间字段取出后的默认时间格式
'datetime_format' => 'Y-m-d H:i:s',
// 是否需要进行SQL性能分析
'sql_explain' => false,
];

Some files were not shown because too many files have changed in this diff Show More