作者 李忠强

更新

要显示太多修改。

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

{
"directory": "public/assets/libs",
"ignoredDependencies": [
"es6-promise",
"file-saver",
"html2canvas",
"jspdf",
"jspdf-autotable"
]
}
\ No newline at end of file
[app]
debug = false
trace = false
[database]
hostname = 127.0.0.1
database = fastadmin
username = root
password = root
hostport = 3306
prefix = fa_
/runtime/*
/public/uploads/*
/public/api.html
.idea
composer.lock
*.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-2020 by FastAdmin (https://www.fastadmin.net)
All rights reserved。
deny from all
\ No newline at end of file
{"files":[],"license":"regular","licenseto":"10789","licensekey":"89rbzyMwR7egBFAo EyUfEJxJ9VFzYuZOURRwgg==","domains":[],"licensecodes":[],"validations":[]}
\ No newline at end of file
<?php
namespace addons\address;
use think\Addons;
/**
* 地址选择
* @author [MiniLing] <[laozheyouxiang@163.com]>
*/
class Address extends Addons
{
/**
* 插件安装方法
* @return bool
*/
public function install()
{
return true;
}
/**
* 插件卸载方法
* @return bool
*/
public function uninstall()
{
return true;
}
}
require([], function () {
//绑定data-toggle=addresspicker属性点击事件
$(document).on('click', "[data-toggle='addresspicker']", function () {
var that = this;
var callback = $(that).data('callback');
var input_id = $(that).data("input-id") ? $(that).data("input-id") : "";
var lat_id = $(that).data("lat-id") ? $(that).data("lat-id") : "";
var lng_id = $(that).data("lng-id") ? $(that).data("lng-id") : "";
var lat = lat_id ? $("#" + lat_id).val() : '';
var lng = lng_id ? $("#" + lng_id).val() : '';
var url = "/addons/address/index/select";
url += (lat && lng) ? '?lat=' + lat + '&lng=' + lng : '';
Fast.api.open(url, '位置选择', {
callback: function (res) {
input_id && $("#" + input_id).val(res.address).trigger("change");
lat_id && $("#" + lat_id).val(res.lat).trigger("change");
lng_id && $("#" + lng_id).val(res.lng).trigger("change");
try {
//执行回调函数
if (typeof callback === 'function') {
callback.call(that, res);
}
} catch (e) {
}
}
});
});
});
<?php
return [
[
'name' => 'maptype',
'title' => '默认地图类型',
'type' => 'radio',
'content' => [
'baidu' => '百度地图',
'amap' => '高德地图',
'tencent' => '腾讯地图',
],
'value' => 'tencent',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
[
'name' => 'location',
'title' => '默认检索城市',
'type' => 'string',
'content' => [],
'value' => '天津',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
[
'name' => 'zoom',
'title' => '默认缩放级别',
'type' => 'string',
'content' => [],
'value' => '12',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
[
'name' => 'lat',
'title' => '默认Lat',
'type' => 'string',
'content' => [],
'value' => '39.135927',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
[
'name' => 'lng',
'title' => '默认Lng',
'type' => 'string',
'content' => [],
'value' => '117.21023',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
[
'name' => 'baidukey',
'title' => '百度地图KEY',
'type' => 'string',
'content' => [],
'value' => 'hAeMFHmpyHa2ZjaCH9VVridl',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
[
'name' => 'amapkey',
'title' => '高德地图KEY',
'type' => 'string',
'content' => [],
'value' => '608d75903d29ad471362f8c58c550daf',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
[
'name' => 'tencentkey',
'title' => '腾讯地图KEY',
'type' => 'string',
'content' => [],
'value' => 'FBZBZ-OCF6W-5QXRV-O7G42-GZGRE-UNBWO',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => '',
],
[
'name' => '__tips__',
'title' => '温馨提示',
'type' => '',
'content' => [],
'value' => '请先申请对应地图的Key,配置后再使用',
'rule' => '',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => 'alert-danger-light',
],
];
<?php
namespace addons\address\controller;
use think\addons\Controller;
use think\Config;
use think\Hook;
class Index extends Controller
{
public function index()
{
// 语言检测
$lang = strip_tags($this->request->langset());
$site = Config::get("site");
// 配置信息
$config = [
'site' => array_intersect_key($site, array_flip(['name', 'cdnurl', 'version', 'timezone', 'languages'])),
'upload' => null,
'modulename' => 'addons',
'controllername' => 'index',
'actionname' => 'index',
'jsname' => 'addons/address',
'moduleurl' => '',
'language' => $lang
];
$config = array_merge($config, Config::get("view_replace_str"));
// 配置信息后
Hook::listen("config_init", $config);
// 加载当前控制器语言包
$this->view->assign('site', $site);
$this->view->assign('config', $config);
return $this->view->fetch();
}
/**
* 选择地址
* @return string
* @throws \think\Exception
*/
public function select()
{
$config = get_addon_config('address');
$lat = $this->request->get('lat', $config['lat']);
$lng = $this->request->get('lng', $config['lng']);
$this->view->assign('lat', $lat);
$this->view->assign('lng', $lng);
$this->view->assign('location', $config['location']);
return $this->view->fetch('index/' . $config['maptype']);
}
}
name = address
title = 地址选择
intro = 地图位置选择插件,可返回地址和经纬度
author = FastAdmin
website = https://www.fastadmin.net
version = 1.0.5
state = 1
url = /addons/address
license = regular
licenseto = 10789
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>地址选择器</title>
<link rel="stylesheet" href="__CDN__/assets/css/bootstrap.min.css"/>
<link rel="stylesheet" href="__CDN__/assets/css/fastadmin.min.css"/>
<link rel="stylesheet" href="__CDN__/assets/libs/font-awesome/css/font-awesome.min.css"/>
<style type="text/css">
body {
margin: 0;
padding: 0;
}
#container {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.confirm {
position: absolute;
bottom: 30px;
right: 4%;
z-index: 99;
height: 50px;
width: 50px;
line-height: 50px;
font-size: 15px;
text-align: center;
background-color: white;
background: #1ABC9C;
color: white;
border: none;
cursor: pointer;
border-radius: 50%;
}
.search {
position: absolute;
width: 400px;
top: 0;
left: 50%;
padding: 5px;
margin-left: -200px;
}
.amap-marker-label {
border: 0;
background-color: transparent;
}
.info {
padding: .75rem 1.25rem;
margin-bottom: 1rem;
border-radius: .25rem;
position: fixed;
top: 2rem;
background-color: white;
width: auto;
min-width: 22rem;
border-width: 0;
left: 1.8rem;
box-shadow: 0 2px 6px 0 rgba(114, 124, 245, .5);
}
</style>
</head>
<body>
<div class="search">
<div class="input-group">
<input type="text" id="place" name="q" class="form-control" placeholder="输入地点"/>
<span class="input-group-btn">
<button type="submit" name="search" id="search-btn" class="btn btn-success">
<i class="fa fa-search"></i>
</button>
</span>
</div>
</div>
<div class="confirm">确定</div>
<div id="container"></div>
<script type="text/javascript" src="//webapi.amap.com/maps?v=1.4.11&key={$config.amapkey|default=''}&plugin=AMap.ToolBar,AMap.Autocomplete,AMap.PlaceSearch,AMap.Geocoder"></script>
<!-- UI组件库 1.0 -->
<script src="//webapi.amap.com/ui/1.0/main.js?v=1.0.11"></script>
<script src="__CDN__/assets/libs/jquery/dist/jquery.min.js"></script>
<script type="text/javascript">
$(function () {
var as, address, map, lat, lng, geocoder;
var init = function () {
AMapUI.loadUI(['misc/PositionPicker', 'misc/PoiPicker'], function (PositionPicker, PoiPicker) {
//加载PositionPicker,loadUI的路径参数为模块名中 'ui/' 之后的部分
map = new AMap.Map('container', {
zoom: parseInt('{$config.zoom}')
});
geocoder = new AMap.Geocoder({
radius: 1000 //范围,默认:500
});
var positionPicker = new PositionPicker({
mode: 'dragMarker',//设定为拖拽地图模式,可选'dragMap'、'dragMarker',默认为'dragMap'
map: map//依赖地图对象
});
//输入提示
var autoOptions = {
input: "place"
};
var relocation = function (lnglat) {
lng = lnglat.lng;
lat = lnglat.lat;
map.panTo([lng, lat]);
positionPicker.start(lnglat);
geocoder.getAddress(lng + ',' + lat, function (status, result) {
if (status === 'complete' && result.regeocode) {
var address = result.regeocode.formattedAddress;
var label = '<div class="info">地址:' + address + '<br>经度:' + lng + '<br>纬度:' + lat + '</div>';
positionPicker.marker.setLabel({
content: label //显示内容
});
} else {
console.log(JSON.stringify(result));
}
});
};
var auto = new AMap.Autocomplete(autoOptions);
//构造地点查询类
var placeSearch = new AMap.PlaceSearch({
map: map
});
//注册监听,当选中某条记录时会触发
AMap.event.addListener(auto, "select", function (e) {
placeSearch.setCity(e.poi.adcode);
placeSearch.search(e.poi.name); //关键字查询查询
});
AMap.event.addListener(map, 'click', function (e) {
relocation(e.lnglat);
});
//加载工具条
var tool = new AMap.ToolBar();
map.addControl(tool);
var poiPicker = new PoiPicker({
input: 'place',
placeSearchOptions: {
map: map,
pageSize: 6 //关联搜索分页
}
});
poiPicker.on('poiPicked', function (poiResult) {
poiPicker.hideSearchResults();
$('.poi .nearpoi').text(poiResult.item.name);
$('.address .info').text(poiResult.item.address);
$('#address').val(poiResult.item.address);
$("#place").val(poiResult.item.name);
relocation(poiResult.item.location);
});
positionPicker.on('success', function (positionResult) {
as = positionResult.position;
address = positionResult.address;
lat = as.lat;
lng = as.lng;
});
positionPicker.on('fail', function (positionResult) {
address = '';
});
positionPicker.start();
});
};
//点击确定后执行回调赋值
var close = function (data) {
var index = parent.Layer.getFrameIndex(window.name);
var callback = parent.$("#layui-layer" + index).data("callback");
//再执行关闭
parent.Layer.close(index);
//再调用回传函数
if (typeof callback === 'function') {
callback.call(undefined, data);
}
};
//点击搜索按钮
$(document).on('click', '.confirm', function () {
var zoom = map.getZoom();
var data = {lat: lat, lng: lng, zoom: zoom, address: address};
close(data);
});
init();
});
</script>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>地址选择器</title>
<link rel="stylesheet" href="__CDN__/assets/css/bootstrap.min.css"/>
<link rel="stylesheet" href="__CDN__/assets/css/fastadmin.min.css"/>
<link rel="stylesheet" href="__CDN__/assets/libs/font-awesome/css/font-awesome.min.css"/>
<style type="text/css">
body {
margin: 0;
padding: 0;
}
#container {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.confirm {
position: absolute;
bottom: 30px;
right: 4%;
z-index: 99;
height: 50px;
width: 50px;
line-height: 50px;
font-size: 15px;
text-align: center;
background-color: white;
background: #1ABC9C;
color: white;
border: none;
cursor: pointer;
border-radius: 50%;
}
.search {
position: absolute;
width: 400px;
top: 0;
left: 50%;
padding: 5px;
margin-left: -200px;
}
label.BMapLabel {
max-width: inherit;
padding: .75rem 1.25rem;
margin-bottom: 1rem;
background-color: white;
width: auto;
min-width: 22rem;
border: none;
box-shadow: 0 2px 6px 0 rgba(114, 124, 245, .5);
}
</style>
</head>
<body>
<div class="search">
<div class="input-group">
<input type="text" id="place" name="q" class="form-control" placeholder="输入地点"/>
<div id="searchResultPanel" style="border:1px solid #C0C0C0;width:150px;height:auto; display:none;"></div>
<span class="input-group-btn">
<button type="button" name="search" id="address" class="btn btn-success">
<i class="fa fa-search"></i>
</button>
</span>
</div>
</div>
<div class="confirm">确定</div>
<div id="container"></div>
<script type="text/javascript" src="//api.map.baidu.com/api?v=2.0&ak={$config.baidukey|default=''}"></script>
<script src="__CDN__/assets/libs/jquery/dist/jquery.min.js"></script>
<script type="text/javascript">
$(function () {
// 百度地图API功能
function G(id) {
return document.getElementById(id);
}
var map, marker, searchService, address = null, lng, lat;
var init = function () {
map = new BMap.Map("container"); // 创建地图实例
var point = new BMap.Point({$lng}, {$lat}); // 创建点坐标
map.enableScrollWheelZoom(true); //开启鼠标滚轮缩放
map.centerAndZoom(point, parseInt("{$config.zoom}")); // 初始化地图,设置中心点坐标和地图级别
var size = new BMap.Size(10, 20);
map.addControl(new BMap.CityListControl({
anchor: BMAP_ANCHOR_TOP_LEFT,
offset: size,
}));
var geoc = new BMap.Geocoder();
var addpoint = function (point) {
//通过点击百度地图,可以获取到对应的point, 由point的lng、lat属性就可以获取对应的经度纬度
var pt = point;
geoc.getLocation(pt, function (rs) {
//对象可以获取到详细的地址信息
address = rs.address;
deletePoint();
var mk = new BMap.Marker(pt);
map.addOverlay(mk);
map.panTo(pt);
var label = new BMap.Label('<div class="info">地址:' + address + '<br>经度:' + pt.lng + '<br>纬度:' + pt.lat + '</div>', {offset: new BMap.Size(16, 20)});
label.setStyle({
border: 'none',
padding: '.75rem 1.25rem'
});
mk.setLabel(label);
//将对应的HTML元素设置值
lng = pt.lng;
lat = pt.lat;
});
};
if ("{$lng}" != '' && "{$lat}" != '') {
addpoint(point);
}
ac = new BMap.Autocomplete({"input": "place", "location": map}); //建立一个自动完成的对象
ac.addEventListener("onhighlight", function (e) { //鼠标放在下拉列表上的事件
var str = "";
var _value = e.fromitem.value;
var value = "";
if (e.fromitem.index > -1) {
value = _value.province + _value.city + _value.district + _value.street + _value.business;
}
str = "FromItem<br />index = " + e.fromitem.index + "<br />value = " + value;
value = "";
if (e.toitem.index > -1) {
_value = e.toitem.value;
value = _value.province + _value.city + _value.district + _value.street + _value.business;
}
str += "<br />ToItem<br />index = " + e.toitem.index + "<br />value = " + value;
G("searchResultPanel").innerHTML = str;
});
ac.addEventListener("onconfirm", function (e) { //鼠标点击下拉列表后的事件
var _value = e.item.value;
myValue = _value.province + _value.city + _value.district + _value.street + _value.business;
G("searchResultPanel").innerHTML = "onconfirm<br />index = " + e.item.index + "<br />myValue = " + myValue;
setPlace();
});
function setPlace() {
map.clearOverlays(); //清除地图上所有覆盖物
function myFun() {
var result = local.getResults().getPoi(0);
var pp = result.point; //获取第一个智能搜索的结果
map.centerAndZoom(pp, 18);
map.addOverlay(new BMap.Marker(pp)); //添加标注
lng = pp.lng;
lat = pp.lat;
address = result.address;
}
var local = new BMap.LocalSearch(map, { //智能搜索
onSearchComplete: myFun
});
local.search(myValue);
}
map.addEventListener("click", function (e) {
//通过点击百度地图,可以获取到对应的point, 由point的lng、lat属性就可以获取对应的经度纬度
var pt = e.point;
addpoint(e.point);
});
/**
* 清除覆盖物
*/
function deletePoint() {
var allOverlay = map.getOverlays();
for (var i = 0; i < allOverlay.length; i++) {
map.removeOverlay(allOverlay[i]);
}
}
};
var close = function (data) {
var index = parent.Layer.getFrameIndex(window.name);
var callback = parent.$("#layui-layer" + index).data("callback");
//再执行关闭
parent.Layer.close(index);
//再调用回传函数
if (typeof callback === 'function') {
callback.call(undefined, data);
}
};
//点击确定后执行回调赋值
$(document).on('click', '.confirm', function () {
var zoom = map.getZoom();
var data = {lat: lat, lng: lng, zoom: zoom, address: address};
close(data);
});
init();
});
</script>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
<title>地图位置(经纬度)选择插件 - FastAdmin</title>
<!-- Bootstrap Core CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom CSS -->
<link href="https://cdn.demo.fastadmin.net/assets/css/frontend.css" rel="stylesheet">
<!-- Plugin CSS -->
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="container">
<div class="bs-docs-section clearfix">
<div class="row">
<div class="col-lg-12">
<div class="page-header">
<h2 id="navbar">地图位置(经纬度)选择示例</h2>
</div>
<div class="bs-component">
<form action="" method="post" role="form">
<div class="form-group">
<label for=""></label>
<input type="text" class="form-control" name="" id="address" placeholder="地址">
</div>
<div class="form-group">
<label for=""></label>
<input type="text" class="form-control" name="" id="lng" placeholder="经度">
</div>
<div class="form-group">
<label for=""></label>
<input type="text" class="form-control" name="" id="lat" placeholder="纬度">
</div>
<button type="button" class="btn btn-primary" data-toggle='addresspicker' data-input-id="address" data-lng-id="lng" data-lat-id="lat">点击选择地址获取经纬度</button>
</form>
</div>
<div class="page-header">
<h2 id="code">调用代码</h2>
</div>
<div class="bs-component">
<textarea class="form-control" rows="17">
<form action="" method="post" role="form">
<div class="form-group">
<label for=""></label>
<input type="text" class="form-control" name="" id="address" placeholder="地址">
</div>
<div class="form-group">
<label for=""></label>
<input type="text" class="form-control" name="" id="lng" placeholder="经度">
</div>
<div class="form-group">
<label for=""></label>
<input type="text" class="form-control" name="" id="lat" placeholder="纬度">
</div>
<button type="button" class="btn btn-primary" data-toggle='addresspicker' data-input-id="address" data-lng-id="lng" data-lat-id="lat">点击选择地址获取经纬度</button>
</form>
</textarea>
</div>
<div class="page-header">
<h2 id="navbar">参数说明</h2>
</div>
<div class="bs-component">
<table class="table table-condensed table-hover">
<thead>
<tr>
<th>参数</th>
<th>释义</th>
</tr>
</thead>
<tbody>
<tr>
<td>data-input-id</td>
<td>填充地址的文本框ID</td>
</tr>
<tr>
<td>data-lng-id</td>
<td>填充经度的文本框ID</td>
</tr>
<tr>
<td>data-lat-id</td>
<td>填充纬度的文本框ID</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
var require = {
config: {$config | json_encode
}
}
;
</script>
<script>
require.callback = function () {
define('addons/address', ['jquery', 'bootstrap', 'frontend', 'template'], function ($, undefined, Frontend, Template) {
var Controller = {
index: function () {
;
}
};
return Controller;
});
define('lang', function () {
return [];
});
}
</script>
<script src="__CDN__/assets/js/require.min.js" data-main="__CDN__/assets/js/require-frontend.min.js?v={$site.version}"></script>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<title>地址选择器</title>
<link rel="stylesheet" href="__CDN__/assets/css/bootstrap.min.css"/>
<link rel="stylesheet" href="__CDN__/assets/css/fastadmin.min.css"/>
<link rel="stylesheet" href="__CDN__/assets/libs/font-awesome/css/font-awesome.min.css"/>
<style type="text/css">
body {
margin: 0;
padding: 0;
}
#container {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.confirm {
position: absolute;
bottom: 30px;
right: 4%;
z-index: 99;
height: 50px;
width: 50px;
line-height: 50px;
font-size: 15px;
text-align: center;
background-color: white;
background: #1ABC9C;
color: white;
border: none;
cursor: pointer;
border-radius: 50%;
}
.search {
position: absolute;
width: 400px;
top: 0;
left: 50%;
padding: 5px;
margin-left: -200px;
}
</style>
</head>
<body>
<div class="search">
<div class="input-group">
<input type="text" id="place" name="q" class="form-control" placeholder="输入地点"/>
<span class="input-group-btn">
<button type="submit" name="search" id="search-btn" class="btn btn-success">
<i class="fa fa-search"></i>
</button>
</span>
</div>
</div>
<div class="confirm">确定</div>
<div id="container"></div>
<script charset="utf-8" src="//map.qq.com/api/js?v=2.exp&libraries=place&key={$config.tencentkey|default=''}"></script>
<script src="__CDN__/assets/libs/jquery/dist/jquery.min.js"></script>
<script type="text/javascript">
$(function () {
var map, marker, geocoder, infoWin, searchService, address = null;
var init = function () {
var center = new qq.maps.LatLng({$lat}, {$lng});
map = new qq.maps.Map(document.getElementById('container'), {
center: center,
zoom: parseInt("{$config.zoom}")
});
//初始化marker
initmarker(center);
//实例化信息窗口
infoWin = new qq.maps.InfoWindow({
map: map
});
geocoder = new qq.maps.Geocoder({
complete: function (result) {
infoWin.open();
address = result.detail.addressComponents.province +
result.detail.addressComponents.city +
result.detail.addressComponents.district;
if (result.detail.addressComponents.streetNumber == '') {
address += result.detail.addressComponents.street;
} else {
address += result.detail.addressComponents.streetNumber;
}
infoWin.setContent(address);
infoWin.setPosition(result.detail.location);
}
});
//显示当前marker的位置信息窗口
geocoder.getAddress(center);
var latlngBounds = new qq.maps.LatLngBounds();
//查询poi类信息
searchService = new qq.maps.SearchService({
complete: function (results) {
var pois = results.detail.pois;
for (var i = 0, l = pois.length; i < l; i++) {
var poi = pois[i];
latlngBounds.extend(poi.latLng);
initmarker(poi.latLng);
//显示当前marker的位置信息窗口
geocoder.getAddress(poi.latLng);
}
map.fitBounds(latlngBounds);
}
});
//实例化自动完成
var ap = new qq.maps.place.Autocomplete(document.getElementById('place'));
//添加监听事件
qq.maps.event.addListener(ap, "confirm", function (res) {
searchKeyword();
});
qq.maps.event.addListener(
map,
'click',
function (event) {
try {
infoWin.setContent('<div style="text-align:center;white-space:nowrap;margin:10px;">加载中</div>');
var latLng = event.latLng,
lat = latLng.getLat().toFixed(5),
lng = latLng.getLng().toFixed(5);
var location = new qq.maps.LatLng(lat, lng);
//调用获取位置方法
geocoder.getAddress(location);
infoWin.setPosition(location);
marker.setPosition(location);
} catch (e) {
console.log(e);
}
}
);
};
//实例化marker和监听拖拽结束事件
var initmarker = function (latLng) {
marker = new qq.maps.Marker({
map: map,
position: latLng,
draggable: true,
title: '拖动图标选择位置'
});
//监听拖拽结束
qq.maps.event.addListener(marker, 'dragend', function (event) {
var latLng = event.latLng,
lat = latLng.getLat().toFixed(5),
lng = latLng.getLng().toFixed(5);
var location = new qq.maps.LatLng(lat, lng);
//调用获取位置方法
geocoder.getAddress(location);
});
};
var close = function (data) {
var index = parent.Layer.getFrameIndex(window.name);
var callback = parent.$("#layui-layer" + index).data("callback");
//再执行关闭
parent.Layer.close(index);
//再调用回传函数
if (typeof callback === 'function') {
callback.call(undefined, data);
}
};
//执行搜索方法
var searchKeyword = function () {
searchService.clear();//先清除
marker.setMap(null);
infoWin.close();
var keyword = $("#place").val();
searchService.setLocation("{$location}");//设置默认检索范围(默认为全国),类型可以是坐标或指定的城市名称。
searchService.setPageIndex(0);//设置检索的特定页数。
searchService.setPageCapacity(1);//设置每页返回的结果数量。
searchService.search(keyword);//开始查询
};
//点击确定后执行回调赋值
$(document).on('click', '.confirm', function () {
var as = marker.getPosition();
var x = as.getLat().toFixed(5);
var y = as.getLng().toFixed(5);
var zoom = map.getZoom();
var data = {lat: x, lng: y, zoom: zoom, address: address};
close(data);
});
//点击搜索按钮
$(document).on('click', '#search-btn', function () {
if ($("#place").val() == '')
return;
searchKeyword();
});
init();
});
</script>
</body>
</html>
\ 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":"ogGuqfEhWU5smPv7 ZCMeDKwDoQx8JuH\/Lrj4FA==","domains":[],"licensecodes":[],"validations":[],"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
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":"v9xQbPO0C1w4Mnau dX3mcbJWxJ80kiBhb42x1Q==","domains":[],"licensecodes":[],"validations":[]}
\ 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-----
MIID8DCCAtigAwIBAgIUVNHUlZ6YeeUOeQu96LV6UjGfLHkwDQYJKoZIhvcNAQEL
BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT
FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg
Q0EwHhcNMjIwMTEyMDc1MDM3WhcNMjcwMTExMDc1MDM3WjCBgTETMBEGA1UEAwwK
MTYxOTE5MzEzMDEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMS0wKwYDVQQL
DCTotLXlt57mqZnlrZDnvZHnu5znp5HmioDmnInpmZDlhazlj7gxCzAJBgNVBAYM
AkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAMUHCGGVLxl2AJdtGkAB7Ck/V4oLoOZAR0ZtCSEPhuBa840nl1RTt55O
KT8ZiRneZoBJ5yoegeDWz4gHjGXYkctIiwmmnTHDtdFhcnKvJMW58z3ZA0xAUrG1
hJSepiQtsaFJSg+XLOCL3773rO9PqwH/vSfpkipqxUapmYKWAYrWrUjyi+BX9hBv
9GhcATO6DSySMkBm1oAhngA3657WGdyU7YzihVn3IL7X12gjdYoE/DfV1g8Dw6ze
aN/hSu8GZ+V2+Npn99AJKRSu08EFgw5o7cMacGNhTDPQsc5CXHN/TKpI7cM0FtoP
BFTh0uo10ebE2eOIe7Nd2RA6I+wPn2kCAwEAAaOBgTB/MAkGA1UdEwQCMAAwCwYD
VR0PBAQDAgTwMGUGA1UdHwReMFwwWqBYoFaGVGh0dHA6Ly9ldmNhLml0cnVzLmNv
bS5jbi9wdWJsaWMvaXRydXNjcmw/Q0E9MUJENDIyMEU1MERCQzA0QjA2QUQzOTc1
NDk4NDZDMDFDM0U4RUJEMjANBgkqhkiG9w0BAQsFAAOCAQEALwQJsqW/yOqlk2mj
CGuASrOpdUZE22bpTw4ajshd+x+/3d4JYX8f7g7CfoJm44hs97lu3FqBr2akUJOo
xmf1RG0hEVzqy5o/rUvKKay2/gIky+m0tKNAlp2aPITFbiwaENckpc0EpcjB7I+W
FuGOR0V35pOxAH9RlGIQcyYUIqOEtm9omD06DPNG8YuBZxynBruajRXVxVznZAeU
zIWYGe3V6Im9r7ElGSI7AKrFXY4XVbUf0VFKEaw39AZdDqMDhA+JdGSQWJpIaPCV
QwAGMfob/+gsJ7SFOBx8uGyStsfGMWpuQqTAfHp0h3dSqKQWSp66V9VeglWbiZ5o
w/haRA==
-----END CERTIFICATE-----
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDFBwhhlS8ZdgCX
bRpAAewpP1eKC6DmQEdGbQkhD4bgWvONJ5dUU7eeTik/GYkZ3maASecqHoHg1s+I
B4xl2JHLSIsJpp0xw7XRYXJyryTFufM92QNMQFKxtYSUnqYkLbGhSUoPlyzgi9++
96zvT6sB/70n6ZIqasVGqZmClgGK1q1I8ovgV/YQb/RoXAEzug0skjJAZtaAIZ4A
N+ue1hnclO2M4oVZ9yC+19doI3WKBPw31dYPA8Os3mjf4UrvBmfldvjaZ/fQCSkU
rtPBBYMOaO3DGnBjYUwz0LHOQlxzf0yqSO3DNBbaDwRU4dLqNdHmxNnjiHuzXdkQ
OiPsD59pAgMBAAECggEAOPuBtwc4afqwzRqmvuBPdtZ08N3QGQd6FaovO7qL3ZMw
YSCUKbVUo5ojX7Z46lxh9LRrAMl18prtxn+4YTQzZkGCxLXFPMFHcljmiriQwAa5
jKeYxpLL182RnJINpuT4PJc/riagugh6G3byMvwtjxRxlnt85HugtoESfqv3ojlW
AHcEVTUNxZZP4FkjZuPcfghq93jIILxuP/ancCtoe53VUDLiiB35XiChYyQZ+CDN
d8sQh2uJ/o/Ptte10f5XiX4CHTj54S9J0tWBIFkbk/jLLBzHsEznlgQdb4tIF4LO
cPsCyItHCA7uUCGy3C8tfC+qpZS2FGaA2EmoXxJEqQKBgQDxgJuwcHWQPC1/mbL3
bsaV+j3xIhVb2zWg54WuEUadOYxa5zrlS5ZCd5kMmrnv0Ssw5ZSZC0rL7CvwoW0U
QQeo4mIxrq0jErBobzSOa2S/UgKBAsvLk7HR2Xzl94xiIeDJdK1fGPsT1OdP28Ly
1i7WAYcN/efsRzzk/NwuvaseswKBgQDQ2u/0a7uRM/b2fJTxrG7zBkk4O5OVsMXc
Pl3qJ0danSrd0ryF8sO3BwJKR1rP+iFUWVoc3OCTj54ZLVzWrietRFK5Phxb9p//
o3XzB1CXeXUas4D4pPF4vyScwpOfWytpakNPTIqmbek/FprZu1gG2B/outIUSH2y
zVfYDTZXcwKBgQCE5hJHQUvg6Mzc+Fb9RQ+xfvlRVkFMwFA90MrG5KjoYr/zUmYd
wr/Yqfombos+BuxuxxhENGuf1sLDdAIT8ElnzdTdpFORBnrfrbrNWRojEt1f44sE
560mXzoVT2oIor4R/sxdleCtpC76ymP4wJcbm9GXiI91fiprlR8R9bxScwKBgD5K
/sTdGR2u7RsJf3cxogn0NwSBqHw+NFWDeIltW7foJq/wufANv1gozAMRk0Fy7lv1
Jo5zRrxcWqBRyl3xpb3lIfNEVjsLrR8XRwfMh7oWLNpg7be7opYMFMR4uIUQMTuI
yTiusLABGk+J03fbi97GuYW1qxEHqg4Zdvjmv+2HAoGBALWGekcrImaNFHodLN9T
L9iRcX9701d/U2OLU1RuyXTV3SaX9sgX2hz4G/QGopZvk9Vp3s0ZaaktvfKHfsmZ
CF4uYDFX+kqqAXqwkgnJXLqLq1LViV0df5V0KTnvCDYaqxURKTMKO6doLOBv/6uo
SEJ/RoWGTYYbsIm5PpRkjbB5
-----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' => '',
'app_secret' => '',
'miniapp_id' => 'wx3fb93805fc3f459d',
'mch_id' => '1619193130',
'key' => 'chengyigou0123456789012345678901',
'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();
}