作者 WangXuebo

ini

正在显示 53 个修改的文件 包含 4825 行增加0 行删除

要显示太多修改。

为保证性能只显示 53 of 53+ 个文件。

{
"directory": "public/assets/libs",
"ignoredDependencies": [
"es6-promise",
"file-saver",
"html2canvas",
"jspdf",
"jspdf-autotable",
"pdfmake"
]
}
... ...
[app]
debug = false
trace = false
[database]
hostname = 127.0.0.1
database = fastadmin
username = root
password = root
hostport = 3306
prefix = fa_
... ...
/nbproject/
/runtime/*
/public/uploads/*
.idea
*.log
*.css.map
!.gitkeep
.env
.svn
.vscode
node_modules
... ...
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:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
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
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 2017 Karson
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.
\ No newline at end of file
... ...
FastAdmin是一款基于ThinkPHP+Bootstrap的极速后台开发框架。
## 主要特性
* 基于`Auth`验证的权限管理系统
* 支持无限级父子级权限继承,父级的管理员可任意增删改子级管理员及权限设置
* 支持单管理员多角色
* 支持管理子级数据或个人数据
* 强大的一键生成功能
* 一键生成CRUD,包括控制器、模型、视图、JS、语言包、菜单、回收站等
* 一键压缩打包JS和CSS文件,一键CDN静态资源部署
* 一键生成控制器菜单和规则
* 一键生成API接口文档
* 完善的前端功能组件开发
* 基于`AdminLTE`二次开发
* 基于`Bootstrap`开发,自适应手机、平板、PC
* 基于`RequireJS`进行JS模块管理,按需加载
* 基于`Less`进行样式开发
* 强大的插件扩展功能,在线安装卸载升级插件
* 通用的会员模块和API模块
* 共用同一账号体系的Web端会员中心权限验证和API接口会员权限验证
* 二级域名部署支持,同时域名支持绑定到应用插件
* 多语言支持,服务端及客户端支持
* 支持大文件分片上传、剪切板粘贴上传、拖拽上传,进度条显示,图片上传前压缩
* 支持表格固定列、固定表头、跨页选择、Excel导出、模板渲染等功能
* 强大的第三方应用模块支持([CMS](https://www.fastadmin.net/store/cms.html)[博客](https://www.fastadmin.net/store/blog.html)[知识付费问答](https://www.fastadmin.net/store/ask.html)[在线投票系统](https://www.fastadmin.net/store/vote.html)[B2C商城](https://www.fastadmin.net/store/shopro.html)[B2B2C商城](https://www.fastadmin.net/store/wanlshop.html))
* 支持CMS、博客、知识付费问答无缝整合[Xunsearch全文搜索](https://www.fastadmin.net/store/xunsearch.html)
* 第三方小程序支持([CMS小程序](https://www.fastadmin.net/store/cms.html)[预订小程序](https://www.fastadmin.net/store/ball.html)[问答小程序](https://www.fastadmin.net/store/ask.html)[点餐小程序](https://www.fastadmin.net/store/unidrink.html)[B2C小程序](https://www.fastadmin.net/store/shopro.html)[B2B2C小程序](https://www.fastadmin.net/store/wanlshop.html)[博客小程序](https://www.fastadmin.net/store/blog.html))
* 整合第三方短信接口(阿里云、腾讯云短信)
* 无缝整合第三方云存储(七牛云、阿里云OSS、又拍云)功能,支持云储存分片上传
* 第三方富文本编辑器支持(Summernote、Kindeditor、百度编辑器)
* 第三方登录(QQ、微信、微博)整合
* 第三方支付(微信、支付宝)无缝整合,微信支持PC端扫码支付
* 丰富的插件应用市场
## 安装使用
https://doc.fastadmin.net
## 在线演示
https://demo.fastadmin.net
用户名:admin
密 码:123456
提 示:演示站数据无法进行修改,请下载源码安装体验全部功能
## 界面截图
![控制台](https://images.gitee.com/uploads/images/2020/0929/202947_8db2d281_10933.gif "控制台")
## 问题反馈
在使用中有任何问题,请使用以下联系方式联系我们
交流社区: https://ask.fastadmin.net
QQ群: [636393962](https://jq.qq.com/?_wv=1027&k=487PNBb)() [708784003](https://jq.qq.com/?_wv=1027&k=5ObjtwM)(满) [964776039](https://jq.qq.com/?_wv=1027&k=59qjU2P)(3群) [749803490](https://jq.qq.com/?_wv=1027&k=5tczi88)(满) [767103006](https://jq.qq.com/?_wv=1027&k=5Z1U751)() [675115483](https://jq.qq.com/?_wv=1027&k=54I6mts)(6群)
Github: https://github.com/karsonzhang/fastadmin
Gitee: https://gitee.com/karson/fastadmin
## 特别鸣谢
感谢以下的项目,排名不分先后
ThinkPHP:http://www.thinkphp.cn
AdminLTE:https://adminlte.io
Bootstrap:http://getbootstrap.com
jQuery:http://jquery.com
Bootstrap-table:https://github.com/wenzhixin/bootstrap-table
Nice-validator: https://validator.niceue.com
SelectPage: https://github.com/TerryZ/SelectPage
Layer: https://layer.layui.com
DropzoneJS: https://www.dropzonejs.com
## 版权信息
FastAdmin遵循Apache2开源协议发布,并提供免费使用。
本项目包含的第三方源码和二进制文件之版权信息另行标注。
版权所有Copyright © 2017-2022 by FastAdmin (https://www.fastadmin.net)
All rights reserved。
... ...
deny from all
\ No newline at end of file
... ...
{"files":["application\\admin\\controller\\Command.php","application\\admin\\lang\\zh-cn\\command.php","application\\admin\\model\\Command.php","application\\admin\\validate\\Command.php","application\\admin\\view\\command\\add.html","application\\admin\\view\\command\\detail.html","application\\admin\\view\\command\\index.html","public\\assets\\js\\backend\\command.js"],"license":"regular","licenseto":"10789","licensekey":"cF9I6tYjn1ORXeDu grOxaQyT3NGbyAOZYnc3Og==","domains":["chexiubao.cc"],"licensecodes":[],"validations":["aaf3e3478c1f9ee1bcf7bc25025eb678"],"menus":["command","command\/index","command\/add","command\/detail","command\/execute","command\/del","command\/multi"]}
\ No newline at end of file
... ...
<?php
namespace addons\command;
use app\common\library\Menu;
use think\Addons;
/**
* 在线命令插件
*/
class Command extends Addons
{
/**
* 插件安装方法
* @return bool
*/
public function install()
{
$menu = [
[
'name' => 'command',
'title' => '在线命令管理',
'icon' => 'fa fa-terminal',
'sublist' => [
['name' => 'command/index', 'title' => '查看'],
['name' => 'command/add', 'title' => '添加'],
['name' => 'command/detail', 'title' => '详情'],
['name' => 'command/execute', 'title' => '运行'],
['name' => 'command/del', 'title' => '删除'],
['name' => 'command/multi', 'title' => '批量更新'],
]
]
];
Menu::create($menu);
return true;
}
/**
* 插件卸载方法
* @return bool
*/
public function uninstall()
{
Menu::delete('command');
return true;
}
/**
* 插件启用方法
* @return bool
*/
public function enable()
{
Menu::enable('command');
return true;
}
/**
* 插件禁用方法
* @return bool
*/
public function disable()
{
Menu::disable('command');
return true;
}
}
... ...
<?php
return [
];
... ...
<?php
namespace addons\command\controller;
use think\addons\Controller;
class Index extends Controller
{
public function index()
{
$this->error("当前插件暂无前台页面");
}
}
... ...
name = command
title = 在线命令
intro = 可在线执行FastAdmin的命令行相关命令
author = Karson
website = https://www.fastadmin.net
version = 1.0.6
state = 1
url = /addons/command
license = regular
licenseto = 10789
... ...
CREATE TABLE IF NOT EXISTS `__PREFIX__command` (
`id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
`type` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '类型',
`params` varchar(1500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '参数',
`command` varchar(1500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '命令',
`content` text COMMENT '返回结果',
`executetime` int(10) UNSIGNED DEFAULT NULL COMMENT '执行时间',
`createtime` int(10) UNSIGNED DEFAULT NULL COMMENT '创建时间',
`updatetime` int(10) UNSIGNED DEFAULT NULL COMMENT '更新时间',
`status` enum('successed','failured') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'failured' COMMENT '状态',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '在线命令表';
... ...
<?php
namespace addons\command\library;
/**
* Class Output
*/
class Output extends \think\console\Output
{
protected $message = [];
public function __construct($driver = 'console')
{
parent::__construct($driver);
}
protected function block($style, $message)
{
$this->message[] = $message;
}
public function getMessage()
{
return $this->message;
}
}
... ...
{"files":["application\\admin\\controller\\Epay.php","public\\assets\\addons\\epay\\css\\common.css","public\\assets\\addons\\epay\\css\\epay.css","public\\assets\\addons\\epay\\images\\alipay.png","public\\assets\\addons\\epay\\images\\expired.png","public\\assets\\addons\\epay\\images\\logo-alipay.png","public\\assets\\addons\\epay\\images\\logo-wechat.png","public\\assets\\addons\\epay\\images\\paid.png","public\\assets\\addons\\epay\\images\\scan.png","public\\assets\\addons\\epay\\images\\screenshot-alipay.png","public\\assets\\addons\\epay\\images\\screenshot-wechat.png","public\\assets\\addons\\epay\\images\\wechat.png","public\\assets\\addons\\epay\\js\\common.js","public\\assets\\addons\\epay\\less\\common.less","public\\assets\\addons\\epay\\less\\epay.less"],"license":"regular","licenseto":"10789","licensekey":"ORU0McGKN6smzaS8 OxO4Mbfr73WG\/vizWncVQg==","domains":["chexiubao.cc"],"licensecodes":[],"validations":["aaf3e3478c1f9ee1bcf7bc25025eb678"]}
\ No newline at end of file
... ...
<?php
namespace addons\epay;
use think\Addons;
use think\Config;
use think\Loader;
/**
* 微信支付宝整合插件
*/
class Epay extends Addons
{
/**
* 插件安装方法
* @return bool
*/
public function install()
{
return true;
}
/**
* 插件卸载方法
* @return bool
*/
public function uninstall()
{
return true;
}
/**
* 插件启用方法
* @return bool
*/
public function enable()
{
return true;
}
/**
* 插件禁用方法
* @return bool
*/
public function disable()
{
return true;
}
/**
* 添加命名空间
*/
public function appInit()
{
//添加命名空间
if (!class_exists('\Yansongda\Pay\Pay')) {
Loader::addNamespace('Yansongda\Pay', ADDON_PATH . 'epay' . DS . 'library' . DS . 'Yansongda' . DS . 'Pay' . DS);
}
if (!class_exists('\Yansongda\Supports\Logger')) {
Loader::addNamespace('Yansongda\Supports', ADDON_PATH . 'epay' . DS . 'library' . DS . 'Yansongda' . DS . 'Supports' . DS);
}
}
}
... ...
-----BEGIN CERTIFICATE-----
MIID9jCCAt6gAwIBAgIUEkn92qxrC2XTPxnol9yW7/eOM84wDQYJKoZIhvcNAQEL
BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
Q0EwHhcNMjIwNTE5MDcwNzA5WhcNMjcwNTE4MDcwNzA5WjCBhzETMBEGA1UEAwwK
MTYyNDc5MDc3MDEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMTMwMQYDVQQL
DCrljJfkuqzkuK3kv53kvJflsqnnvZHnu5znp5HmioDmnInpmZDlhazlj7gxCzAJ
BgNVBAYMAkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAMi4qudLfIL5WJeYE0tX+H/GsvBtmnzU5BKVA658msgaM3F2
oAqQDSzI1L0EpneW9LI+N2rz3OyiNGks2nAOAzkHF69BYVmWTf7Ylo3okiW+r2RR
v/OVTPHvOFYdKkHwNaTntFbCIxUtFEV0q76z96ZHDs605WYhWYRMtqxi+/ijgq1f
njWVktD8Lbezi9bHXK1LXyx/FdYwEdkD3f0Y5IIhLT47Aad0af2RqjXOR/eqA7xe
7AK2lAUe8ujEMSt5hR80t1R1/+5OAyovVsn/GkGdgcW6scKZ0BO9UKhkfq6A+a8m
gSmmXGcfCQuWTZrBKFWBtQUNm4Ks3k8C0B9UC0UCAwEAAaOBgTB/MAkGA1UdEwQC
MAAwCwYDVR0PBAQDAgTwMGUGA1UdHwReMFwwWqBYoFaGVGh0dHA6Ly9ldmNhLml0
cnVzLmNvbS5jbi9wdWJsaWMvaXRydXNjcmw/Q0E9MUJENDIyMEU1MERCQzA0QjA2
QUQzOTc1NDk4NDZDMDFDM0U4RUJEMjANBgkqhkiG9w0BAQsFAAOCAQEAI37mG5ip
wgueNqPb/eVNVeaBbHeXv2Ac6LRHw9iKMhF8HRFDh/bj45MEnbCKJlSvPt834Axs
uMNkEK4/+tr6JeNkPSMg4krMlyA4fiKm04P3fdIf4XYuHTooKuUNoU5zLfgZZ+2l
po3ElZBaqE95C/OOwNdgKm8X2uTig0Uzn+aFExXxz4UVU1Sb3UADFYhrBKBuD5aK
tc/cF1OE5eHvHOi/7C5pkDXCuni4BCWMzrZTSLV6tAmSPiQxwIl6ctC3Jk/voa+c
JL/hleqrcf3Y4DOBLLcH14fpijAlT2Hg3GLVsP5cUP1gTl2Q/omv2ySjAGTLJ7x0
EpV/v/n06y+Rsw==
-----END CERTIFICATE-----
... ...
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDIuKrnS3yC+ViX
mBNLV/h/xrLwbZp81OQSlQOufJrIGjNxdqAKkA0syNS9BKZ3lvSyPjdq89zsojRp
LNpwDgM5BxevQWFZlk3+2JaN6JIlvq9kUb/zlUzx7zhWHSpB8DWk57RWwiMVLRRF
dKu+s/emRw7OtOVmIVmETLasYvv4o4KtX541lZLQ/C23s4vWx1ytS18sfxXWMBHZ
A939GOSCIS0+OwGndGn9kao1zkf3qgO8XuwCtpQFHvLoxDEreYUfNLdUdf/uTgMq
L1bJ/xpBnYHFurHCmdATvVCoZH6ugPmvJoEpplxnHwkLlk2awShVgbUFDZuCrN5P
AtAfVAtFAgMBAAECggEAdoPTJU6E7jCcBqZcfhgrOgbwkNWmah6pq+8ghV49ZflS
dqQ8SwBTRyYtto2QXPxc7wGdZyEmSPJdT9yT/aXIcVumuGw6NtNwqhDolFHGOVMl
7G34kLKPBCrjcLU3xkc25W+KI8/tNwuu9HO3FJreludN7u8QpAT8/gjts0uey2dz
5jUbSYAtrtaZn7r+fcGVT+8bEySW569CccODfE85YCDyUoDPf5dQUsT16zzO5ru9
OsiS9eXyb+ojybaOcvIkXciiS3nt38zeZWG09Dg4gQI4LQRVKe8TYz/mIo5JnXNU
r5zK/5xKFLnvhMiZ3GW5rZvoHJpGTGgnIpmmc3BTAQKBgQDy07YHyJL1kBJO3U3B
Q9nhegMxp49/BkCqYkhmy9z7UhYAueo5Ht+zOgkU9vPl6c7DaIDwQz9KSb6iHVci
lhWt0weBmJi/Xd+uUIT/3HnivQpvf6piqbQWYsTUNBOwkbXMQ1HckczBJiWlRVcC
FoZWqNJLYy8pcx+vnhmdvDqmCQKBgQDTnDWeRJrwWleiee/ipmdZGzl/YUQ0BnXf
mP0O7jZsBgFrXo8dvZ63plYxiGa6GTfKoaEHPinNlUroxVL2tEtk35f1uEpSjCA5
vNIEpICHNyBtXQDkrQHmPTN9IEs4ajDVOmN4os9vAGDvxT2py+S44ifixmywkwdB
/Zg2zJZqXQKBgD+8lkEAwqI2CLgummQqHRxz3DbCVoXu7DZMydhBlmVZLHFQW0xW
8Dyo9Q1NrMMiVaUw3vcxj4E+Tg1bSi3H2zTe+U9o8F76kuW8OMy34D5FhGGzztNK
2/wsPKG+cZSdCmztFqWgl2/zRVpWEvp3UxeYBGrJQ+3LS/vasbqXV4WRAoGAfTjN
SfETNvRfM5iw6hDKy6IerlBiyd6g1kZ0SeeRcO9E28LDMjhJuifGjd1H3QybRV/M
KLsShcO45nIOnEso6nKePBxobtzZHRGtvm2SWXG30L/ejXQL0bGZzIvVt3avmx7I
SlUeJ3Oz6W9xBA/SM84xCDOfkgYPn29wKc17nUkCgYAmRguhI8XfQgv8E+hw2nXj
HWKYRzjNyACOkMPwrMM2r8S+LyCP0vBteLEEHOOXLPEKgRSYRp/AVEaW0e/1gtmb
PMYSudQhzDdSa0cKjewkOxsZqifhu7niaxFECWUIF/QVoo6Bt3rT4sNqwEwImuaB
VusoS8dP08UjpGojgl+9lQ==
-----END PRIVATE KEY-----
... ...
<form id="config-form" class="edit-form form-horizontal" role="form" data-toggle="validator" method="POST" action="">
<div class="panel panel-default panel-intro">
<div class="panel-heading">
<ul class="nav nav-tabs nav-group">
<li class="active"><a href="#wechat" data-toggle="tab">微信支付</a></li>
<li><a href="#alipay" data-toggle="tab">支付宝</a></li>
</ul>
</div>
<div class="panel-body">
<div id="myTabContent" class="tab-content">
{foreach $addon.config as $item}
{if $item.name=='wechat'}
<div class="tab-pane fade active in" id="wechat">
<table class="table table-striped table-config">
<tbody>
<tr>
<td width="15%">APP appid</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[wechat][appid]" value="{$item.value.appid|default=''}" class="form-control" data-rule="" data-tip="APP应用中支付时使用"/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>公众号的app_id</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[wechat][app_id]" value="{$item.value.app_id|default=''}" class="form-control" data-rule="" data-tip="公众号支付必须"/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>公众号的app_secret</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[wechat][app_secret]" value="{$item.value.app_secret|default=''}" class="form-control" data-rule="" data-tip="仅在需要获取Openid时使用,一般情况下为空"/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>小程序的app_id</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[wechat][miniapp_id]" value="{$item.value.miniapp_id|default=''}" class="form-control" data-rule="" data-tip="仅在小程序支付时使用"/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>微信支付商户号ID</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[wechat][mch_id]" value="{$item.value.mch_id|default=''}" class="form-control" data-rule="" data-tip=""/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>微信支付商户的密钥</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[wechat][key]" value="{$item.value.key|default=''}" class="form-control" data-rule="" data-tip=""/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>支付模式</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
{:Form::radios('row[wechat][mode]',['normal'=>'正式环境','dev'=>'沙箱环境','service'=>'服务商模式'],$item.value.mode??'normal')}
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr data-type="service" class="{:$item.value.mode!='service'?'hidden':''}">
<td>子商户商户号ID</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[wechat][sub_mch_id]" value="{$item.value.sub_mch_id|default=''}" class="form-control" data-rule="" data-tip="如果未用到子商户,请勿填写"/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr data-type="service" class="{:$item.value.mode!='service'?'hidden':''}">
<td>子商户 APP appid</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[wechat][sub_appid]" value="{$item.value.sub_appid|default=''}" class="form-control" data-rule="" data-tip="如果未用到子商户,请勿填写"/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr data-type="service" class="{:$item.value.mode!='service'?'hidden':''}">
<td>子商户公众号的appid</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[wechat][sub_app_id]" value="{$item.value.sub_app_id|default=''}" class="form-control" data-rule="" data-tip="如果未用到子商户,请勿填写"/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr data-type="service" class="{:$item.value.mode!='service'?'hidden':''}">
<td>子商户小程序的appid</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[wechat][sub_miniapp_id]" value="{$item.value.sub_miniapp_id|default=''}" class="form-control" data-rule="" data-tip="如果未用到子商户,请勿填写"/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>回调通知地址</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[wechat][notify_url]" value="{$item.value.notify_url|default=''}" class="form-control" data-rule="" data-tip="请勿随意修改,实际以逻辑代码中请求的为准"/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>微信支付API证书cert</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<div class="input-group">
<input id="c-cert_client" class="form-control" size="50" name="row[wechat][cert_client]" type="text" value="{$item.value.cert_client|htmlentities}" data-tip="可选, 仅在退款、红包等情况时需要用到">
<div class="input-group-addon no-border no-padding">
<span><button type="button" id="faupload-cert_client" class="btn btn-danger faupload" data-url="epay/upload" data-multipart='{"certname":"cert_client"}' data-mimetype="pem" data-input-id="c-cert_client" data-multiple="false"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
</div>
<span class="msg-box n-right" for="c-cert_client"></span>
</div>
<div style="margin-top:5px;"><a href="https://pay.weixin.qq.com" target="_blank"><i class="fa fa-question-circle"></i> 如何获取微信支付API证书?</a></div>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>微信支付API证书key</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<div class="input-group">
<input id="c-cert_key" class="form-control" size="50" name="row[wechat][cert_key]" type="text" value="{$item.value.cert_key|htmlentities}" data-tip="可选, 仅在退款、红包等情况时需要用到">
<div class="input-group-addon no-border no-padding">
<span><button type="button" id="faupload-cert_key" class="btn btn-danger faupload" data-url="epay/upload" data-multipart='{"certname":"cert_key"}' data-mimetype="pem" data-input-id="c-cert_key" data-multiple="false"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
</div>
<span class="msg-box n-right" for="c-cert_key"></span>
</div>
<div style="margin-top:5px;"><a href="https://pay.weixin.qq.com" target="_blank"><i class="fa fa-question-circle"></i> 如何获取微信支付API证书?</a></div>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>记录日志</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
{:Form::radios('row[wechat][log]',['1'=>'开启','0'=>'关闭'],$item.value.log)}
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
{elseif $item.name=='alipay'}
<div class="tab-pane fade" id="alipay">
<table class="table table-striped table-config">
<tbody>
<tr>
<td width="15%">应用ID(app_id)</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[alipay][app_id]" value="{$item.value.app_id|default=''}" class="form-control" data-rule="" data-tip=""/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>支付模式</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
{:Form::radios('row[alipay][mode]',['normal'=>'正式环境','dev'=>'沙箱环境'],$item.value.mode??'normal')}
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>回调通知地址</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[alipay][notify_url]" value="{$item.value.notify_url|default=''}" class="form-control" data-rule="" data-tip="请勿随意修改,实际以逻辑代码中请求的为准"/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>支付跳转地址</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[alipay][return_url]" value="{$item.value.return_url|default=''}" class="form-control" data-rule="" data-tip="请勿随意修改,实际以逻辑代码中请求的为准"/>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>应用私钥(private_key)</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<input type="text" name="row[alipay][private_key]" value="{$item.value.private_key|default=''}" class="form-control" data-rule="" />
<div style="margin-top:5px;"><a href="https://opensupport.alipay.com/support/helpcenter/207/201602469554" target="_blank"><i class="fa fa-question-circle"></i> 如何获取应用私钥?</a></div>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>支付宝公钥路径(ali_public_key)</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<div class="input-group">
<input id="c-ali_public_key" class="form-control" size="50" name="row[alipay][ali_public_key]" type="text" value="{$item.value.ali_public_key|default=''|htmlentities}" placeholder="公钥请直接粘贴,公钥证书请点击右侧的上传">
<div class="input-group-addon no-border no-padding">
<span><button type="button" id="faupload-ali_public_key" class="btn btn-danger faupload" data-url="epay/upload" data-multipart='{"certname":"ali_public_key"}' data-mimetype="crt" data-input-id="c-ali_public_key" data-multiple="false"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
</div>
<span class="msg-box n-right" for="c-ali_public_key"></span>
</div>
<div style="margin-top:5px;"><a href="javascript:" data-toggle="tooltip" data-title="如果要使用转账、提现功能,则必须使用公钥证书"> <i class="fa fa-info-circle"></i> 公钥和公钥证书说明</a> <a href="https://opensupport.alipay.com/support/helpcenter/207/201602471154" target="_blank"><i class="fa fa-question-circle"></i> 如何获取支付宝公钥证书?</a></div>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>应用公钥证书路径(app_cert_public_key)</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<div class="input-group">
<input id="c-app_cert_public_key" class="form-control" size="50" name="row[alipay][app_cert_public_key]" type="text" value="{$item.value.app_cert_public_key|default=''|htmlentities}">
<div class="input-group-addon no-border no-padding">
<span><button type="button" id="faupload-app_cert_public_key" class="btn btn-danger faupload" data-url="epay/upload" data-multipart='{"certname":"app_cert_public_key"}' data-mimetype="crt" data-input-id="c-app_cert_public_key" data-multiple="false"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
</div>
<span class="msg-box n-right" for="c-app_cert_public_key"></span>
</div>
<div style="margin-top:5px;"><a href="https://opensupport.alipay.com/support/helpcenter/207/201602469554" target="_blank"><i class="fa fa-question-circle"></i> 如何获取应用公钥证书?</a></div>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>支付宝根证书路径(alipay_root_cert)</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
<div class="input-group">
<input id="c-alipay_root_cert" class="form-control" size="50" name="row[alipay][alipay_root_cert]" type="text" value="{$item.value.alipay_root_cert|default=''|htmlentities}">
<div class="input-group-addon no-border no-padding">
<span><button type="button" id="faupload-alipay_root_cert" class="btn btn-danger faupload" data-url="epay/upload" data-multipart='{"certname":"alipay_root_cert"}' data-mimetype="crt" data-input-id="c-alipay_root_cert" data-multiple="false"><i class="fa fa-upload"></i> {:__('Upload')}</button></span>
</div>
<span class="msg-box n-right" for="c-alipay_root_cert"></span>
</div>
<div style="margin-top:5px;"><a href="https://opensupport.alipay.com/support/helpcenter/207/201602469554" target="_blank"><i class="fa fa-question-circle"></i> 如何获取支付宝证书?</a></div>
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>记录日志</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
{:Form::radios('row[alipay][log]',['1'=>'开启','0'=>'关闭'],$item.value.log)}
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
<tr>
<td>PC端使用扫码支付</td>
<td>
<div class="row">
<div class="col-sm-8 col-xs-12">
{:Form::radios('row[alipay][scanpay]',['1'=>'开启','0'=>'关闭'],$item.value.scanpay??0)}
</div>
<div class="col-sm-4"></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
{/if}
{/foreach}
<div class="form-group layer-footer">
<label class="control-label col-xs-12 col-sm-2"></label>
<div class="col-xs-12 col-sm-8">
<button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
<button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
</div>
</div>
</div>
</div>
</div>
</form>
<script>
document.querySelectorAll("input[name='row[wechat][mode]']").forEach(function (i, j) {
i.addEventListener("click", function () {
document.querySelectorAll("#wechat table tr[data-type]").forEach(function (m, n) {
m.classList.add("hidden");
});
document.querySelectorAll("#wechat table tr[data-type='" + this.value + "']").forEach(function (m, n) {
m.classList.remove("hidden");
});
});
});
</script>
... ...
<?php
return [
[
'name' => 'wechat',
'title' => '微信',
'type' => 'array',
'content' => [],
'value' => [
'appid' => '',
'app_id' => 'wx239e35047fe80913',
'app_secret' => '9b478ad7e79e91084d94d2a6de9b3221',
'miniapp_id' => '',
'mch_id' => '1624790770',
'key' => 'chexiubaogonghzonghao20220519000',
'mode' => 'normal',
'sub_mch_id' => '',
'sub_appid' => '',
'sub_app_id' => '',
'sub_miniapp_id' => '',
'notify_url' => '/addons/epay/api/notifyx/type/wechat',
'cert_client' => '/addons/epay/certs/apiclient_cert.pem',
'cert_key' => '/addons/epay/certs/apiclient_key.pem',
'log' => '1',
],
'rule' => '',
'msg' => '',
'tip' => '微信参数配置',
'ok' => '',
'extend' => '',
],
[
'name' => 'alipay',
'title' => '支付宝',
'type' => 'array',
'content' => [],
'value' => [
'app_id' => '',
'mode' => 'normal',
'notify_url' => '/addons/epay/api/notifyx/type/alipay',
'return_url' => '/addons/epay/api/returnx/type/alipay',
'private_key' => '',
'ali_public_key' => '',
'app_cert_public_key' => '',
'alipay_root_cert' => '',
'log' => '1',
'scanpay' => '0',
],
'rule' => 'required',
'msg' => '',
'tip' => '支付宝参数配置',
'ok' => '',
'extend' => '',
],
[
'name' => '__tips__',
'title' => '温馨提示',
'type' => 'array',
'content' => [],
'value' => '请注意微信支付证书路径位于/addons/epay/certs目录下,请替换成你自己的证书<br>appid:APP的appid<br>app_id:公众号的appid<br>app_secret:公众号的secret<br>miniapp_id:小程序ID<br>mch_id:微信商户ID<br>key:微信商户支付的密钥',
'rule' => '',
'msg' => '',
'tip' => '微信参数配置',
'ok' => '',
'extend' => '',
],
];
... ...
<?php
namespace addons\epay\controller;
use addons\epay\library\QRCode;
use addons\epay\library\Service;
use addons\epay\library\Wechat;
use addons\third\model\Third;
use app\common\library\Auth;
use think\addons\Controller;
use think\Response;
use think\Session;
use Yansongda\Pay\Exceptions\GatewayException;
use Yansongda\Pay\Pay;
/**
* API接口控制器
*
* @package addons\epay\controller
*/
class Api extends Controller
{
protected $layout = 'default';
protected $config = [];
/**
* 默认方法
*/
public function index()
{
return;
}
/**
* 外部提交
*/
public function submit()
{
$this->request->filter('trim');
$out_trade_no = $this->request->request("out_trade_no");
$title = $this->request->request("title");
$amount = $this->request->request('amount');
$type = $this->request->request('type');
$method = $this->request->request('method', 'web');
$openid = $this->request->request('openid', '');
$auth_code = $this->request->request('auth_code', '');
$notifyurl = $this->request->request('notifyurl', '');
$returnurl = $this->request->request('returnurl', '');
if (!$amount || $amount < 0) {
$this->error("支付金额必须大于0");
}
if (!$type || !in_array($type, ['alipay', 'wechat'])) {
$this->error("支付类型错误");
}
$params = [
'type' => $type,
'out_trade_no' => $out_trade_no,
'title' => $title,
'amount' => $amount,
'method' => $method,
'openid' => $openid,
'auth_code' => $auth_code,
'notifyurl' => $notifyurl,
'returnurl' => $returnurl,
];
return Service::submitOrder($params);
}
/**
* 微信支付(公众号支付&PC扫码支付)
* @return string
*/
public function wechat()
{
$config = Service::getConfig('wechat');
$isWechat = stripos($this->request->server('HTTP_USER_AGENT'), 'MicroMessenger') !== false;
$isMobile = $this->request->isMobile();
$this->view->assign("isWechat", $isWechat);
$this->view->assign("isMobile", $isMobile);
//发起PC支付(Scan支付)(PC扫码模式)
if ($this->request->isAjax()) {
$pay = Pay::wechat($config);
$orderid = $this->request->post("orderid");
try {
$result = $pay->find($orderid);
if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
$this->success("", "", ['status' => $result['trade_state']]);
} else {
$this->error("查询失败");
}
} catch (GatewayException $e) {
$this->error("查询失败");
}
}
$orderData = Session::get("wechatorderdata");
if (!$orderData) {
$this->error("请求参数错误");
}
if ($isWechat) {
//发起公众号(jsapi支付),openid必须
//如果没有openid,则自动去获取openid
if (!isset($orderData['openid']) || !$orderData['openid']) {
$orderData['openid'] = Service::getOpenid();
}
$orderData['method'] = 'mp';
$type = 'jsapi';
$payData = Service::submitOrder($orderData);
if (!isset($payData['paySign'])) {
$this->error("创建订单失败,请返回重试", "");
}
} else {
$orderData['method'] = 'scan';
$type = 'pc';
$payData = Service::submitOrder($orderData);
if (!isset($payData['code_url'])) {
$this->error("创建订单失败,请返回重试", "");
}
}
$this->view->assign("orderData", $orderData);
$this->view->assign("payData", $payData);
$this->view->assign("type", $type);
$this->view->assign("title", "微信支付");
return $this->view->fetch();
}
/**
* 支付宝支付(PC扫码支付)
* @return string
*/
public function alipay()
{
$config = Service::getConfig('alipay');
$isWechat = stripos($this->request->server('HTTP_USER_AGENT'), 'MicroMessenger') !== false;
$isMobile = $this->request->isMobile();
$this->view->assign("isWechat", $isWechat);
$this->view->assign("isMobile", $isMobile);
if ($this->request->isAjax()) {
$orderid = $this->request->post("orderid");
$pay = Pay::alipay($config);
try {
$result = $pay->find($orderid);
if ($result['code'] == '10000' && $result['trade_status'] == 'TRADE_SUCCESS') {
$this->success("", "", ['status' => $result['trade_status']]);
} else {
$this->error("查询失败");
}
} catch (GatewayException $e) {
$this->error("查询失败");
}
}
//发起PC支付(Scan支付)(PC扫码模式)
$orderData = Session::get("alipayorderdata");
if (!$orderData) {
$this->error("请求参数错误");
}
$orderData['method'] = 'scan';
$payData = Service::submitOrder($orderData);
if (!isset($payData['qr_code'])) {
$this->error("创建订单失败,请返回重试");
}
$type = 'pc';
$this->view->assign("orderData", $orderData);
$this->view->assign("payData", $payData);
$this->view->assign("type", $type);
$this->view->assign("title", "支付宝支付");
return $this->view->fetch();
}
/**
* 支付成功回调
*/
public function notifyx()
{
$type = $this->request->param('type');
if (!Service::checkNotify($type)) {
echo '签名错误';
return;
}
//你可以在这里你的业务处理逻辑,比如处理你的订单状态、给会员加余额等等功能
//下面这句必须要执行,且在此之前不能有任何输出
echo "success";
return;
}
/**
* 支付成功返回
*/
public function returnx()
{
$type = $this->request->param('type');
if (Service::checkReturn($type)) {
echo '签名错误';
return;
}
//你可以在这里定义你的提示信息,但切记不可在此编写逻辑
$this->success("恭喜你!支付成功!", addon_url("epay/index/index"));
return;
}
/**
* 生成二维码
*/
public function qrcode()
{
$text = $this->request->get('text', 'hello world');
//如果有安装二维码插件,则调用插件的生成方法
if (class_exists("\addons\qrcode\library\Service") && get_addon_info('qrcode')['state']) {
$qrCode = \addons\qrcode\library\Service::qrcode(['text' => $text]);
$response = Response::create()->header("Content-Type", "image/png");
header('Content-Type: ' . $qrCode->getContentType());
$response->content($qrCode->writeString());
return $response;
} else {
$qr = QRCode::getMinimumQRCode($text);
$im = $qr->createImage(8, 5);
header("Content-type: image/png");
imagepng($im);
imagedestroy($im);
return;
}
}
}
... ...
<?php
namespace addons\epay\controller;
use addons\epay\library\Service;
use fast\Random;
use think\addons\Controller;
use Exception;
/**
* 微信支付宝插件首页
*
* 此控制器仅用于开发展示说明和体验,建议自行添加一个新的控制器进行处理返回和回调事件,同时删除此控制器文件
*
* Class Index
* @package addons\epay\controller
*/
class Index extends Controller
{
protected $layout = 'default';
protected $config = [];
public function _initialize()
{
parent::_initialize();
if (!config("app_debug")) {
$this->error("仅在开发环境下查看");
}
}
public function index()
{
$this->view->assign("title", "微信支付宝整合插件");
return $this->view->fetch();
}
/**
* 体验,仅供开发测试
*/
public function experience()
{
$amount = $this->request->request('amount');
$type = $this->request->request('type');
$method = $this->request->request('method');
if (!$amount || $amount < 0) {
$this->error("支付金额必须大于0");
}
if (!$type || !in_array($type, ['alipay', 'wechat'])) {
$this->error("支付类型不能为空");
}
//订单号
$out_trade_no = date("YmdHis") . mt_rand(100000, 999999);
//订单标题
$title = '测试订单';
//回调链接
$notifyurl = $this->request->root(true) . '/addons/epay/index/notifyx/paytype/' . $type;
$returnurl = $this->request->root(true) . '/addons/epay/index/returnx/paytype/' . $type . '/out_trade_no/' . $out_trade_no;
$response = Service::submitOrder($amount, $out_trade_no, $type, $title, $notifyurl, $returnurl, $method);
return $response;
}
/**
* 支付成功,仅供开发测试
*/
public function notifyx()
{
$paytype = $this->request->param('paytype');
$pay = Service::checkNotify($paytype);
if (!$pay) {
echo '签名错误';
return;
}
$data = $pay->verify();
try {
$payamount = $paytype == 'alipay' ? $data['total_amount'] : $data['total_fee'] / 100;
$out_trade_no = $data['out_trade_no'];
//你可以在此编写订单逻辑
} catch (Exception $e) {
}
echo $pay->success();
}
/**
* 支付返回,仅供开发测试
*/
public function returnx()
{
$paytype = $this->request->param('paytype');
$out_trade_no = $this->request->param('out_trade_no');
$pay = Service::checkReturn($paytype);
if (!$pay) {
$this->error('签名错误', '');
}
//你可以在这里通过out_trade_no去验证订单状态
//但是不可以在此编写订单逻辑!!!
$this->success("请返回网站查看支付结果", addon_url("epay/index/index"));
}
}
... ...
name = epay
title = 微信支付宝整合
intro = 可用于快速整合微信、支付宝支付功能
author = FastAdmin
website = https://www.fastadmin.net
version = 1.2.5
state = 1
url = /addons/epay
license = regular
licenseto = 10789
... ...
<?php
namespace addons\epay\library;
class Collection extends \Yansongda\Supports\Collection
{
/**
* 创建 Collection 实例
* @access public
* @param array $items 数据
* @return static
*/
public static function make($items = [])
{
return new static($items);
}
}
... ...
<?php
namespace addons\epay\library;
use think\Exception;
class OrderException extends Exception
{
public function __construct($message = "", $code = 0, $data = [])
{
$this->message = $message;
$this->code = $code;
$this->data = $data;
}
}
... ...
<?php
namespace addons\epay\library;
//---------------------------------------------------------------
// QRCode for PHP5
//
// Copyright (c) 2009 Kazuhiko Arase
//
// URL: http://www.d-project.com/
//
// Licensed under the MIT license:
// http://www.opensource.org/licenses/mit-license.php
//
// The word "QR Code" is registered trademark of
// DENSO WAVE INCORPORATED
// http://www.denso-wave.com/qrcode/faqpatent-e.html
//
//---------------------------------------------------------------------
//---------------------------------------------------------------
// QRCode
//---------------------------------------------------------------
define("QR_PAD0", 0xEC);
define("QR_PAD1", 0x11);
class QRCode
{
var $typeNumber;
var $modules;
var $moduleCount;
var $errorCorrectLevel;
var $qrDataList;
function __construct()
{
$this->typeNumber = 1;
$this->errorCorrectLevel = QR_ERROR_CORRECT_LEVEL_H;
$this->qrDataList = array();
}
function getTypeNumber()
{
return $this->typeNumber;
}
function setTypeNumber($typeNumber)
{
$this->typeNumber = $typeNumber;
}
function getErrorCorrectLevel()
{
return $this->errorCorrectLevel;
}
function setErrorCorrectLevel($errorCorrectLevel)
{
$this->errorCorrectLevel = $errorCorrectLevel;
}
function addData($data, $mode = 0)
{
if ($mode == 0) {
$mode = QRUtil::getMode($data);
}
switch ($mode) {
case QR_MODE_NUMBER :
$this->addDataImpl(new QRNumber($data));
break;
case QR_MODE_ALPHA_NUM :
$this->addDataImpl(new QRAlphaNum($data));
break;
case QR_MODE_8BIT_BYTE :
$this->addDataImpl(new QR8BitByte($data));
break;
case QR_MODE_KANJI :
$this->addDataImpl(new QRKanji($data));
break;
default :
trigger_error("mode:$mode", E_USER_ERROR);
}
}
function clearData()
{
$this->qrDataList = array();
}
function addDataImpl($qrData)
{
$this->qrDataList[] = $qrData;
}
function getDataCount()
{
return count($this->qrDataList);
}
function getData($index)
{
return $this->qrDataList[$index];
}
function isDark($row, $col)
{
if ($this->modules[$row][$col] !== null) {
return $this->modules[$row][$col];
} else {
return false;
}
}
function getModuleCount()
{
return $this->moduleCount;
}
// used for converting fg/bg colors (e.g. #0000ff = 0x0000FF)
// added 2015.07.27 ~ DoktorJ
function hex2rgb($hex = 0x0)
{
return array(
'r' => floor($hex / 65536),
'g' => floor($hex / 256) % 256,
'b' => $hex % 256
);
}
function make()
{
$this->makeImpl(false, $this->getBestMaskPattern());
}
function getBestMaskPattern()
{
$minLostPoint = 0;
$pattern = 0;
for ($i = 0; $i < 8; $i++) {
$this->makeImpl(true, $i);
$lostPoint = QRUtil::getLostPoint($this);
if ($i == 0 || $minLostPoint > $lostPoint) {
$minLostPoint = $lostPoint;
$pattern = $i;
}
}
return $pattern;
}
function createNullArray($length)
{
$nullArray = array();
for ($i = 0; $i < $length; $i++) {
$nullArray[] = null;
}
return $nullArray;
}
function makeImpl($test, $maskPattern)
{
$this->moduleCount = $this->typeNumber * 4 + 17;
$this->modules = array();
for ($i = 0; $i < $this->moduleCount; $i++) {
$this->modules[] = QRCode::createNullArray($this->moduleCount);
}
$this->setupPositionProbePattern(0, 0);
$this->setupPositionProbePattern($this->moduleCount - 7, 0);
$this->setupPositionProbePattern(0, $this->moduleCount - 7);
$this->setupPositionAdjustPattern();
$this->setupTimingPattern();
$this->setupTypeInfo($test, $maskPattern);
if ($this->typeNumber >= 7) {
$this->setupTypeNumber($test);
}
$dataArray = $this->qrDataList;
$data = QRCode::createData($this->typeNumber, $this->errorCorrectLevel, $dataArray);
$this->mapData($data, $maskPattern);
}
function mapData(&$data, $maskPattern)
{
$inc = -1;
$row = $this->moduleCount - 1;
$bitIndex = 7;
$byteIndex = 0;
for ($col = $this->moduleCount - 1; $col > 0; $col -= 2) {
if ($col == 6) $col--;
while (true) {
for ($c = 0; $c < 2; $c++) {
if ($this->modules[$row][$col - $c] === null) {
$dark = false;
if ($byteIndex < count($data)) {
$dark = ((($data[$byteIndex] >> $bitIndex) & 1) == 1);
}
if (QRUtil::getMask($maskPattern, $row, $col - $c)) {
$dark = !$dark;
}
$this->modules[$row][$col - $c] = $dark;
$bitIndex--;
if ($bitIndex == -1) {
$byteIndex++;
$bitIndex = 7;
}
}
}
$row += $inc;
if ($row < 0 || $this->moduleCount <= $row) {
$row -= $inc;
$inc = -$inc;
break;
}
}
}
}
function setupPositionAdjustPattern()
{
$pos = QRUtil::getPatternPosition($this->typeNumber);
for ($i = 0; $i < count($pos); $i++) {
for ($j = 0; $j < count($pos); $j++) {
$row = $pos[$i];
$col = $pos[$j];
if ($this->modules[$row][$col] !== null) {
continue;
}
for ($r = -2; $r <= 2; $r++) {
for ($c = -2; $c <= 2; $c++) {
$this->modules[$row + $r][$col + $c] =
$r == -2 || $r == 2 || $c == -2 || $c == 2 || ($r == 0 && $c == 0);
}
}
}
}
}
function setupPositionProbePattern($row, $col)
{
for ($r = -1; $r <= 7; $r++) {
for ($c = -1; $c <= 7; $c++) {
if ($row + $r <= -1 || $this->moduleCount <= $row + $r
|| $col + $c <= -1 || $this->moduleCount <= $col + $c) {
continue;
}
$this->modules[$row + $r][$col + $c] =
(0 <= $r && $r <= 6 && ($c == 0 || $c == 6))
|| (0 <= $c && $c <= 6 && ($r == 0 || $r == 6))
|| (2 <= $r && $r <= 4 && 2 <= $c && $c <= 4);
}
}
}
function setupTimingPattern()
{
for ($i = 8; $i < $this->moduleCount - 8; $i++) {
if ($this->modules[$i][6] !== null || $this->modules[6][$i] !== null) {
continue;
}
$this->modules[$i][6] = ($i % 2 == 0);
$this->modules[6][$i] = ($i % 2 == 0);
}
}
function setupTypeNumber($test)
{
$bits = QRUtil::getBCHTypeNumber($this->typeNumber);
for ($i = 0; $i < 18; $i++) {
$mod = (!$test && (($bits >> $i) & 1) == 1);
$this->modules[(int)floor($i / 3)][$i % 3 + $this->moduleCount - 8 - 3] = $mod;
$this->modules[$i % 3 + $this->moduleCount - 8 - 3][floor($i / 3)] = $mod;
}
}
function setupTypeInfo($test, $maskPattern)
{
$data = ($this->errorCorrectLevel << 3) | $maskPattern;
$bits = QRUtil::getBCHTypeInfo($data);
for ($i = 0; $i < 15; $i++) {
$mod = (!$test && (($bits >> $i) & 1) == 1);
if ($i < 6) {
$this->modules[$i][8] = $mod;
} else if ($i < 8) {
$this->modules[$i + 1][8] = $mod;
} else {
$this->modules[$this->moduleCount - 15 + $i][8] = $mod;
}
if ($i < 8) {
$this->modules[8][$this->moduleCount - $i - 1] = $mod;
} else if ($i < 9) {
$this->modules[8][15 - $i - 1 + 1] = $mod;
} else {
$this->modules[8][15 - $i - 1] = $mod;
}
}
$this->modules[$this->moduleCount - 8][8] = !$test;
}
function createData($typeNumber, $errorCorrectLevel, $dataArray)
{
$rsBlocks = QRRSBlock::getRSBlocks($typeNumber, $errorCorrectLevel);
$buffer = new QRBitBuffer();
for ($i = 0; $i < count($dataArray); $i++) {
/** @var \QRData $data */
$data = $dataArray[$i];
$buffer->put($data->getMode(), 4);
$buffer->put($data->getLength(), $data->getLengthInBits($typeNumber));
$data->write($buffer);
}
$totalDataCount = 0;
for ($i = 0; $i < count($rsBlocks); $i++) {
$totalDataCount += $rsBlocks[$i]->getDataCount();
}
if ($buffer->getLengthInBits() > $totalDataCount * 8) {
trigger_error("code length overflow. ("
. $buffer->getLengthInBits()
. ">"
. $totalDataCount * 8
. ")", E_USER_ERROR);
}
// end code.
if ($buffer->getLengthInBits() + 4 <= $totalDataCount * 8) {
$buffer->put(0, 4);
}
// padding
while ($buffer->getLengthInBits() % 8 != 0) {
$buffer->putBit(false);
}
// padding
while (true) {
if ($buffer->getLengthInBits() >= $totalDataCount * 8) {
break;
}
$buffer->put(QR_PAD0, 8);
if ($buffer->getLengthInBits() >= $totalDataCount * 8) {
break;
}
$buffer->put(QR_PAD1, 8);
}
return QRCode::createBytes($buffer, $rsBlocks);
}
/**
* @param \QRBitBuffer $buffer
* @param \QRRSBlock[] $rsBlocks
*
* @return array
*/
function createBytes(&$buffer, &$rsBlocks)
{
$offset = 0;
$maxDcCount = 0;
$maxEcCount = 0;
$dcdata = QRCode::createNullArray(count($rsBlocks));
$ecdata = QRCode::createNullArray(count($rsBlocks));
$rsBlockCount = count($rsBlocks);
for ($r = 0; $r < $rsBlockCount; $r++) {
$dcCount = $rsBlocks[$r]->getDataCount();
$ecCount = $rsBlocks[$r]->getTotalCount() - $dcCount;
$maxDcCount = max($maxDcCount, $dcCount);
$maxEcCount = max($maxEcCount, $ecCount);
$dcdata[$r] = QRCode::createNullArray($dcCount);
$dcDataCount = count($dcdata[$r]);
for ($i = 0; $i < $dcDataCount; $i++) {
$bdata = $buffer->getBuffer();
$dcdata[$r][$i] = 0xff & $bdata[$i + $offset];
}
$offset += $dcCount;
$rsPoly = QRUtil::getErrorCorrectPolynomial($ecCount);
$rawPoly = new QRPolynomial($dcdata[$r], $rsPoly->getLength() - 1);
$modPoly = $rawPoly->mod($rsPoly);
$ecdata[$r] = QRCode::createNullArray($rsPoly->getLength() - 1);
$ecDataCount = count($ecdata[$r]);
for ($i = 0; $i < $ecDataCount; $i++) {
$modIndex = $i + $modPoly->getLength() - count($ecdata[$r]);
$ecdata[$r][$i] = ($modIndex >= 0) ? $modPoly->get($modIndex) : 0;
}
}
$totalCodeCount = 0;
for ($i = 0; $i < $rsBlockCount; $i++) {
$totalCodeCount += $rsBlocks[$i]->getTotalCount();
}
$data = QRCode::createNullArray($totalCodeCount);
$index = 0;
for ($i = 0; $i < $maxDcCount; $i++) {
for ($r = 0; $r < $rsBlockCount; $r++) {
if ($i < count($dcdata[$r])) {
$data[$index++] = $dcdata[$r][$i];
}
}
}
for ($i = 0; $i < $maxEcCount; $i++) {
for ($r = 0; $r < $rsBlockCount; $r++) {
if ($i < count($ecdata[$r])) {
$data[$index++] = $ecdata[$r][$i];
}
}
}
return $data;
}
static function getMinimumQRCode($data, $errorCorrectLevel = QR_ERROR_CORRECT_LEVEL_L)
{
$mode = QRUtil::getMode($data);
$qr = new QRCode();
$qr->setErrorCorrectLevel($errorCorrectLevel);
$qr->addData($data, $mode);
$qrData = $qr->getData(0);
$length = $qrData->getLength();
for ($typeNumber = 1; $typeNumber <= 40; $typeNumber++) {
if ($length <= QRUtil::getMaxLength($typeNumber, $mode, $errorCorrectLevel)) {
$qr->setTypeNumber($typeNumber);
break;
}
}
$qr->make();
return $qr;
}
// added $fg (foreground), $bg (background), and $bgtrans (use transparent bg) parameters
// also added some simple error checking on parameters
// updated 2015.07.27 ~ DoktorJ
function createImage($size = 2, $margin = 2, $fg = 0x000000, $bg = 0xFFFFFF, $bgtrans = false)
{
// size/margin EC
if (!is_numeric($size)) $size = 2;
if (!is_numeric($margin)) $margin = 2;
if ($size < 1) $size = 1;
if ($margin < 0) $margin = 0;
$image_size = $this->getModuleCount() * $size + $margin * 2;
$image = imagecreatetruecolor($image_size, $image_size);
// fg/bg EC
if ($fg < 0 || $fg > 0xFFFFFF) $fg = 0x0;
if ($bg < 0 || $bg > 0xFFFFFF) $bg = 0xFFFFFF;
// convert hexadecimal RGB to arrays for imagecolorallocate
$fgrgb = $this->hex2rgb($fg);
$bgrgb = $this->hex2rgb($bg);
// replace $black and $white with $fgc and $bgc
$fgc = imagecolorallocate($image, $fgrgb['r'], $fgrgb['g'], $fgrgb['b']);
$bgc = imagecolorallocate($image, $bgrgb['r'], $bgrgb['g'], $bgrgb['b']);
if ($bgtrans) imagecolortransparent($image, $bgc);
// update $white to $bgc
imagefilledrectangle($image, 0, 0, $image_size, $image_size, $bgc);
for ($r = 0; $r < $this->getModuleCount(); $r++) {
for ($c = 0; $c < $this->getModuleCount(); $c++) {
if ($this->isDark($r, $c)) {
// update $black to $fgc
imagefilledrectangle($image,
$margin + $c * $size,
$margin + $r * $size,
$margin + ($c + 1) * $size - 1,
$margin + ($r + 1) * $size - 1,
$fgc);
}
}
}
return $image;
}
function printHTML($size = "2px")
{
$style = "border-style:none;border-collapse:collapse;margin:0px;padding:0px;";
print("<table style='$style'>");
for ($r = 0; $r < $this->getModuleCount(); $r++) {
print("<tr style='$style'>");
for ($c = 0; $c < $this->getModuleCount(); $c++) {
$color = $this->isDark($r, $c) ? "#000000" : "#ffffff";
print("<td style='$style;width:$size;height:$size;background-color:$color'></td>");
}
print("</tr>");
}
print("</table>");
}
}
//---------------------------------------------------------------
// QRUtil
//---------------------------------------------------------------
define("QR_G15", (1 << 10) | (1 << 8) | (1 << 5)
| (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0));
define("QR_G18", (1 << 12) | (1 << 11) | (1 << 10)
| (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0));
define("QR_G15_MASK", (1 << 14) | (1 << 12) | (1 << 10)
| (1 << 4) | (1 << 1));
class QRUtil
{
static $QR_MAX_LENGTH = array(
array(array(41, 25, 17, 10), array(34, 20, 14, 8), array(27, 16, 11, 7), array(17, 10, 7, 4)),
array(array(77, 47, 32, 20), array(63, 38, 26, 16), array(48, 29, 20, 12), array(34, 20, 14, 8)),
array(array(127, 77, 53, 32), array(101, 61, 42, 26), array(77, 47, 32, 20), array(58, 35, 24, 15)),
array(array(187, 114, 78, 48), array(149, 90, 62, 38), array(111, 67, 46, 28), array(82, 50, 34, 21)),
array(array(255, 154, 106, 65), array(202, 122, 84, 52), array(144, 87, 60, 37), array(106, 64, 44, 27)),
array(array(322, 195, 134, 82), array(255, 154, 106, 65), array(178, 108, 74, 45), array(139, 84, 58, 36)),
array(array(370, 224, 154, 95), array(293, 178, 122, 75), array(207, 125, 86, 53), array(154, 93, 64, 39)),
array(array(461, 279, 192, 118), array(365, 221, 152, 93), array(259, 157, 108, 66), array(202, 122, 84, 52)),
array(array(552, 335, 230, 141), array(432, 262, 180, 111), array(312, 189, 130, 80), array(235, 143, 98, 60)),
array(array(652, 395, 271, 167), array(513, 311, 213, 131), array(364, 221, 151, 93), array(288, 174, 119, 74))
);
static $QR_PATTERN_POSITION_TABLE = array(
array(),
array(6, 18),
array(6, 22),
array(6, 26),
array(6, 30),
array(6, 34),
array(6, 22, 38),
array(6, 24, 42),
array(6, 26, 46),
array(6, 28, 50),
array(6, 30, 54),
array(6, 32, 58),
array(6, 34, 62),
array(6, 26, 46, 66),
array(6, 26, 48, 70),
array(6, 26, 50, 74),
array(6, 30, 54, 78),
array(6, 30, 56, 82),
array(6, 30, 58, 86),
array(6, 34, 62, 90),
array(6, 28, 50, 72, 94),
array(6, 26, 50, 74, 98),
array(6, 30, 54, 78, 102),
array(6, 28, 54, 80, 106),
array(6, 32, 58, 84, 110),
array(6, 30, 58, 86, 114),
array(6, 34, 62, 90, 118),
array(6, 26, 50, 74, 98, 122),
array(6, 30, 54, 78, 102, 126),
array(6, 26, 52, 78, 104, 130),
array(6, 30, 56, 82, 108, 134),
array(6, 34, 60, 86, 112, 138),
array(6, 30, 58, 86, 114, 142),
array(6, 34, 62, 90, 118, 146),
array(6, 30, 54, 78, 102, 126, 150),
array(6, 24, 50, 76, 102, 128, 154),
array(6, 28, 54, 80, 106, 132, 158),
array(6, 32, 58, 84, 110, 136, 162),
array(6, 26, 54, 82, 110, 138, 166),
array(6, 30, 58, 86, 114, 142, 170)
);
static function getPatternPosition($typeNumber)
{
return self::$QR_PATTERN_POSITION_TABLE[$typeNumber - 1];
}
static function getMaxLength($typeNumber, $mode, $errorCorrectLevel)
{
$t = $typeNumber - 1;
$e = 0;
$m = 0;
switch ($errorCorrectLevel) {
case QR_ERROR_CORRECT_LEVEL_L :
$e = 0;
break;
case QR_ERROR_CORRECT_LEVEL_M :
$e = 1;
break;
case QR_ERROR_CORRECT_LEVEL_Q :
$e = 2;
break;
case QR_ERROR_CORRECT_LEVEL_H :
$e = 3;
break;
default :
trigger_error("e:$errorCorrectLevel", E_USER_ERROR);
}
switch ($mode) {
case QR_MODE_NUMBER :
$m = 0;
break;
case QR_MODE_ALPHA_NUM :
$m = 1;
break;
case QR_MODE_8BIT_BYTE :
$m = 2;
break;
case QR_MODE_KANJI :
$m = 3;
break;
default :
trigger_error("m:$mode", E_USER_ERROR);
}
return self::$QR_MAX_LENGTH[$t][$e][$m];
}
static function getErrorCorrectPolynomial($errorCorrectLength)
{
$a = new QRPolynomial(array(1));
for ($i = 0; $i < $errorCorrectLength; $i++) {
$a = $a->multiply(new QRPolynomial(array(1, QRMath::gexp($i))));
}
return $a;
}
static function getMask($maskPattern, $i, $j)
{
switch ($maskPattern) {
case QR_MASK_PATTERN000 :
return ($i + $j) % 2 == 0;
case QR_MASK_PATTERN001 :
return $i % 2 == 0;
case QR_MASK_PATTERN010 :
return $j % 3 == 0;
case QR_MASK_PATTERN011 :
return ($i + $j) % 3 == 0;
case QR_MASK_PATTERN100 :
return (floor($i / 2) + floor($j / 3)) % 2 == 0;
case QR_MASK_PATTERN101 :
return ($i * $j) % 2 + ($i * $j) % 3 == 0;
case QR_MASK_PATTERN110 :
return (($i * $j) % 2 + ($i * $j) % 3) % 2 == 0;
case QR_MASK_PATTERN111 :
return (($i * $j) % 3 + ($i + $j) % 2) % 2 == 0;
default :
trigger_error("mask:$maskPattern", E_USER_ERROR);
}
}
/**
* @param \QRCode $qrCode
*
* @return float|int
*/
static function getLostPoint($qrCode)
{
$moduleCount = $qrCode->getModuleCount();
$lostPoint = 0;
// LEVEL1
for ($row = 0; $row < $moduleCount; $row++) {
for ($col = 0; $col < $moduleCount; $col++) {
$sameCount = 0;
$dark = $qrCode->isDark($row, $col);
for ($r = -1; $r <= 1; $r++) {
if ($row + $r < 0 || $moduleCount <= $row + $r) {
continue;
}
for ($c = -1; $c <= 1; $c++) {
if (($col + $c < 0 || $moduleCount <= $col + $c) || ($r == 0 && $c == 0)) {
continue;
}
if ($dark == $qrCode->isDark($row + $r, $col + $c)) {
$sameCount++;
}
}
}
if ($sameCount > 5) {
$lostPoint += (3 + $sameCount - 5);
}
}
}
// LEVEL2
for ($row = 0; $row < $moduleCount - 1; $row++) {
for ($col = 0; $col < $moduleCount - 1; $col++) {
$count = 0;
if ($qrCode->isDark($row, $col)) $count++;
if ($qrCode->isDark($row + 1, $col)) $count++;
if ($qrCode->isDark($row, $col + 1)) $count++;
if ($qrCode->isDark($row + 1, $col + 1)) $count++;
if ($count == 0 || $count == 4) {
$lostPoint += 3;
}
}
}
// LEVEL3
for ($row = 0; $row < $moduleCount; $row++) {
for ($col = 0; $col < $moduleCount - 6; $col++) {
if ($qrCode->isDark($row, $col)
&& !$qrCode->isDark($row, $col + 1)
&& $qrCode->isDark($row, $col + 2)
&& $qrCode->isDark($row, $col + 3)
&& $qrCode->isDark($row, $col + 4)
&& !$qrCode->isDark($row, $col + 5)
&& $qrCode->isDark($row, $col + 6)) {
$lostPoint += 40;
}
}
}
for ($col = 0; $col < $moduleCount; $col++) {
for ($row = 0; $row < $moduleCount - 6; $row++) {
if ($qrCode->isDark($row, $col)
&& !$qrCode->isDark($row + 1, $col)
&& $qrCode->isDark($row + 2, $col)
&& $qrCode->isDark($row + 3, $col)
&& $qrCode->isDark($row + 4, $col)
&& !$qrCode->isDark($row + 5, $col)
&& $qrCode->isDark($row + 6, $col)) {
$lostPoint += 40;
}
}
}
// LEVEL4
$darkCount = 0;
for ($col = 0; $col < $moduleCount; $col++) {
for ($row = 0; $row < $moduleCount; $row++) {
if ($qrCode->isDark($row, $col)) {
$darkCount++;
}
}
}
$ratio = abs(100 * $darkCount / $moduleCount / $moduleCount - 50) / 5;
$lostPoint += $ratio * 10;
return $lostPoint;
}
static function getMode($s)
{
if (QRUtil::isAlphaNum($s)) {
if (QRUtil::isNumber($s)) {
return QR_MODE_NUMBER;
}
return QR_MODE_ALPHA_NUM;
} else if (QRUtil::isKanji($s)) {
return QR_MODE_KANJI;
} else {
return QR_MODE_8BIT_BYTE;
}
}
static function isNumber($s)
{
for ($i = 0; $i < strlen($s); $i++) {
$c = ord($s[$i]);
if (!(QRUtil::toCharCode('0') <= $c && $c <= QRUtil::toCharCode('9'))) {
return false;
}
}
return true;
}
static function isAlphaNum($s)
{
for ($i = 0; $i < strlen($s); $i++) {
$c = ord($s[$i]);
if (!(QRUtil::toCharCode('0') <= $c && $c <= QRUtil::toCharCode('9'))
&& !(QRUtil::toCharCode('A') <= $c && $c <= QRUtil::toCharCode('Z'))
&& strpos(" $%*+-./:", $s[$i]) === false) {
return false;
}
}
return true;
}
static function isKanji($s)
{
$data = $s;
$i = 0;
while ($i + 1 < strlen($data)) {
$c = ((0xff & ord($data[$i])) << 8) | (0xff & ord($data[$i + 1]));
if (!(0x8140 <= $c && $c <= 0x9FFC) && !(0xE040 <= $c && $c <= 0xEBBF)) {
return false;
}
$i += 2;
}
if ($i < strlen($data)) {
return false;
}
return true;
}
static function toCharCode($s)
{
return ord($s[0]);
}
static function getBCHTypeInfo($data)
{
$d = $data << 10;
while (QRUtil::getBCHDigit($d) - QRUtil::getBCHDigit(QR_G15) >= 0) {
$d ^= (QR_G15 << (QRUtil::getBCHDigit($d) - QRUtil::getBCHDigit(QR_G15)));
}
return (($data << 10) | $d) ^ QR_G15_MASK;
}
static function getBCHTypeNumber($data)
{
$d = $data << 12;
while (QRUtil::getBCHDigit($d) - QRUtil::getBCHDigit(QR_G18) >= 0) {
$d ^= (QR_G18 << (QRUtil::getBCHDigit($d) - QRUtil::getBCHDigit(QR_G18)));
}
return ($data << 12) | $d;
}
static function getBCHDigit($data)
{
$digit = 0;
while ($data != 0) {
$digit++;
$data >>= 1;
}
return $digit;
}
}
//---------------------------------------------------------------
// QRRSBlock
//---------------------------------------------------------------
class QRRSBlock
{
var $totalCount;
var $dataCount;
static $QR_RS_BLOCK_TABLE = array(
// L
// M
// Q
// H
// 1
array(1, 26, 19),
array(1, 26, 16),
array(1, 26, 13),
array(1, 26, 9),
// 2
array(1, 44, 34),
array(1, 44, 28),
array(1, 44, 22),
array(1, 44, 16),
// 3
array(1, 70, 55),
array(1, 70, 44),
array(2, 35, 17),
array(2, 35, 13),
// 4
array(1, 100, 80),
array(2, 50, 32),
array(2, 50, 24),
array(4, 25, 9),
// 5
array(1, 134, 108),
array(2, 67, 43),
array(2, 33, 15, 2, 34, 16),
array(2, 33, 11, 2, 34, 12),
// 6
array(2, 86, 68),
array(4, 43, 27),
array(4, 43, 19),
array(4, 43, 15),
// 7
array(2, 98, 78),
array(4, 49, 31),
array(2, 32, 14, 4, 33, 15),
array(4, 39, 13, 1, 40, 14),
// 8
array(2, 121, 97),
array(2, 60, 38, 2, 61, 39),
array(4, 40, 18, 2, 41, 19),
array(4, 40, 14, 2, 41, 15),
// 9
array(2, 146, 116),
array(3, 58, 36, 2, 59, 37),
array(4, 36, 16, 4, 37, 17),
array(4, 36, 12, 4, 37, 13),
// 10
array(2, 86, 68, 2, 87, 69),
array(4, 69, 43, 1, 70, 44),
array(6, 43, 19, 2, 44, 20),
array(6, 43, 15, 2, 44, 16),
// 11
array(4, 101, 81),
array(1, 80, 50, 4, 81, 51),
array(4, 50, 22, 4, 51, 23),
array(3, 36, 12, 8, 37, 13),
// 12
array(2, 116, 92, 2, 117, 93),
array(6, 58, 36, 2, 59, 37),
array(4, 46, 20, 6, 47, 21),
array(7, 42, 14, 4, 43, 15),
// 13
array(4, 133, 107),
array(8, 59, 37, 1, 60, 38),
array(8, 44, 20, 4, 45, 21),
array(12, 33, 11, 4, 34, 12),
// 14
array(3, 145, 115, 1, 146, 116),
array(4, 64, 40, 5, 65, 41),
array(11, 36, 16, 5, 37, 17),
array(11, 36, 12, 5, 37, 13),
// 15
array(5, 109, 87, 1, 110, 88),
array(5, 65, 41, 5, 66, 42),
array(5, 54, 24, 7, 55, 25),
array(11, 36, 12, 7, 37, 13),
// 16
array(5, 122, 98, 1, 123, 99),
array(7, 73, 45, 3, 74, 46),
array(15, 43, 19, 2, 44, 20),
array(3, 45, 15, 13, 46, 16),
// 17
array(1, 135, 107, 5, 136, 108),
array(10, 74, 46, 1, 75, 47),
array(1, 50, 22, 15, 51, 23),
array(2, 42, 14, 17, 43, 15),
// 18
array(5, 150, 120, 1, 151, 121),
array(9, 69, 43, 4, 70, 44),
array(17, 50, 22, 1, 51, 23),
array(2, 42, 14, 19, 43, 15),
// 19
array(3, 141, 113, 4, 142, 114),
array(3, 70, 44, 11, 71, 45),
array(17, 47, 21, 4, 48, 22),
array(9, 39, 13, 16, 40, 14),
// 20
array(3, 135, 107, 5, 136, 108),
array(3, 67, 41, 13, 68, 42),
array(15, 54, 24, 5, 55, 25),
array(15, 43, 15, 10, 44, 16),
// 21
array(4, 144, 116, 4, 145, 117),
array(17, 68, 42),
array(17, 50, 22, 6, 51, 23),
array(19, 46, 16, 6, 47, 17),
// 22
array(2, 139, 111, 7, 140, 112),
array(17, 74, 46),
array(7, 54, 24, 16, 55, 25),
array(34, 37, 13),
// 23
array(4, 151, 121, 5, 152, 122),
array(4, 75, 47, 14, 76, 48),
array(11, 54, 24, 14, 55, 25),
array(16, 45, 15, 14, 46, 16),
// 24
array(6, 147, 117, 4, 148, 118),
array(6, 73, 45, 14, 74, 46),
array(11, 54, 24, 16, 55, 25),
array(30, 46, 16, 2, 47, 17),
// 25
array(8, 132, 106, 4, 133, 107),
array(8, 75, 47, 13, 76, 48),
array(7, 54, 24, 22, 55, 25),
array(22, 45, 15, 13, 46, 16),
// 26
array(10, 142, 114, 2, 143, 115),
array(19, 74, 46, 4, 75, 47),
array(28, 50, 22, 6, 51, 23),
array(33, 46, 16, 4, 47, 17),
// 27
array(8, 152, 122, 4, 153, 123),
array(22, 73, 45, 3, 74, 46),
array(8, 53, 23, 26, 54, 24),
array(12, 45, 15, 28, 46, 16),
// 28
array(3, 147, 117, 10, 148, 118),
array(3, 73, 45, 23, 74, 46),
array(4, 54, 24, 31, 55, 25),
array(11, 45, 15, 31, 46, 16),
// 29
array(7, 146, 116, 7, 147, 117),
array(21, 73, 45, 7, 74, 46),
array(1, 53, 23, 37, 54, 24),
array(19, 45, 15, 26, 46, 16),
// 30
array(5, 145, 115, 10, 146, 116),
array(19, 75, 47, 10, 76, 48),
array(15, 54, 24, 25, 55, 25),
array(23, 45, 15, 25, 46, 16),
// 31
array(13, 145, 115, 3, 146, 116),
array(2, 74, 46, 29, 75, 47),
array(42, 54, 24, 1, 55, 25),
array(23, 45, 15, 28, 46, 16),
// 32
array(17, 145, 115),
array(10, 74, 46, 23, 75, 47),
array(10, 54, 24, 35, 55, 25),
array(19, 45, 15, 35, 46, 16),
// 33
array(17, 145, 115, 1, 146, 116),
array(14, 74, 46, 21, 75, 47),
array(29, 54, 24, 19, 55, 25),
array(11, 45, 15, 46, 46, 16),
// 34
array(13, 145, 115, 6, 146, 116),
array(14, 74, 46, 23, 75, 47),
array(44, 54, 24, 7, 55, 25),
array(59, 46, 16, 1, 47, 17),
// 35
array(12, 151, 121, 7, 152, 122),
array(12, 75, 47, 26, 76, 48),
array(39, 54, 24, 14, 55, 25),
array(22, 45, 15, 41, 46, 16),
// 36
array(6, 151, 121, 14, 152, 122),
array(6, 75, 47, 34, 76, 48),
array(46, 54, 24, 10, 55, 25),
array(2, 45, 15, 64, 46, 16),
// 37
array(17, 152, 122, 4, 153, 123),
array(29, 74, 46, 14, 75, 47),
array(49, 54, 24, 10, 55, 25),
array(24, 45, 15, 46, 46, 16),
// 38
array(4, 152, 122, 18, 153, 123),
array(13, 74, 46, 32, 75, 47),
array(48, 54, 24, 14, 55, 25),
array(42, 45, 15, 32, 46, 16),
// 39
array(20, 147, 117, 4, 148, 118),
array(40, 75, 47, 7, 76, 48),
array(43, 54, 24, 22, 55, 25),
array(10, 45, 15, 67, 46, 16),
// 40
array(19, 148, 118, 6, 149, 119),
array(18, 75, 47, 31, 76, 48),
array(34, 54, 24, 34, 55, 25),
array(20, 45, 15, 61, 46, 16)
);
function __construct($totalCount, $dataCount)
{
$this->totalCount = $totalCount;
$this->dataCount = $dataCount;
}
function getDataCount()
{
return $this->dataCount;
}
function getTotalCount()
{
return $this->totalCount;
}
static function getRSBlocks($typeNumber, $errorCorrectLevel)
{
$rsBlock = QRRSBlock::getRsBlockTable($typeNumber, $errorCorrectLevel);
$length = count($rsBlock) / 3;
$list = array();
for ($i = 0; $i < $length; $i++) {
$count = $rsBlock[$i * 3 + 0];
$totalCount = $rsBlock[$i * 3 + 1];
$dataCount = $rsBlock[$i * 3 + 2];
for ($j = 0; $j < $count; $j++) {
$list[] = new QRRSBlock($totalCount, $dataCount);
}
}
return $list;
}
static function getRsBlockTable($typeNumber, $errorCorrectLevel)
{
switch ($errorCorrectLevel) {
case QR_ERROR_CORRECT_LEVEL_L :
return self::$QR_RS_BLOCK_TABLE[($typeNumber - 1) * 4 + 0];
case QR_ERROR_CORRECT_LEVEL_M :
return self::$QR_RS_BLOCK_TABLE[($typeNumber - 1) * 4 + 1];
case QR_ERROR_CORRECT_LEVEL_Q :
return self::$QR_RS_BLOCK_TABLE[($typeNumber - 1) * 4 + 2];
case QR_ERROR_CORRECT_LEVEL_H :
return self::$QR_RS_BLOCK_TABLE[($typeNumber - 1) * 4 + 3];
default :
trigger_error("tn:$typeNumber/ecl:$errorCorrectLevel", E_USER_ERROR);
}
}
}
//---------------------------------------------------------------
// QRNumber
//---------------------------------------------------------------
class QRNumber extends QRData
{
function __construct($data)
{
parent::__construct(QR_MODE_NUMBER, $data);
}
function write(&$buffer)
{
$data = $this->getData();
$i = 0;
while ($i + 2 < strlen($data)) {
$num = QRNumber::parseInt(substr($data, $i, 3));
$buffer->put($num, 10);
$i += 3;
}
if ($i < strlen($data)) {
if (strlen($data) - $i == 1) {
$num = QRNumber::parseInt(substr($data, $i, $i + 1));
$buffer->put($num, 4);
} else if (strlen($data) - $i == 2) {
$num = QRNumber::parseInt(substr($data, $i, $i + 2));
$buffer->put($num, 7);
}
}
}
static function parseInt($s)
{
$num = 0;
for ($i = 0; $i < strlen($s); $i++) {
$num = $num * 10 + QRNumber::parseIntAt(ord($s[$i]));
}
return $num;
}
static function parseIntAt($c)
{
if (QRUtil::toCharCode('0') <= $c && $c <= QRUtil::toCharCode('9')) {
return $c - QRUtil::toCharCode('0');
}
trigger_error("illegal char : $c", E_USER_ERROR);
}
}
//---------------------------------------------------------------
// QRKanji
//---------------------------------------------------------------
class QRKanji extends QRData
{
function __construct($data)
{
parent::__construct(QR_MODE_KANJI, $data);
}
function write(&$buffer)
{
$data = $this->getData();
$i = 0;
while ($i + 1 < strlen($data)) {
$c = ((0xff & ord($data[$i])) << 8) | (0xff & ord($data[$i + 1]));
if (0x8140 <= $c && $c <= 0x9FFC) {
$c -= 0x8140;
} else if (0xE040 <= $c && $c <= 0xEBBF) {
$c -= 0xC140;
} else {
trigger_error("illegal char at " . ($i + 1) . "/$c", E_USER_ERROR);
}
$c = (($c >> 8) & 0xff) * 0xC0 + ($c & 0xff);
$buffer->put($c, 13);
$i += 2;
}
if ($i < strlen($data)) {
trigger_error("illegal char at " . ($i + 1), E_USER_ERROR);
}
}
function getLength()
{
return floor(strlen($this->getData()) / 2);
}
}
//---------------------------------------------------------------
// QRAlphaNum
//---------------------------------------------------------------
class QRAlphaNum extends QRData
{
function __construct($data)
{
parent::__construct(QR_MODE_ALPHA_NUM, $data);
}
function write(&$buffer)
{
$i = 0;
$c = $this->getData();
while ($i + 1 < strlen($c)) {
$buffer->put(QRAlphaNum::getCode(ord($c[$i])) * 45
+ QRAlphaNum::getCode(ord($c[$i + 1])), 11);
$i += 2;
}
if ($i < strlen($c)) {
$buffer->put(QRAlphaNum::getCode(ord($c[$i])), 6);
}
}
static function getCode($c)
{
if (QRUtil::toCharCode('0') <= $c
&& $c <= QRUtil::toCharCode('9')) {
return $c - QRUtil::toCharCode('0');
} else if (QRUtil::toCharCode('A') <= $c
&& $c <= QRUtil::toCharCode('Z')) {
return $c - QRUtil::toCharCode('A') + 10;
} else {
switch ($c) {
case QRUtil::toCharCode(' ') :
return 36;
case QRUtil::toCharCode('$') :
return 37;
case QRUtil::toCharCode('%') :
return 38;
case QRUtil::toCharCode('*') :
return 39;
case QRUtil::toCharCode('+') :
return 40;
case QRUtil::toCharCode('-') :
return 41;
case QRUtil::toCharCode('.') :
return 42;
case QRUtil::toCharCode('/') :
return 43;
case QRUtil::toCharCode(':') :
return 44;
default :
trigger_error("illegal char : $c", E_USER_ERROR);
}
}
}
}
//---------------------------------------------------------------
// QR8BitByte
//---------------------------------------------------------------
class QR8BitByte extends QRData
{
function __construct($data)
{
parent::__construct(QR_MODE_8BIT_BYTE, $data);
}
function write(&$buffer)
{
$data = $this->getData();
for ($i = 0; $i < strlen($data); $i++) {
$buffer->put(ord($data[$i]), 8);
}
}
}
//---------------------------------------------------------------
// QRData
//---------------------------------------------------------------
abstract class QRData
{
var $mode;
var $data;
function __construct($mode, $data)
{
$this->mode = $mode;
$this->data = $data;
}
function getMode()
{
return $this->mode;
}
function getData()
{
return $this->data;
}
/**
* @return int
*/
function getLength()
{
return strlen($this->getData());
}
/**
* @param \QRBitBuffer $buffer
*/
abstract function write(&$buffer);
function getLengthInBits($type)
{
if (1 <= $type && $type < 10) {
// 1 - 9
switch ($this->mode) {
case QR_MODE_NUMBER :
return 10;
case QR_MODE_ALPHA_NUM :
return 9;
case QR_MODE_8BIT_BYTE :
return 8;
case QR_MODE_KANJI :
return 8;
default :
trigger_error("mode:$this->mode", E_USER_ERROR);
}
} else if ($type < 27) {
// 10 - 26
switch ($this->mode) {
case QR_MODE_NUMBER :
return 12;
case QR_MODE_ALPHA_NUM :
return 11;
case QR_MODE_8BIT_BYTE :
return 16;
case QR_MODE_KANJI :
return 10;
default :
trigger_error("mode:$this->mode", E_USER_ERROR);
}
} else if ($type < 41) {
// 27 - 40
switch ($this->mode) {
case QR_MODE_NUMBER :
return 14;
case QR_MODE_ALPHA_NUM :
return 13;
case QR_MODE_8BIT_BYTE :
return 16;
case QR_MODE_KANJI :
return 12;
default :
trigger_error("mode:$this->mode", E_USER_ERROR);
}
} else {
trigger_error("mode:$this->mode", E_USER_ERROR);
}
}
}
//---------------------------------------------------------------
// QRMath
//---------------------------------------------------------------
class QRMath
{
static $QR_MATH_EXP_TABLE = null;
static $QR_MATH_LOG_TABLE = null;
static function init()
{
self::$QR_MATH_EXP_TABLE = QRMath::createNumArray(256);
for ($i = 0; $i < 8; $i++) {
self::$QR_MATH_EXP_TABLE[$i] = 1 << $i;
}
for ($i = 8; $i < 256; $i++) {
self::$QR_MATH_EXP_TABLE[$i] = self::$QR_MATH_EXP_TABLE[$i - 4]
^ self::$QR_MATH_EXP_TABLE[$i - 5]
^ self::$QR_MATH_EXP_TABLE[$i - 6]
^ self::$QR_MATH_EXP_TABLE[$i - 8];
}
self::$QR_MATH_LOG_TABLE = QRMath::createNumArray(256);
for ($i = 0; $i < 255; $i++) {
self::$QR_MATH_LOG_TABLE[self::$QR_MATH_EXP_TABLE[$i]] = $i;
}
}
static function createNumArray($length)
{
$num_array = array();
for ($i = 0; $i < $length; $i++) {
$num_array[] = 0;
}
return $num_array;
}
static function glog($n)
{
if ($n < 1) {
trigger_error("log($n)", E_USER_ERROR);
}
return self::$QR_MATH_LOG_TABLE[$n];
}
static function gexp($n)
{
while ($n < 0) {
$n += 255;
}
while ($n >= 256) {
$n -= 255;
}
return self::$QR_MATH_EXP_TABLE[$n];
}
}
// init static table
QRMath::init();
//---------------------------------------------------------------
// QRPolynomial
//---------------------------------------------------------------
class QRPolynomial
{
var $num;
function __construct($num, $shift = 0)
{
$offset = 0;
while ($offset < count($num) && $num[$offset] == 0) {
$offset++;
}
$this->num = QRMath::createNumArray(count($num) - $offset + $shift);
for ($i = 0; $i < count($num) - $offset; $i++) {
$this->num[$i] = $num[$i + $offset];
}
}
function get($index)
{
return $this->num[$index];
}
function getLength()
{
return count($this->num);
}
// PHP5
function __toString()
{
return $this->toString();
}
function toString()
{
$buffer = "";
for ($i = 0; $i < $this->getLength(); $i++) {
if ($i > 0) {
$buffer .= ",";
}
$buffer .= $this->get($i);
}
return $buffer;
}
function toLogString()
{
$buffer = "";
for ($i = 0; $i < $this->getLength(); $i++) {
if ($i > 0) {
$buffer .= ",";
}
$buffer .= QRMath::glog($this->get($i));
}
return $buffer;
}
/**
* @param \QRPolynomial $e
*
* @return \QRPolynomial
*/
function multiply($e)
{
$num = QRMath::createNumArray($this->getLength() + $e->getLength() - 1);
for ($i = 0; $i < $this->getLength(); $i++) {
$vi = QRMath::glog($this->get($i));
for ($j = 0; $j < $e->getLength(); $j++) {
$num[$i + $j] ^= QRMath::gexp($vi + QRMath::glog($e->get($j)));
}
}
return new QRPolynomial($num);
}
/**
* @param \QRPolynomial $e
*
* @return $this|\QRPolynomial
*/
function mod($e)
{
if ($this->getLength() - $e->getLength() < 0) {
return $this;
}
$ratio = QRMath::glog($this->get(0)) - QRMath::glog($e->get(0));
$num = QRMath::createNumArray($this->getLength());
for ($i = 0; $i < $this->getLength(); $i++) {
$num[$i] = $this->get($i);
}
for ($i = 0; $i < $e->getLength(); $i++) {
$num[$i] ^= QRMath::gexp(QRMath::glog($e->get($i)) + $ratio);
}
$newPolynomial = new QRPolynomial($num);
return $newPolynomial->mod($e);
}
}
//---------------------------------------------------------------
// Mode
//---------------------------------------------------------------
define("QR_MODE_NUMBER", 1 << 0);
define("QR_MODE_ALPHA_NUM", 1 << 1);
define("QR_MODE_8BIT_BYTE", 1 << 2);
define("QR_MODE_KANJI", 1 << 3);
//---------------------------------------------------------------
// MaskPattern
//---------------------------------------------------------------
define("QR_MASK_PATTERN000", 0);
define("QR_MASK_PATTERN001", 1);
define("QR_MASK_PATTERN010", 2);
define("QR_MASK_PATTERN011", 3);
define("QR_MASK_PATTERN100", 4);
define("QR_MASK_PATTERN101", 5);
define("QR_MASK_PATTERN110", 6);
define("QR_MASK_PATTERN111", 7);
//---------------------------------------------------------------
// ErrorCorrectLevel
// 7%.
define("QR_ERROR_CORRECT_LEVEL_L", 1);
// 15%.
define("QR_ERROR_CORRECT_LEVEL_M", 0);
// 25%.
define("QR_ERROR_CORRECT_LEVEL_Q", 3);
// 30%.
define("QR_ERROR_CORRECT_LEVEL_H", 2);
//---------------------------------------------------------------
// QRBitBuffer
//---------------------------------------------------------------
class QRBitBuffer
{
var $buffer;
var $length;
function __construct()
{
$this->buffer = array();
$this->length = 0;
}
function getBuffer()
{
return $this->buffer;
}
function getLengthInBits()
{
return $this->length;
}
function __toString()
{
$buffer = "";
for ($i = 0; $i < $this->getLengthInBits(); $i++) {
$buffer .= $this->get($i) ? '1' : '0';
}
return $buffer;
}
function get($index)
{
$bufIndex = (int)floor($index / 8);
return (($this->buffer[$bufIndex] >> (7 - $index % 8)) & 1) == 1;
}
function put($num, $length)
{
for ($i = 0; $i < $length; $i++) {
$this->putBit((($num >> ($length - $i - 1)) & 1) == 1);
}
}
function putBit($bit)
{
$bufIndex = (int)floor($this->length / 8);
if (count($this->buffer) <= $bufIndex) {
$this->buffer[] = 0;
}
if ($bit) {
$this->buffer[$bufIndex] |= (0x80 >> ($this->length % 8));
}
$this->length++;
}
}
... ...
<?php
namespace addons\epay\library;
class RedirectResponse extends \Symfony\Component\HttpFoundation\RedirectResponse implements \JsonSerializable, \Serializable
{
public function __toString()
{
return $this->getContent();
}
public function setTargetUrl($url)
{
if ('' === ($url ?? '')) {
throw new \InvalidArgumentException('无法跳转到空页面');
}
$this->targetUrl = $url;
$this->setContent(
sprintf('<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="refresh" content="0;url=\'%1$s\'" />
<title>正在跳转支付 %1$s</title>
</head>
<body>
<div id="redirect" style="display:none;">正在跳转支付 <a href="%1$s">%1$s</a></div>
<script type="text/javascript">
setTimeout(function(){
document.getElementById("redirect").style.display = "block";
}, 1000);
</script>
</body>
</html>', htmlspecialchars($url, \ENT_QUOTES, 'UTF-8')));
$this->headers->set('Location', $url);
return $this;
}
public function jsonSerialize()
{
return $this->getContent();
}
public function serialize()
{
return serialize($this->content);
}
public function unserialize($serialized)
{
return $this->content = unserialize($serialized);
}
}
... ...
<?php
namespace addons\epay\library;
class Response extends \Symfony\Component\HttpFoundation\Response implements \JsonSerializable, \Serializable
{
public function __toString()
{
return $this->getContent();
}
public function jsonSerialize()
{
return $this->getContent();
}
public function serialize()
{
return serialize($this->content);
}
public function unserialize($serialized)
{
return $this->content = unserialize($serialized);
}
}
... ...
<?php
namespace addons\epay\library;
use addons\third\model\Third;
use app\common\library\Auth;
use Exception;
use think\Session;
use Yansongda\Pay\Pay;
use Yansongda\Supports\Str;
/**
* 订单服务类
*
* @package addons\epay\library
*/
class Service
{
/**
* 提交订单
* @param array|float $amount 订单金额
* @param string $orderid 订单号
* @param string $type 支付类型,可选alipay或wechat
* @param string $title 订单标题
* @param string $notifyurl 通知回调URL
* @param string $returnurl 跳转返回URL
* @param string $method 支付方法
* @return Response|RedirectResponse|Collection
* @throws Exception
*/
public static function submitOrder($amount, $orderid = null, $type = null, $title = null, $notifyurl = null, $returnurl = null, $method = null, $openid = '')
{
if (!is_array($amount)) {
$params = [
'amount' => $amount,
'orderid' => $orderid,
'type' => $type,
'title' => $title,
'notifyurl' => $notifyurl,
'returnurl' => $returnurl,
'method' => $method,
'openid' => $openid,
];
} else {
$params = $amount;
}
$type = isset($params['type']) && in_array($params['type'], ['alipay', 'wechat']) ? $params['type'] : 'wechat';
$method = isset($params['method']) ? $params['method'] : 'web';
$orderid = isset($params['orderid']) ? $params['orderid'] : date("YmdHis") . mt_rand(100000, 999999);
$amount = isset($params['amount']) ? $params['amount'] : 1;
$title = isset($params['title']) ? $params['title'] : "支付";
$auth_code = isset($params['auth_code']) ? $params['auth_code'] : '';
$openid = isset($params['openid']) ? $params['openid'] : '';
$request = request();
$notifyurl = isset($params['notifyurl']) ? $params['notifyurl'] : $request->root(true) . '/addons/epay/index/' . $type . 'notify';
$returnurl = isset($params['returnurl']) ? $params['returnurl'] : $request->root(true) . '/addons/epay/index/' . $type . 'return/out_trade_no/' . $orderid;
$html = '';
$config = Service::getConfig($type);
$config['notify_url'] = $notifyurl;
$config['return_url'] = $returnurl;
$isWechat = strpos($request->server('HTTP_USER_AGENT'), 'MicroMessenger') !== false;
$result = null;
if ($type == 'alipay') {
//如果是PC支付,判断当前环境,进行跳转
if ($method == 'web') {
//如果是微信环境或后台配置PC使用扫码支付
if ($isWechat || $config['scanpay']) {
Session::set("alipayorderdata", $params);
$url = addon_url('epay/api/alipay', [], true, true);
return RedirectResponse::create($url);
} elseif ($request->isMobile()) {
$method = 'wap';
}
}
//创建支付对象
$pay = Pay::alipay($config);
$params = [
'out_trade_no' => $orderid,//你的订单号
'total_amount' => $amount,//单位元
'subject' => $title,
];
switch ($method) {
case 'web':
//电脑支付
$result = $pay->web($params);
break;
case 'wap':
//手机网页支付
$result = $pay->wap($params);
break;
case 'app':
//APP支付
$result = $pay->app($params);
break;
case 'scan':
//扫码支付
$result = $pay->scan($params);
break;
case 'pos':
//刷卡支付必须要有auth_code
$params['auth_code'] = $auth_code;
$result = $pay->pos($params);
break;
default:
}
} else {
//如果是PC支付,判断当前环境,进行跳转
if ($method == 'web') {
//如果是移动端,但不是微信环境
if ($request->isMobile() && !$isWechat) {
$method = 'wap';
} else {
Session::set("wechatorderdata", $params);
$url = addon_url('epay/api/wechat', [], true, true);
return RedirectResponse::create($url);
}
}
//创建支付对象
$pay = Pay::wechat($config);
$params = [
'out_trade_no' => $orderid,//你的订单号
'body' => $title,
'total_fee' => $amount * 100, //单位分
];
switch ($method) {
//case 'web':
// //电脑支付,跳转到自定义展示页面(FastAdmin独有)
// $result = $pay->web($params);
// break;
case 'mp':
//公众号支付
//公众号支付必须有openid
$params['openid'] = $openid;
$result = $pay->mp($params);
break;
case 'wap':
//手机网页支付,跳转
$params['spbill_create_ip'] = $request->ip(0, false);
$result = $pay->wap($params);
break;
case 'app':
//APP支付,直接返回字符串
$result = $pay->app($params);
break;
case 'scan':
//扫码支付,直接返回字符串
$result = $pay->scan($params);
break;
case 'pos':
//刷卡支付,直接返回字符串
//刷卡支付必须要有auth_code
$params['auth_code'] = $auth_code;
$result = $pay->pos($params);
break;
case 'miniapp':
//小程序支付,直接返回字符串
//小程序支付必须要有openid
$params['openid'] = $openid;
$result = $pay->miniapp($params);
break;
default:
}
}
//使用重写的Response类、RedirectResponse、Collection类
if ($result instanceof \Symfony\Component\HttpFoundation\RedirectResponse) {
$result = RedirectResponse::create($result->getTargetUrl());
} elseif ($result instanceof \Symfony\Component\HttpFoundation\Response) {
$result = Response::create($result->getContent());
} elseif ($result instanceof \Yansongda\Supports\Collection) {
$result = Collection::make($result->all());
}
return $result;
}
/**
* 验证回调是否成功
* @param string $type 支付类型
* @param array $config 配置信息
* @return bool|\Yansongda\Pay\Gateways\Alipay|\Yansongda\Pay\Gateways\Wechat
*/
public static function checkNotify($type, $config = [])
{
$type = strtolower($type);
if (!in_array($type, ['wechat', 'alipay'])) {
return false;
}
try {
$config = self::getConfig($type);
$pay = $type == 'wechat' ? Pay::wechat($config) : Pay::alipay($config);
$data = $pay->verify();
if ($type == 'alipay') {
if (in_array($data['trade_status'], ['TRADE_SUCCESS', 'TRADE_FINISHED'])) {
return $pay;
}
} else {
return $pay;
}
} catch (Exception $e) {
return false;
}
return false;
}
/**
* 验证返回是否成功,请勿用于判断是否支付成功的逻辑验证
* 已弃用
*
* @param string $type 支付类型
* @param array $config 配置信息
* @return bool
* @deprecated 已弃用,请勿用于逻辑验证
*/
public static function checkReturn($type, $config = [])
{
//由于PC及移动端无法获取请求的参数信息,取消return验证,均返回true
return true;
}
/**
* 获取配置
* @param string $type 支付类型
* @return array|mixed
*/
public static function getConfig($type = 'wechat')
{
$config = get_addon_config('epay');
$config = isset($config[$type]) ? $config[$type] : $config['wechat'];
if ($config['log']) {
$config['log'] = [
'file' => LOG_PATH . 'epaylogs' . DS . $type . '-' . date("Y-m-d") . '.log',
'level' => 'debug'
];
}
if (isset($config['cert_client']) && substr($config['cert_client'], 0, 8) == '/addons/') {
$config['cert_client'] = ROOT_PATH . str_replace('/', DS, substr($config['cert_client'], 1));
}
if (isset($config['cert_key']) && substr($config['cert_key'], 0, 8) == '/addons/') {
$config['cert_key'] = ROOT_PATH . str_replace('/', DS, substr($config['cert_key'], 1));
}
if (isset($config['app_cert_public_key']) && substr($config['app_cert_public_key'], 0, 8) == '/addons/') {
$config['app_cert_public_key'] = ROOT_PATH . str_replace('/', DS, substr($config['app_cert_public_key'], 1));
}
if (isset($config['alipay_root_cert']) && substr($config['alipay_root_cert'], 0, 8) == '/addons/') {
$config['alipay_root_cert'] = ROOT_PATH . str_replace('/', DS, substr($config['alipay_root_cert'], 1));
}
if (isset($config['ali_public_key']) && (Str::endsWith($config['ali_public_key'], '.crt') || Str::endsWith($config['ali_public_key'], '.pem'))) {
$config['ali_public_key'] = ROOT_PATH . str_replace('/', DS, substr($config['ali_public_key'], 1));
}
// 可选
$config['http'] = [
'timeout' => 10,
'connect_timeout' => 10,
// 更多配置项请参考 [Guzzle](https://guzzle-cn.readthedocs.io/zh_CN/latest/request-options.html)
];
$config['notify_url'] = empty($config['notify_url']) ? addon_url('epay/api/notifyx', [], false) . '/type/' . $type : $config['notify_url'];
$config['notify_url'] = !preg_match("/^(http:\/\/|https:\/\/)/i", $config['notify_url']) ? request()->root(true) . $config['notify_url'] : $config['notify_url'];
$config['return_url'] = empty($config['return_url']) ? addon_url('epay/api/returnx', [], false) . '/type/' . $type : $config['return_url'];
$config['return_url'] = !preg_match("/^(http:\/\/|https:\/\/)/i", $config['return_url']) ? request()->root(true) . $config['return_url'] : $config['return_url'];
return $config;
}
/**
* 获取微信Openid
*
* @return mixed|string
*/
public static function getOpenid()
{
$config = self::getConfig('wechat');
$openid = '';
$auth = Auth::instance();
if ($auth->isLogin()) {
$third = get_addon_info('third');
if ($third && $third['state']) {
$thirdInfo = Third::where('user_id', $auth->id)->where('platform', 'wechat')->where('apptype', 'mp')->find();
$openid = $thirdInfo ? $thirdInfo['openid'] : '';
}
}
if (!$openid) {
$openid = Session::get("openid");
//如果未传openid,则去读取openid
if (!$openid) {
$wechat = new Wechat($config['app_id'], $config['app_secret']);
$openid = $wechat->getOpenid();
}
}
return $openid;
}
}
... ...
<?php
namespace addons\epay\library;
use fast\Http;
use think\Cache;
use think\Session;
/**
* 微信授权
*
*/
class Wechat
{
private $app_id = '';
private $app_secret = '';
private $scope = 'snsapi_userinfo';
public function __construct($app_id, $app_secret)
{
$this->app_id = $app_id;
$this->app_secret = $app_secret;
}
/**
* 获取微信授权链接
*
* @return string
*/
public function getAuthorizeUrl()
{
$redirect_uri = addon_url('epay/api/wechat', [], true, true);
$redirect_uri = urlencode($redirect_uri);
$state = \fast\Random::alnum();
Session::set('state', $state);
return "https://open.weixin.qq.com/connect/oauth2/authorize?appid={$this->app_id}&redirect_uri={$redirect_uri}&response_type=code&scope={$this->scope}&state={$state}#wechat_redirect";
}
/**
* 获取微信openid
*
* @return mixed|string
*/
public function getOpenid()
{
$openid = Session::get('openid');
if (!$openid) {
if (!isset($_GET['code'])) {
$url = $this->getAuthorizeUrl();
Header("Location: $url");
exit();
} else {
$state = Session::get('state');
if ($state == $_GET['state']) {
$code = $_GET['code'];
$token = $this->getAccessToken($code);
if (!isset($token['openid']) && isset($token['errmsg'])) {
exception($token['errmsg']);
}
$openid = isset($token['openid']) ? $token['openid'] : '';
if ($openid) {
Session::set("openid", $openid);
}
}
}
}
return $openid;
}
/**
* 获取授权token网页授权
*
* @param string $code
* @return mixed|string
*/
public function getAccessToken($code = '')
{
$params = [
'appid' => $this->app_id,
'secret' => $this->app_secret,
'code' => $code,
'grant_type' => 'authorization_code'
];
$ret = Http::sendRequest('https://api.weixin.qq.com/sns/oauth2/access_token', $params, 'GET');
if ($ret['ret']) {
$ar = json_decode($ret['msg'], true);
return $ar;
}
return [];
}
public function getJsticket($code = '')
{
$jsticket = Session::get('jsticket');
if (!$jsticket) {
$token = $this->getAccessToken($code);
$params = [
'access_token' => 'token',
'type' => 'jsapi',
];
$ret = Http::sendRequest('https://api.weixin.qq.com/cgi-bin/ticket/getticket', $params, 'GET');
if ($ret['ret']) {
$ar = json_decode($ret['msg'], true);
return $ar;
}
}
return $jsticket;
}
}
... ...
<?php
namespace Yansongda\Pay\Contracts;
use Symfony\Component\HttpFoundation\Response;
use Yansongda\Supports\Collection;
interface GatewayApplicationInterface
{
/**
* To pay.
*
* @author yansongda <me@yansonga.cn>
*
* @param string $gateway
* @param array $params
*
* @return Collection|Response
*/
public function pay($gateway, $params);
/**
* Query an order.
*
* @author yansongda <me@yansongda.cn>
*
* @param string|array $order
*
* @return Collection
*/
public function find($order, string $type);
/**
* Refund an order.
*
* @author yansongda <me@yansongda.cn>
*
* @return Collection
*/
public function refund(array $order);
/**
* Cancel an order.
*
* @author yansongda <me@yansongda.cn>
*
* @param string|array $order
*
* @return Collection
*/
public function cancel($order);
/**
* Close an order.
*
* @author yansongda <me@yansongda.cn>
*
* @param string|array $order
*
* @return Collection
*/
public function close($order);
/**
* Verify a request.
*
* @author yansongda <me@yansongda.cn>
*
* @param string|array|null $content
*
* @return Collection
*/
public function verify($content, bool $refund);
/**
* Echo success to server.
*
* @author yansongda <me@yansongda.cn>
*
* @return Response
*/
public function success();
}
... ...
<?php
namespace Yansongda\Pay\Contracts;
use Symfony\Component\HttpFoundation\Response;
use Yansongda\Supports\Collection;
interface GatewayInterface
{
/**
* Pay an order.
*
* @author yansongda <me@yansongda.cn>
*
* @param string $endpoint
*
* @return Collection|Response
*/
public function pay($endpoint, array $payload);
}
... ...
<?php
namespace Yansongda\Pay;
use Exception;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Contracts\EventDispatcher\Event;
/**
* @author yansongda <me@yansongda.cn>
*
* @method static Event dispatch(Event $event) Dispatches an event to all registered listeners
* @method static array getListeners($eventName = null) Gets the listeners of a specific event or all listeners sorted by descending priority.
* @method static int|void getListenerPriority($eventName, $listener) Gets the listener priority for a specific event.
* @method static bool hasListeners($eventName = null) Checks whether an event has any registered listeners.
* @method static void addListener($eventName, $listener, $priority = 0) Adds an event listener that listens on the specified events.
* @method static removeListener($eventName, $listener) Removes an event listener from the specified events.
* @method static void addSubscriber(EventSubscriberInterface $subscriber) Adds an event subscriber.
* @method static void removeSubscriber(EventSubscriberInterface $subscriber)
*/
class Events
{
/**
* dispatcher.
*
* @var EventDispatcher
*/
protected static $dispatcher;
/**
* Forward call.
*
* @author yansongda <me@yansongda.cn>
*
* @param string $method
* @param array $args
*
* @throws Exception
*
* @return mixed
*/
public static function __callStatic($method, $args)
{
return call_user_func_array([self::getDispatcher(), $method], $args);
}
/**
* Forward call.
*
* @author yansongda <me@yansongda.cn>
*
* @param string $method
* @param array $args
*
* @throws Exception
*
* @return mixed
*/
public function __call($method, $args)
{
return call_user_func_array([self::getDispatcher(), $method], $args);
}
/**
* setDispatcher.
*
* @author yansongda <me@yansongda.cn>
*/
public static function setDispatcher(EventDispatcher $dispatcher)
{
self::$dispatcher = $dispatcher;
}
/**
* getDispatcher.
*
* @author yansongda <me@yansongda.cn>
*/
public static function getDispatcher(): EventDispatcher
{
if (self::$dispatcher) {
return self::$dispatcher;
}
return self::$dispatcher = self::createDispatcher();
}
/**
* createDispatcher.
*
* @author yansongda <me@yansongda.cn>
*/
public static function createDispatcher(): EventDispatcher
{
return new EventDispatcher();
}
}
... ...
<?php
namespace Yansongda\Pay\Events;
class ApiRequested extends Event
{
/**
* Endpoint.
*
* @var string
*/
public $endpoint;
/**
* Result.
*
* @var array
*/
public $result;
/**
* Bootstrap.
*/
public function __construct(string $driver, string $gateway, string $endpoint, array $result)
{
$this->endpoint = $endpoint;
$this->result = $result;
parent::__construct($driver, $gateway);
}
}
... ...
<?php
namespace Yansongda\Pay\Events;
class ApiRequesting extends Event
{
/**
* Endpoint.
*
* @var string
*/
public $endpoint;
/**
* Payload.
*
* @var array
*/
public $payload;
/**
* Bootstrap.
*/
public function __construct(string $driver, string $gateway, string $endpoint, array $payload)
{
$this->endpoint = $endpoint;
$this->payload = $payload;
parent::__construct($driver, $gateway);
}
}
... ...
<?php
namespace Yansongda\Pay\Events;
use Symfony\Contracts\EventDispatcher\Event as SymfonyEvent;
class Event extends SymfonyEvent
{
/**
* Driver.
*
* @var string
*/
public $driver;
/**
* Method.
*
* @var string
*/
public $gateway;
/**
* Extra attributes.
*
* @var mixed
*/
public $attributes;
/**
* Bootstrap.
*
* @author yansongda <me@yansongda.cn>
*/
public function __construct(string $driver, string $gateway)
{
$this->driver = $driver;
$this->gateway = $gateway;
}
}
... ...
<?php
namespace Yansongda\Pay\Events;
class MethodCalled extends Event
{
/**
* endpoint.
*
* @var string
*/
public $endpoint;
/**
* payload.
*
* @var array
*/
public $payload;
/**
* Bootstrap.
*
* @author yansongda <me@yansongda.cn>
*/
public function __construct(string $driver, string $gateway, string $endpoint, array $payload = [])
{
$this->endpoint = $endpoint;
$this->payload = $payload;
parent::__construct($driver, $gateway);
}
}
... ...
<?php
namespace Yansongda\Pay\Events;
class PayStarted extends Event
{
/**
* Endpoint.
*
* @var string
*/
public $endpoint;
/**
* Payload.
*
* @var array
*/
public $payload;
/**
* Bootstrap.
*/
public function __construct(string $driver, string $gateway, string $endpoint, array $payload)
{
$this->endpoint = $endpoint;
$this->payload = $payload;
parent::__construct($driver, $gateway);
}
}
... ...
<?php
namespace Yansongda\Pay\Events;
class PayStarting extends Event
{
/**
* Params.
*
* @var array
*/
public $params;
/**
* Bootstrap.
*/
public function __construct(string $driver, string $gateway, array $params)
{
$this->params = $params;
parent::__construct($driver, $gateway);
}
}
... ...
<?php
namespace Yansongda\Pay\Events;
class RequestReceived extends Event
{
/**
* Received data.
*
* @var array
*/
public $data;
/**
* Bootstrap.
*
* @author yansongda <me@yansongda.cn>
*/
public function __construct(string $driver, string $gateway, array $data)
{
$this->data = $data;
parent::__construct($driver, $gateway);
}
}
... ...
<?php
namespace Yansongda\Pay\Events;
class SignFailed extends Event
{
/**
* Received data.
*
* @var array
*/
public $data;
/**
* Bootstrap.
*
* @author yansongda <me@yansongda.cn>
*/
public function __construct(string $driver, string $gateway, array $data)
{
$this->data = $data;
parent::__construct($driver, $gateway);
}
}
... ...
<?php
namespace Yansongda\Pay\Exceptions;
class BusinessException extends GatewayException
{
/**
* Bootstrap.
*
* @author yansongda <me@yansonga.cn>
*
* @param string $message
* @param array|string $raw
*/
public function __construct($message, $raw = [])
{
parent::__construct('ERROR_BUSINESS: '.$message, $raw, self::ERROR_BUSINESS);
}
}
... ...
<?php
namespace Yansongda\Pay\Exceptions;
class Exception extends \Exception
{
const UNKNOWN_ERROR = 9999;
const INVALID_GATEWAY = 1;
const INVALID_CONFIG = 2;
const INVALID_ARGUMENT = 3;
const ERROR_GATEWAY = 4;
const INVALID_SIGN = 5;
const ERROR_BUSINESS = 6;
/**
* Raw error info.
*
* @var array
*/
public $raw;
/**
* Bootstrap.
*
* @author yansongda <me@yansonga.cn>
*
* @param string $message
* @param array|string $raw
* @param int|string $code
*/
public function __construct($message = '', $raw = [], $code = self::UNKNOWN_ERROR)
{
$message = '' === $message ? 'Unknown Error' : $message;
$this->raw = is_array($raw) ? $raw : [$raw];
parent::__construct($message, intval($code));
}
}
... ...
<?php
namespace Yansongda\Pay\Exceptions;
class GatewayException extends Exception
{
/**
* Bootstrap.
*
* @author yansongda <me@yansonga.cn>
*
* @param string $message
* @param array|string $raw
* @param int $code
*/
public function __construct($message, $raw = [], $code = self::ERROR_GATEWAY)
{
parent::__construct('ERROR_GATEWAY: '.$message, $raw, $code);
}
}
... ...
<?php
namespace Yansongda\Pay\Exceptions;
class InvalidArgumentException extends Exception
{
/**
* Bootstrap.
*
* @author yansongda <me@yansonga.cn>
*
* @param string $message
* @param array|string $raw
*/
public function __construct($message, $raw = [])
{
parent::__construct('INVALID_ARGUMENT: '.$message, $raw, self::INVALID_ARGUMENT);
}
}
... ...
<?php
namespace Yansongda\Pay\Exceptions;
class InvalidConfigException extends Exception
{
/**
* Bootstrap.
*
* @author yansongda <me@yansonga.cn>
*
* @param string $message
* @param array|string $raw
*/
public function __construct($message, $raw = [])
{
parent::__construct('INVALID_CONFIG: '.$message, $raw, self::INVALID_CONFIG);
}
}
... ...
<?php
namespace Yansongda\Pay\Exceptions;
class InvalidGatewayException extends Exception
{
/**
* Bootstrap.
*
* @author yansongda <me@yansonga.cn>
*
* @param string $message
* @param array|string $raw
*/
public function __construct($message, $raw = [])
{
parent::__construct('INVALID_GATEWAY: '.$message, $raw, self::INVALID_GATEWAY);
}
}
... ...
<?php
namespace Yansongda\Pay\Exceptions;
class InvalidSignException extends Exception
{
/**
* Bootstrap.
*
* @author yansongda <me@yansonga.cn>
*
* @param string $message
* @param array|string $raw
*/
public function __construct($message, $raw = [])
{
parent::__construct('INVALID_SIGN: '.$message, $raw, self::INVALID_SIGN);
}
}
... ...
<?php
namespace Yansongda\Pay\Gateways;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Yansongda\Pay\Contracts\GatewayApplicationInterface;
use Yansongda\Pay\Contracts\GatewayInterface;
use Yansongda\Pay\Events;
use Yansongda\Pay\Exceptions\GatewayException;
use Yansongda\Pay\Exceptions\InvalidArgumentException;
use Yansongda\Pay\Exceptions\InvalidConfigException;
use Yansongda\Pay\Exceptions\InvalidGatewayException;
use Yansongda\Pay\Exceptions\InvalidSignException;
use Yansongda\Pay\Gateways\Alipay\Support;
use Yansongda\Supports\Collection;
use Yansongda\Supports\Config;
use Yansongda\Supports\Str;
/**
* @method Response app(array $config) APP 支付
* @method Collection pos(array $config) 刷卡支付
* @method Collection scan(array $config) 扫码支付
* @method Collection transfer(array $config) 帐户转账
* @method Response wap(array $config) 手机网站支付
* @method Response web(array $config) 电脑支付
* @method Collection mini(array $config) 小程序支付
*/
class Alipay implements GatewayApplicationInterface
{
/**
* Const mode_normal.
*/
const MODE_NORMAL = 'normal';
/**
* Const mode_dev.
*/
const MODE_DEV = 'dev';
/**
* Const mode_service.
*/
const MODE_SERVICE = 'service';
/**
* Const url.
*/
const URL = [
self::MODE_NORMAL => 'https://openapi.alipay.com/gateway.do?charset=utf-8',
self::MODE_DEV => 'https://openapi.alipaydev.com/gateway.do?charset=utf-8',
];
/**
* Alipay payload.
*
* @var array
*/
protected $payload;
/**
* Alipay gateway.
*
* @var string
*/
protected $gateway;
/**
* extends.
*
* @var array
*/
protected $extends;
/**
* Bootstrap.
*
* @author yansongda <me@yansongda.cn>
*
* @throws \Exception
*/
public function __construct(Config $config)
{
$this->gateway = Support::create($config)->getBaseUri();
$this->payload = [
'app_id' => $config->get('app_id'),
'method' => '',
'format' => 'JSON',
'charset' => 'utf-8',
'sign_type' => 'RSA2',
'version' => '1.0',
'return_url' => $config->get('return_url'),
'notify_url' => $config->get('notify_url'),
'timestamp' => date('Y-m-d H:i:s'),
'sign' => '',
'biz_content' => '',
'app_auth_token' => $config->get('app_auth_token'),
];
if ($config->get('app_cert_public_key') && $config->get('alipay_root_cert')) {
$this->payload['app_cert_sn'] = Support::getCertSN($config->get('app_cert_public_key'));
$this->payload['alipay_root_cert_sn'] = Support::getRootCertSN($config->get('alipay_root_cert'));
}
}
/**
* Magic pay.
*
* @author yansongda <me@yansongda.cn>
*
* @param string $method
* @param array $params
*
* @throws GatewayException
* @throws InvalidArgumentException
* @throws InvalidConfigException
* @throws InvalidGatewayException
* @throws InvalidSignException
*
* @return Response|Collection
*/
public function __call($method, $params)
{
if (isset($this->extends[$method])) {
return $this->makeExtend($method, ...$params);
}
return $this->pay($method, ...$params);
}
/**
* Pay an order.
*
* @author yansongda <me@yansongda.cn>
*
* @param string $gateway
* @param array $params
*
* @throws InvalidGatewayException
*
* @return Response|Collection
*/
public function pay($gateway, $params = [])
{
Events::dispatch(new Events\PayStarting('Alipay', $gateway, $params));
$this->payload['return_url'] = $params['return_url'] ?? $this->payload['return_url'];
$this->payload['notify_url'] = $params['notify_url'] ?? $this->payload['notify_url'];
unset($params['return_url'], $params['notify_url']);
$this->payload['biz_content'] = json_encode($params);
$gateway = get_class($this).'\\'.Str::studly($gateway).'Gateway';
if (class_exists($gateway)) {
return $this->makePay($gateway);
}
throw new InvalidGatewayException("Pay Gateway [{$gateway}] not exists");
}
/**
* Verify sign.
*
* @author yansongda <me@yansongda.cn>
*
* @param array|null $data
*
* @throws InvalidSignException
* @throws InvalidConfigException
*/
public function verify($data = null, bool $refund = false): Collection
{
if (is_null($data)) {
$request = Request::createFromGlobals();
$data = $request->request->count() > 0 ? $request->request->all() : $request->query->all();
}
if (isset($data['fund_bill_list'])) {
$data['fund_bill_list'] = htmlspecialchars_decode($data['fund_bill_list']);
}
Events::dispatch(new Events\RequestReceived('Alipay', '', $data));
if (Support::verifySign($data)) {
return new Collection($data);
}
Events::dispatch(new Events\SignFailed('Alipay', '', $data));
throw new InvalidSignException('Alipay Sign Verify FAILED', $data);
}
/**
* Query an order.
*
* @author yansongda <me@yansongda.cn>
*
* @param string|array $order
*
* @throws GatewayException
* @throws InvalidConfigException
* @throws InvalidSignException
*/
public function find($order, string $type = 'wap'): Collection
{
$gateway = get_class($this).'\\'.Str::studly($type).'Gateway';
if (!class_exists($gateway) || !is_callable([new $gateway(), 'find'])) {
throw new GatewayException("{$gateway} Done Not Exist Or Done Not Has FIND Method");
}
$config = call_user_func([new $gateway(), 'find'], $order);
$this->payload['method'] = $config['method'];
$this->payload['biz_content'] = $config['biz_content'];
$this->payload['sign'] = Support::generateSign($this->payload);
Events::dispatch(new Events\MethodCalled('Alipay', 'Find', $this->gateway, $this->payload));
return Support::requestApi($this->payload);
}
/**
* Refund an order.
*
* @author yansongda <me@yansongda.cn>
*
* @throws GatewayException
* @throws InvalidConfigException
* @throws InvalidSignException
*/
public function refund(array $order): Collection
{
$this->payload['method'] = 'alipay.trade.refund';
$this->payload['biz_content'] = json_encode($order);
$this->payload['sign'] = Support::generateSign($this->payload);
Events::dispatch(new Events\MethodCalled('Alipay', 'Refund', $this->gateway, $this->payload));
return Support::requestApi($this->payload);
}
/**
* Cancel an order.
*
* @author yansongda <me@yansongda.cn>
*
* @param array|string $order
*
* @throws GatewayException
* @throws InvalidConfigException
* @throws InvalidSignException
*/
public function cancel($order): Collection
{
$this->payload['method'] = 'alipay.trade.cancel';
$this->payload['biz_content'] = json_encode(is_array($order) ? $order : ['out_trade_no' => $order]);
$this->payload['sign'] = Support::generateSign($this->payload);
Events::dispatch(new Events\MethodCalled('Alipay', 'Cancel', $this->gateway, $this->payload));
return Support::requestApi($this->payload);
}
/**
* Close an order.
*
* @param string|array $order
*
* @author yansongda <me@yansongda.cn>
*
* @throws GatewayException
* @throws InvalidConfigException
* @throws InvalidSignException
*/
public function close($order): Collection
{
$this->payload['method'] = 'alipay.trade.close';
$this->payload['biz_content'] = json_encode(is_array($order) ? $order : ['out_trade_no' => $order]);
$this->payload['sign'] = Support::generateSign($this->payload);
Events::dispatch(new Events\MethodCalled('Alipay', 'Close', $this->gateway, $this->payload));
return Support::requestApi($this->payload);
}
/**
* Download bill.
*
* @author yansongda <me@yansongda.cn>
*
* @param string|array $bill
*
* @throws GatewayException
* @throws InvalidConfigException
* @throws InvalidSignException
*/
public function download($bill): string
{
$this->payload['method'] = 'alipay.data.dataservice.bill.downloadurl.query';
$this->payload['biz_content'] = json_encode(is_array($bill) ? $bill : ['bill_type' => 'trade', 'bill_date' => $bill]);
$this->payload['sign'] = Support::generateSign($this->payload);
Events::dispatch(new Events\MethodCalled('Alipay', 'Download', $this->gateway, $this->payload));
$result = Support::requestApi($this->payload);
return ($result instanceof Collection) ? $result->get('bill_download_url') : '';
}
/**
* Reply success to alipay.
*
* @author yansongda <me@yansongda.cn>
*/
public function success(): Response
{
Events::dispatch(new Events\MethodCalled('Alipay', 'Success', $this->gateway));
return new Response('success');
}
/**
* extend.
*
* @author yansongda <me@yansongda.cn>
*
* @throws GatewayException
* @throws InvalidConfigException
* @throws InvalidSignException
* @throws InvalidArgumentException
*/
public function extend(string $method, callable $function, bool $now = true): ?Collection
{
if (!$now && !method_exists($this, $method)) {
$this->extends[$method] = $function;
return null;
}
$customize = $function($this->payload);
if (!is_array($customize) && !($customize instanceof Collection)) {
throw new InvalidArgumentException('Return Type Must Be Array Or Collection');
}
Events::dispatch(new Events\MethodCalled('Alipay', 'extend', $this->gateway, $customize));
if (is_array($customize)) {
$this->payload = $customize;
$this->payload['sign'] = Support::generateSign($this->payload);
return Support::requestApi($this->payload);
}
return $customize;
}
/**
* Make pay gateway.
*
* @author yansongda <me@yansongda.cn>
*
* @throws InvalidGatewayException
*
* @return Response|Collection
*/
protected function makePay(string $gateway)
{
$app = new $gateway();
if ($app instanceof GatewayInterface) {
return $app->pay($this->gateway, array_filter($this->payload, function ($value) {
return '' !== $value && !is_null($value);
}));
}
throw new InvalidGatewayException("Pay Gateway [{$gateway}] Must Be An Instance Of GatewayInterface");
}
/**
* makeExtend.
*
* @author yansongda <me@yansongda.cn>
*
* @throws GatewayException
* @throws InvalidArgumentException
* @throws InvalidConfigException
* @throws InvalidSignException
*/
protected function makeExtend(string $method, array ...$params): Collection
{
$params = count($params) >= 1 ? $params[0] : $params;
$function = $this->extends[$method];
$customize = $function($this->payload, $params);
if (!is_array($customize) && !($customize instanceof Collection)) {
throw new InvalidArgumentException('Return Type Must Be Array Or Collection');
}
Events::dispatch(new Events\MethodCalled(
'Alipay',
'extend - '.$method,
$this->gateway,
is_array($customize) ? $customize : $customize->toArray()
));
if (is_array($customize)) {
$this->payload = $customize;
$this->payload['sign'] = Support::generateSign($this->payload);
return Support::requestApi($this->payload);
}
return $customize;
}
}
... ...
<?php
namespace Yansongda\Pay\Gateways\Alipay;
use Symfony\Component\HttpFoundation\Response;
use Yansongda\Pay\Events;
use Yansongda\Pay\Exceptions\InvalidArgumentException;
use Yansongda\Pay\Exceptions\InvalidConfigException;
use Yansongda\Pay\Gateways\Alipay;
class AppGateway extends Gateway
{
/**
* Pay an order.
*
* @author yansongda <me@yansongda.cn>
*
* @param string $endpoint
*
* @throws InvalidConfigException
* @throws InvalidArgumentException
*/
public function pay($endpoint, array $payload): Response
{
$payload['method'] = 'alipay.trade.app.pay';
$biz_array = json_decode($payload['biz_content'], true);
if ((Alipay::MODE_SERVICE === $this->mode) && (!empty(Support::getInstance()->pid))) {
$biz_array['extend_params'] = is_array($biz_array['extend_params']) ? array_merge(['sys_service_provider_id' => Support::getInstance()->pid], $biz_array['extend_params']) : ['sys_service_provider_id' => Support::getInstance()->pid];
}
$payload['biz_content'] = json_encode(array_merge($biz_array, ['product_code' => 'QUICK_MSECURITY_PAY']));
$payload['sign'] = Support::generateSign($payload);
Events::dispatch(new Events\PayStarted('Alipay', 'App', $endpoint, $payload));
return new Response(http_build_query($payload));
}
}
... ...