first commit

This commit is contained in:
李志强 2026-04-15 20:16:52 +08:00
commit 8f45044411
1172 changed files with 329978 additions and 0 deletions

15
.editorconfig Normal file
View File

@ -0,0 +1,15 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2

48
.env.example Normal file
View File

@ -0,0 +1,48 @@
APP_NAME={title}
APP_ENV=local
APP_KEY={app_key}
APP_DEBUG=true
APP_URL={app_url}
LOG_CHANNEL=stack
# 数据库配置
DB_CONNECTION=mysql
DB_HOST={db_host}
DB_PORT={db_port}
DB_DATABASE={db_database}
DB_USERNAME={db_username}
DB_PASSWORD={db_password}
# redis配置
REDIS_HOST={redis_host}
REDIS_PASSWORD={redis_password}
REDIS_PORT={redis_port}
BROADCAST_DRIVER=log
SESSION_DRIVER=file
SESSION_LIFETIME=120
# 缓存配置
# file为磁盘文件 redis为内存级别
# redis为内存需要安装好redis服务端并配置
CACHE_DRIVER=redis
# 异步消息队列
# sync为同步 redis为异步
# 使用redis异步需要安装好redis服务端并配置
QUEUE_CONNECTION=redis
# 后台语言
## zh_CN 简体中文
## zh_TW 繁体中文
## en 英文
DUJIAO_ADMIN_LANGUAGE=zh_CN
# 后台登录地址
ADMIN_ROUTE_PREFIX={admin_path}
# 是否开启https (前端开启了后端也必须为true)
# 后台登录出现0err或者其他登录异常问题大概率是开启了https而后台没有开启把下面的false改为true即可
ADMIN_HTTPS=false

5
.gitattributes vendored Normal file
View File

@ -0,0 +1,5 @@
* text=auto
*.css linguist-vendored
*.scss linguist-vendored
*.js linguist-vendored
CHANGELOG.md export-ignore

16
.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
/node_modules
/public/hot
/public/storage
/public/uploads
/storage/*.key
/vendor
.env
.env.backup
.phpunit.result.cache
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
.idea
install.lock
.env.buck

13
.styleci.yml Normal file
View File

@ -0,0 +1,13 @@
php:
preset: laravel
disabled:
- unused_use
finder:
not-name:
- index.php
- server.php
js:
finder:
not-name:
- webpack.mix.js
css: true

7
Dockerfile Normal file
View File

@ -0,0 +1,7 @@
FROM webdevops/php-nginx:7.4
COPY . /app
WORKDIR /app
RUN [ "sh", "-c", "composer install --ignore-platform-reqs" ]
RUN echo "#!/bin/bash\nphp artisan queue:work >/tmp/work.log 2>&1 &\nsupervisord" > /app/start.sh
RUN [ "sh", "-c", "chmod -R 777 /app" ]
CMD [ "sh", "-c","/app/start.sh" ]

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 assimon/dujiaoka
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

18
README.md Normal file
View File

@ -0,0 +1,18 @@
<p align="center"><img src="https://i.loli.net/2020/04/07/nAzjDJlX7oc5qEw.png" width="400"></p>
<p align="center">
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-blue" alt="license MIT"></a>
<a href="https://github.com/assimon/dujiaoka/releases/tag/2.0.4"><img src="https://img.shields.io/badge/version-2.0.4-red" alt="version 2.0.4"></a>
<a href="https://www.php.net/releases/7_4_0.php"><img src="https://img.shields.io/badge/PHP-7.4-lightgrey" alt="php74"></a>
</p>
<div align="center">
<a href="https://www.vmrack.net/?ref_code=5iXmGUMf5f5">
<img src="https://files.seeusercontent.com/2026/03/12/2mXf/photo_2026-03-12_10-13-12.jpg" width="400">
</a>
<p align="center"><a href="https://www.vmrack.net/?ref_code=5iXmGUMf5f5">vmrack.net - 全球自动化云基础设施服务商
提供先进的云服务器、裸金属、CDN、媒体处理、对象存储和网络解决方案助力企业轻松上云</a></p>
</div>
## 📢停更通知2026/02/12
### 项目已经停止更新和维护,请前往新版[Dujiao-Next(dujiao-next.com)](https://dujiao-next.com)

View File

@ -0,0 +1,53 @@
<?php
/**
* The file was created by Assimon.
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
namespace App\Admin\Actions\Post;
use Dcat\Admin\Grid\BatchAction;
use Illuminate\Http\Request;
class BatchRestore extends BatchAction
{
protected $title;
protected $model;
// 注意构造方法的参数必须要有默认值
public function __construct(string $model = null)
{
$this->title = admin_trans('dujiaoka.restore');
$this->model = $model;
}
public function handle(Request $request)
{
$model = $request->get('model');
foreach ((array) $this->getKey() as $key) {
$model::withTrashed()->findOrFail($key)->restore();
}
return $this->response()->success(admin_trans('dujiaoka.restored'))->refresh();
}
public function confirm()
{
return [admin_trans('dujiaoka.are_you_restore_sure')];
}
public function parameters()
{
return [
'model' => $this->model,
];
}
}

View File

@ -0,0 +1,52 @@
<?php
/**
* The file was created by Assimon.
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
namespace App\Admin\Actions\Post;
use Dcat\Admin\Grid\RowAction;
use Illuminate\Http\Request;
class Restore extends RowAction
{
protected $title;
protected $model;
// 注意构造方法的参数必须要有默认值
public function __construct(string $model = null)
{
$this->title = admin_trans('dujiaoka.restore');
$this->model = $model;
}
public function handle(Request $request)
{
$key = $this->getKey();
$model = $request->get('model');
$model::withTrashed()->findOrFail($key)->restore();
return $this->response()->success(admin_trans('dujiaoka.restored'))->refresh();
}
public function confirm()
{
return [admin_trans('dujiaoka.are_you_restore_sure')];
}
public function parameters()
{
return [
'model' => $this->model,
];
}
}

View File

@ -0,0 +1,176 @@
<?php
/**
* The file was created by Assimon.
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
namespace App\Admin\Charts;
use App\Models\Order;
use Dcat\Admin\Widgets\Metrics\RadialBar;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
class DashBoard extends RadialBar
{
/**
* 初始化卡片内容
*/
protected function init()
{
parent::init();
$this->title(admin_trans('dujiaoka.sales_data'));
$this->height(400);
$this->chartHeight(300);
$this->chartLabels(admin_trans('dujiaoka.order_success_rate'));
$this->dropdown([
'today' => admin_trans('dujiaoka.last_today'),
'seven' => admin_trans('dujiaoka.last_seven_days'),
'month' => admin_trans('dujiaoka.last_month'),
'year' => admin_trans('dujiaoka.last_year'),
]);
}
/**
* 处理请求
*
* @param Request $request
*
* @return mixed|void
*/
public function handle(Request $request)
{
$endTime = Carbon::now();
switch ($request->get('option')) {
case 'seven':
$startTime = Carbon::now()->subDays(7);
break;
case 'month':
$startTime = Carbon::now()->subDays(30);
break;
case 'year':
$startTime = Carbon::now()->subDays(365);
break;
case 'today':
default:
$startTime = Carbon::today();
}
// 分组查询
$orderGroup = Order::query()
->where('created_at', '>=', $startTime)
->where('created_at', '<=', $endTime)
->select('status', DB::raw('count(id) as num'))
->groupBy('status')
->pluck('num', 'status')
->toArray();
$pending = $orderGroup[Order::STATUS_PENDING] ?? 0;
$processing = $orderGroup[Order::STATUS_PROCESSING] ?? 0;
$completed = $orderGroup[Order::STATUS_COMPLETED] ?? 0;
$failure = $orderGroup[Order::STATUS_FAILURE] ?? 0;
$abnormal = $orderGroup[Order::STATUS_ABNORMAL] ?? 0;
$orderCount = array_sum($orderGroup);
if ($orderCount == 0) {
$successRate = 0;
} else {
$rate = bcdiv($completed, $orderCount, 2);
$successRate = bcmul($rate, 100);
}
// 订单数
$this->withOrderCount($orderCount);
// 卡片底部
$this->withFooter($pending, $processing, $completed, $failure, $abnormal);
// 图表数据
$this->withChart($successRate);
}
/**
* 订单总数
*
* @param $count
* @return DashBoard
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function withOrderCount($count)
{
$title = admin_trans('dujiaoka.order_count_number');
return $this->content(
<<<HTML
<div class="d-flex flex-column flex-wrap text-center">
<h1 class="font-lg-2 mt-2 mb-0">{$count}</h1>
<small>{$title}</small>
</div>
HTML
);
}
/**
* 成交率.
*
* @param int $data
*
* @return $this
*/
public function withChart(int $data)
{
return $this->chart([
'series' => [$data],
]);
}
/**
* @param $pending
* @param $processing
* @param $completed
* @param $failure
* @param $abnormal
* @return DashBoard
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function withFooter($pending, $processing, $completed, $failure, $abnormal)
{
$statusPendingTitle = admin_trans('dujiaoka.status_pending_number');
$statusProcessingNumber = admin_trans('dujiaoka.status_processing_number');
$statusCompletedNumber = admin_trans('dujiaoka.status_completed_number');
$statusFailureNumber = admin_trans('dujiaoka.status_failure_number');
$statusAbnormalNumber = admin_trans('dujiaoka.status_abnormal_number');
return $this->footer(
<<<HTML
<div class="d-flex justify-content-between p-1" style="padding-top: 0!important;">
<div class="text-center">
<p>{$statusPendingTitle}</p>
<span class="font-lg-1">{$pending}</span>
</div>
<div class="text-center">
<p>{$statusProcessingNumber}</p>
<span class="font-lg-1">{$processing}</span>
</div>
<div class="text-center">
<p>{$statusCompletedNumber}</p>
<span class="font-lg-1">{$completed}</span>
</div>
<div class="text-center">
<p>{$statusFailureNumber}</p>
<span class="font-lg-1">{$failure}</span>
</div>
<div class="text-center">
<p>{$statusAbnormalNumber}</p>
<span class="font-lg-1">{$abnormal}</span>
</div>
</div>
HTML
);
}
}

View File

@ -0,0 +1,137 @@
<?php
/**
* The file was created by Assimon.
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
namespace App\Admin\Charts;
use App\Models\Order;
use Dcat\Admin\Admin;
use Dcat\Admin\Widgets\Metrics\Donut;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
class PayoutRateCard extends Donut
{
protected $labels;
/**
* 初始化卡片内容
*/
protected function init()
{
parent::init();
$this->labels = [admin_trans('dujiaoka.payment_successful_number'), admin_trans('dujiaoka.unpaid_number')];
$color = Admin::color();
$colors = [$color->primary(), $color->alpha('blue2', 0.5)];
$this->title(admin_trans('dujiaoka.payment_chart'));
$this->chartLabels($this->labels);
// 设置图表颜色
$this->chartColors($colors);
$this->dropdown([
'seven' => admin_trans('dujiaoka.last_seven_days'),
'today' => admin_trans('dujiaoka.last_today'),
'month' => admin_trans('dujiaoka.last_month'),
'year' => admin_trans('dujiaoka.last_year'),
]);
}
/**
* 处理请求
*
* @param Request $request
*
* @return mixed|void
*/
public function handle(Request $request)
{
$endTime = Carbon::now();
switch ($request->get('option')) {
case 'seven':
$startTime = Carbon::now()->subDays(7);
break;
case 'month':
$startTime = Carbon::now()->subDays(30);
break;
case 'year':
$startTime = Carbon::now()->subDays(365);
break;
case 'today':
$startTime = Carbon::today();
break;
default:
$startTime = Carbon::now()->subDays(7);
}
// 成功的数量
$success = Order::query()
->where('created_at', '>=', $startTime)
->where('created_at', '<=', $endTime)
->where('status', '>', Order::STATUS_WAIT_PAY)
->count();
// 待支付的数量
$unpaid = Order::query()
->where('created_at', '>=', $startTime)
->where('created_at', '<=', $endTime)
->where('status', '<=', Order::STATUS_WAIT_PAY)
->count();
$this->withContent($success, $unpaid);
// 图表数据
$this->withChart([$success, $unpaid]);
}
/**
* 设置图表数据.
*
* @param array $data
*
* @return $this
*/
public function withChart(array $data)
{
return $this->chart([
'series' => $data
]);
}
/**
* 设置卡片头部内容.
*
* @param mixed $success
* @param mixed $unpaid
*
* @return $this
*/
protected function withContent($success, $unpaid)
{
$blue = Admin::color()->alpha('blue2', 0.5);
$style = 'margin-bottom: 8px';
$labelWidth = 120;
return $this->content(
<<<HTML
<div class="d-flex pl-1 pr-1 pt-1" style="{$style}">
<div style="width: {$labelWidth}px">
<i class="fa fa-circle text-primary"></i> {$this->labels[0]}
</div>
<div>{$success}</div>
</div>
<div class="d-flex pl-1 pr-1" style="{$style}">
<div style="width: {$labelWidth}px">
<i class="fa fa-circle" style="color: $blue"></i> {$this->labels[1]}
</div>
<div>{$unpaid}</div>
</div>
HTML
);
}
}

View File

@ -0,0 +1,139 @@
<?php
/**
* The file was created by Assimon.
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
namespace App\Admin\Charts;
use App\Models\Order;
use Dcat\Admin\Admin;
use Dcat\Admin\Widgets\Metrics\Bar;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
class SalesCard extends Bar
{
/**
* 初始化卡片内容
*/
protected function init()
{
parent::init();
$color = Admin::color();
$dark35 = $color->dark35();
// 卡片内容宽度
$this->contentWidth(5, 7);
// 标题
$this->title(admin_trans('dujiaoka.sales_chart'));
// 设置下拉选项
$this->dropdown([
'seven' => admin_trans('dujiaoka.last_seven_days'),
'today' => admin_trans('dujiaoka.last_today'),
'month' => admin_trans('dujiaoka.last_month'),
]);
// 设置图表颜色
$this->chartColors([
$dark35,
$color->primary(),
]);
}
/**
* 处理请求
*
* @param Request $request
*
* @return mixed|void
*/
public function handle(Request $request)
{
$endTime = Carbon::now();
switch ($request->get('option')) {
case 'seven':
$startTime = Carbon::now()->subDays(7);
break;
case 'month':
$startTime = Carbon::now()->subDays(30);
break;
case 'today':
$startTime = Carbon::today();
break;
default:
$startTime = Carbon::now()->subDays(7);
}
// 分组查询
$orderGroup = Order::query()
->where('created_at', '>=', $startTime)
->where('created_at', '<=', $endTime)
->where('status', '>', Order::STATUS_PENDING)
->select(DB::raw('DATE(created_at) as date'), DB::raw('sum(actual_price) as actual_price'))
->groupBy('date')
->pluck('actual_price')
->toArray();
$totalPrice = array_sum($orderGroup);
$this->withContent($totalPrice, '', '', $startTime, $endTime);
$this->withChart([
[
'name' => admin_trans('dujiaoka.sales_chart'),
'data' => $orderGroup,
]
]);
}
/**
* 设置图表数据.
*
* @param array $data
*
* @return $this
*/
public function withChart(array $data)
{
return $this->chart([
'series' => $data,
]);
}
/**
* 设置卡片内容.
*
* @param string $title
* @param string $value
* @param string $style
*
* @return $this
*/
public function withContent($title, $value, $style = 'success', $startTime, $endTime)
{
$minHeight = '183px';
$uri = admin_route('order.index', [
'created_at[start]' => $startTime->format('Y-m-d H:i:s'),
'created_at[end]' => $endTime->format('Y-m-d H:i:s')
]);
return $this->content(
<<<HTML
<div class="d-flex p-1 flex-column justify-content-between" style="padding-top: 0;width: 100%;height: 100%;min-height: {$minHeight}">
<div class="text-left">
<h1 class="font-lg-2 mt-2 mb-0">{$title}</h1>
<h5 class="font-medium-2" style="margin-top: 10px;">
<span class="text-{$style}">{$value} </span>
</h5>
</div>
<a href="{$uri}" class="btn btn-primary shadow waves-effect waves-light">View Details <i class="feather icon-chevrons-right"></i></a>
</div>
HTML
);
}
}

View File

@ -0,0 +1,116 @@
<?php
/**
* The file was created by Assimon.
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
namespace App\Admin\Charts;
use App\Models\Order;
use Dcat\Admin\Widgets\Metrics\Line;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
class SuccessOrderCard extends Line
{
/**
* 初始化卡片内容
*
* @return void
*/
protected function init()
{
parent::init();
$this->title(admin_trans('dujiaoka.status_completed_number'));
$this->dropdown([
'seven' => admin_trans('dujiaoka.last_seven_days'),
'today' => admin_trans('dujiaoka.last_today'),
'month' => admin_trans('dujiaoka.last_month'),
]);
}
/**
* 处理请求
*
* @param Request $request
*
* @return mixed|void
*/
public function handle(Request $request)
{
$endTime = Carbon::now();
switch ($request->get('option')) {
case 'seven':
$startTime = Carbon::now()->subDays(7);
break;
case 'month':
$startTime = Carbon::now()->subDays(30);
break;
case 'today':
$startTime = Carbon::today();
break;
default:
$startTime = Carbon::now()->subDays(7);
}
// 分组查询
$orderGroup = Order::query()
->where('created_at', '>=', $startTime)
->where('created_at', '<=', $endTime)
->where('status', Order::STATUS_COMPLETED)
->select(DB::raw('DATE(created_at) as date'), DB::raw('count(id) as num'))
->groupBy('date')
->pluck('num')
->toArray();
$successCount = array_sum($orderGroup);
// 卡片内容
$this->withContent($successCount);
// 图表数据
$this->withChart($orderGroup);
}
/**
* 设置图表数据.
*
* @param array $data
*
* @return $this
*/
public function withChart(array $data)
{
return $this->chart([
'series' => [
[
'name' => $this->title,
'data' => $data,
],
],
]);
}
/**
* 设置卡片内容.
*
* @param string $content
*
* @return $this
*/
public function withContent($content)
{
return $this->content(
<<<HTML
<div class="d-flex justify-content-between align-items-center mt-1" style="margin-bottom: 2px">
<h2 class="ml-1 font-lg-1">{$content}</h2>
<span class="mb-0 mr-1 text-80">{$this->title}</span>
</div>
HTML
);
}
}

View File

@ -0,0 +1,9 @@
<?php
namespace App\Admin\Controllers;
use Dcat\Admin\Http\Controllers\AuthController as BaseAuthController;
class AuthController extends BaseAuthController
{
}

View File

@ -0,0 +1,124 @@
<?php
namespace App\Admin\Controllers;
use App\Admin\Actions\Post\BatchRestore;
use App\Admin\Actions\Post\Restore;
use App\Admin\Forms\ImportCarmis;
use App\Admin\Repositories\Carmis;
use App\Models\Goods;
use Dcat\Admin\Form;
use Dcat\Admin\Grid;
use Dcat\Admin\Layout\Content;
use Dcat\Admin\Show;
use Dcat\Admin\Http\Controllers\AdminController;
use App\Models\Carmis as CarmisModel;
use Dcat\Admin\Widgets\Card;
class CarmisController extends AdminController
{
/**
* Make a grid builder.
*
* @return Grid
*/
protected function grid()
{
return Grid::make(new Carmis(['goods']), function (Grid $grid) {
$grid->model()->orderBy('id', 'DESC');
$grid->column('id')->sortable();
$grid->column('goods.gd_name', admin_trans('carmis.fields.goods_id'));
$grid->column('status')->select(CarmisModel::getStatusMap());
$grid->column('is_loop')->display(function($v){return $v==1?admin_trans('carmis.fields.yes'):"";});
$grid->column('carmi')->limit(20);
$grid->column('created_at');
$grid->column('updated_at')->sortable();
$grid->filter(function (Grid\Filter $filter) {
$filter->equal('id');
$filter->equal('goods_id')->select(
Goods::query()->where('type', Goods::AUTOMATIC_DELIVERY)->pluck('gd_name', 'id')
);
$filter->equal('status')->select(CarmisModel::getStatusMap());
$filter->scope(admin_trans('dujiaoka.trashed'))->onlyTrashed();
});
$grid->actions(function (Grid\Displayers\Actions $actions) {
if (request('_scope_') == admin_trans('dujiaoka.trashed')) {
$actions->append(new Restore(CarmisModel::class));
}
});
$grid->batchActions(function (Grid\Tools\BatchActions $batch) {
if (request('_scope_') == admin_trans('dujiaoka.trashed')) {
$batch->add(new BatchRestore(CarmisModel::class));
}
});
$grid->export()->titles(['goods.gd_name' => admin_trans('carmis.fields.goods_id'), 'carmi' => admin_trans('carmis.fields.carmi'), 'created_at' => admin_trans('admin.created_at')]);
});
}
/**
* Make a show builder.
*
* @param mixed $id
*
* @return Show
*/
protected function detail($id)
{
return Show::make($id, new Carmis(['goods']), function (Show $show) {
$show->field('id');
$show->field('goods.gd_name', admin_trans('carmis.fields.goods_id'));
$show->field('status')->as(function ($type) {
if ($type == CarmisModel::STATUS_UNSOLD) {
return admin_trans('carmis.fields.status_unsold');
} else {
return admin_trans('carmis.fields.status_sold');
}
});
$show->field('is_loop')->as(function ($v) {return $v==1?admin_trans('carmis.fields.yes'):"";});
$show->field('carmi');
$show->field('created_at');
$show->field('updated_at');
});
}
/**
* Make a form builder.
*
* @return Form
*/
protected function form()
{
return Form::make(new Carmis(), function (Form $form) {
$form->display('id');
$form->select('goods_id')->options(
Goods::query()->where('type', Goods::AUTOMATIC_DELIVERY)->pluck('gd_name', 'id')
)->required();
$form->radio('status')
->options(CarmisModel::getStatusMap())
->default(CarmisModel::STATUS_UNSOLD);
$form->switch('is_loop')->default(false);
$form->textarea('carmi')->required();
$form->display('created_at');
$form->display('updated_at');
});
}
/**
* 导入卡密
*
* @param Content $content
* @return Content
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function importCarmis(Content $content)
{
return $content
->title(admin_trans('carmis.fields.import_carmis'))
->body(new Card(new ImportCarmis()));
}
}

View File

@ -0,0 +1,116 @@
<?php
namespace App\Admin\Controllers;
use App\Admin\Actions\Post\BatchRestore;
use App\Admin\Actions\Post\Restore;
use App\Admin\Repositories\Coupon;
use App\Models\Goods;
use Dcat\Admin\Admin;
use Dcat\Admin\Form;
use Dcat\Admin\Grid;
use Dcat\Admin\Show;
use Dcat\Admin\Http\Controllers\AdminController;
use App\Models\Coupon as CouponModel;
class CouponController extends AdminController
{
/**
* Make a grid builder.
*
* @return Grid
*/
protected function grid()
{
return Grid::make(new Coupon(['goods']), function (Grid $grid) {
$grid->model()->orderBy('id', 'DESC');
$grid->column('id')->sortable();
$grid->column('discount');
$grid->column('is_use')->select(CouponModel::getStatusUseMap());
$grid->column('is_open')->switch();
$grid->column('coupon')->copyable();
$grid->column('ret');
$grid->column('created_at');
$grid->column('updated_at')->sortable();
$grid->actions(function (Grid\Displayers\Actions $actions) {
if (request('_scope_') == admin_trans('dujiaoka.trashed')) {
$actions->append(new Restore(CouponModel::class));
}
});
$grid->batchActions(function (Grid\Tools\BatchActions $batch) {
if (request('_scope_') == admin_trans('dujiaoka.trashed')) {
$batch->add(new BatchRestore(CouponModel::class));
}
});
$grid->filter(function (Grid\Filter $filter) {
$filter->equal('id');
$filter->equal('goods.goods_id', admin_trans('coupon.fields.goods_id'))->select(
Goods::query()->pluck('gd_name', 'id')
);
$filter->scope(admin_trans('dujiaoka.trashed'))->onlyTrashed();
});
});
}
/**
* Make a show builder.
*
* @param mixed $id
*
* @return Show
*/
protected function detail($id)
{
return Show::make($id, new Coupon(), function (Show $show) {
$show->field('id');
$show->field('discount');
$show->field('is_use')->as(function ($isUse) {
if ($isUse == CouponModel::STATUS_UNUSED) {
return admin_trans('coupon.fields.status_unused');
} else {
return admin_trans('coupon.fields.status_use');
}
});
$show->field('is_open')->as(function ($isOPen) {
if ($isOPen == CouponModel::STATUS_OPEN) {
return admin_trans('dujiaoka.status_open');
} else {
return admin_trans('dujiaoka.status_close');
}
});
$show->field('coupon');
$show->field('ret');
$show->field('created_at');
$show->field('updated_at');
});
}
/**
* Make a form builder.
*
* @return Form
*/
protected function form()
{
return Form::make(Coupon::with('goods'), function (Form $form) {
$form->display('id');
$form->multipleSelect('goods', admin_trans('coupon.fields.goods_id'))
->options(Goods::all()->pluck('gd_name', 'id'))
->customFormat(function ($v) {
if (! $v) {
return [];
}
// 从数据库中查出的二维数组中转化成ID
return array_column($v, 'id');
});
$form->currency('discount')->default(0)->required();
$form->text('coupon')->required();
$form->number('ret')->default(1);
$form->radio('is_use')->options(CouponModel::getStatusUseMap())->default(CouponModel::STATUS_UNUSED);
$form->switch('is_open')->default(CouponModel::STATUS_OPEN);
$form->display('created_at');
$form->display('updated_at');
});
}
}

View File

@ -0,0 +1,38 @@
<?php
/**
* The file was created by Assimon.
*
* @author ZhangYiQiu<me@zhangyiqiu.net>
* @copyright ZhangYiQiu<me@zhangyiqiu.net>
* @link http://zhangyiqiu.net/
*/
namespace App\Admin\Controllers;
use App\Admin\Forms\EmailTest;
use Dcat\Admin\Http\Controllers\AdminController;
use Dcat\Admin\Layout\Content;
use Dcat\Admin\Widgets\Card;
class EmailTestController extends AdminController
{
/**
* 系统设置
*
* @param Content $content
* @return Content
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function emailTest(Content $content)
{
return $content
->title(admin_trans('menu.titles.email_test'))
->body(new Card(new EmailTest()));
}
}

View File

@ -0,0 +1,94 @@
<?php
namespace App\Admin\Controllers;
use App\Admin\Actions\Post\BatchRestore;
use App\Admin\Actions\Post\Restore;
use App\Admin\Repositories\Emailtpl;
use Dcat\Admin\Form;
use Dcat\Admin\Grid;
use Dcat\Admin\Show;
use Dcat\Admin\Http\Controllers\AdminController;
use App\Models\Emailtpl as EmailTplModel;
class EmailtplController extends AdminController
{
/**
* Make a grid builder.
*
* @return Grid
*/
protected function grid()
{
return Grid::make(new Emailtpl(), function (Grid $grid) {
$grid->column('id')->sortable();
$grid->column('tpl_name');
$grid->column('tpl_token');
$grid->column('created_at');
$grid->column('updated_at')->sortable();
$grid->disableViewButton();
$grid->disableDeleteButton();
$grid->filter(function (Grid\Filter $filter) {
$filter->equal('id');
$filter->like('tpl_name');
$filter->like('tpl_token');
});
$grid->actions(function (Grid\Displayers\Actions $actions) {
if (request('_scope_') == admin_trans('dujiaoka.trashed')) {
$actions->append(new Restore(EmailTplModel::class));
}
});
$grid->batchActions(function (Grid\Tools\BatchActions $batch) {
if (request('_scope_') == admin_trans('dujiaoka.trashed')) {
$batch->add(new BatchRestore(EmailTplModel::class));
}
});
});
}
/**
* Make a show builder.
*
* @param mixed $id
*
* @return Show
*/
protected function detail($id)
{
return Show::make($id, new Emailtpl(), function (Show $show) {
$show->field('id');
$show->field('tpl_name');
$show->field('tpl_content');
$show->field('tpl_token');
$show->field('created_at');
$show->field('updated_at');
});
}
/**
* Make a form builder.
*
* @return Form
*/
protected function form()
{
return Form::make(new Emailtpl(), function (Form $form) {
$form->display('id');
$form->text('tpl_name')->required();
$form->editor('tpl_content')->required();
if ($form->isCreating()) {
$form->text('tpl_token')->required();
} else {
$form->text('tpl_token')->disable();
}
$form->display('created_at');
$form->display('updated_at');
$form->disableViewButton();
$form->disableDeleteButton();
$form->footer(function ($footer) {
// 去掉`查看`checkbox
$footer->disableViewCheck();
});
});
}
}

View File

@ -0,0 +1,160 @@
<?php
namespace App\Admin\Controllers;
use App\Admin\Actions\Post\BatchRestore;
use App\Admin\Actions\Post\Restore;
use App\Admin\Repositories\Goods;
use App\Models\Carmis;
use App\Models\Coupon;
use App\Models\GoodsGroup as GoodsGroupModel;
use Dcat\Admin\Admin;
use Dcat\Admin\Form;
use Dcat\Admin\Grid;
use Dcat\Admin\Show;
use Dcat\Admin\Http\Controllers\AdminController;
use App\Models\Goods as GoodsModel;
class GoodsController extends AdminController
{
/**
* Make a grid builder.
*
* @return Grid
*/
protected function grid()
{
return Grid::make(new Goods(['group', 'coupon']), function (Grid $grid) {
$grid->model()->orderBy('id', 'DESC');
$grid->column('id')->sortable();
$grid->column('picture')->image('', 100, 100);
$grid->column('gd_name');
$grid->column('gd_description');
$grid->column('gd_keywords');
$grid->column('group.gp_name', admin_trans('goods.fields.group_id'));
$grid->column('type')
->using(GoodsModel::getGoodsTypeMap())
->label([
GoodsModel::AUTOMATIC_DELIVERY => Admin::color()->success(),
GoodsModel::MANUAL_PROCESSING => Admin::color()->info(),
]);
$grid->column('retail_price');
$grid->column('actual_price')->sortable();
$grid->column('in_stock')->display(function () {
// 如果为自动发货,则加载库存卡密
if ($this->type == GoodsModel::AUTOMATIC_DELIVERY) {
return Carmis::query()->where('goods_id', $this->id)
->where('status', Carmis::STATUS_UNSOLD)
->count();
} else {
return $this->in_stock;
}
});
$grid->column('sales_volume');
$grid->column('ord')->editable()->sortable();
$grid->column('is_open')->switch();
$grid->column('created_at')->sortable();
$grid->column('updated_at');
$grid->filter(function (Grid\Filter $filter) {
$filter->equal('id');
$filter->like('gd_name');
$filter->equal('type')->select(GoodsModel::getGoodsTypeMap());
$filter->equal('group_id')->select(GoodsGroupModel::query()->pluck('gp_name', 'id'));
$filter->scope(admin_trans('dujiaoka.trashed'))->onlyTrashed();
$filter->equal('coupon.coupons_id', admin_trans('goods.fields.coupon_id'))->select(
Coupon::query()->pluck('coupon', 'id')
);
});
$grid->actions(function (Grid\Displayers\Actions $actions) {
if (request('_scope_') == admin_trans('dujiaoka.trashed')) {
$actions->append(new Restore(GoodsModel::class));
}
});
$grid->batchActions(function (Grid\Tools\BatchActions $batch) {
if (request('_scope_') == admin_trans('dujiaoka.trashed')) {
$batch->add(new BatchRestore(GoodsModel::class));
}
});
});
}
/**
* Make a show builder.
*
* @param mixed $id
*
* @return Show
*/
protected function detail($id)
{
return Show::make($id, new Goods(), function (Show $show) {
$show->id('id');
$show->field('gd_name');
$show->field('gd_description');
$show->field('gd_keywords');
$show->field('picture')->image();
$show->field('retail_price');
$show->field('actual_price');
$show->field('in_stock');
$show->field('ord');
$show->field('sales_volume');
$show->field('type')->as(function ($type) {
if ($type == GoodsModel::AUTOMATIC_DELIVERY) {
return admin_trans('goods.fields.automatic_delivery');
} else {
return admin_trans('goods.fields.manual_processing');
}
});
$show->field('is_open')->as(function ($isOpen) {
if ($isOpen == GoodsGroupModel::STATUS_OPEN) {
return admin_trans('dujiaoka.status_open');
} else {
return admin_trans('dujiaoka.status_close');
}
});
$show->wholesale_price_cnf()->unescape()->as(function ($wholesalePriceCnf) {
return "<textarea class=\"form-control field_wholesale_price_cnf _normal_\" rows=\"10\" cols=\"30\">" . $wholesalePriceCnf . "</textarea>";
});
$show->other_ipu_cnf()->unescape()->as(function ($otherIpuCnf) {
return "<textarea class=\"form-control field_wholesale_price_cnf _normal_\" rows=\"10\" cols=\"30\">" . $otherIpuCnf . "</textarea>";
});
$show->api_hook()->unescape()->as(function ($apiHook) {
return "<textarea class=\"form-control field_wholesale_price_cnf _normal_\" rows=\"10\" cols=\"30\">" . $apiHook . "</textarea>";
});;
});
}
/**
* Make a form builder.
*
* @return Form
*/
protected function form()
{
return Form::make(new Goods(), function (Form $form) {
$form->display('id');
$form->text('gd_name')->required();
$form->text('gd_description')->required();
$form->text('gd_keywords')->required();
$form->select('group_id')->options(
GoodsGroupModel::query()->pluck('gp_name', 'id')
)->required();
$form->image('picture')->autoUpload()->uniqueName()->help(admin_trans('goods.helps.picture'));
$form->radio('type')->options(GoodsModel::getGoodsTypeMap())->default(GoodsModel::AUTOMATIC_DELIVERY)->required();
$form->currency('retail_price')->default(0)->help(admin_trans('goods.helps.retail_price'));
$form->currency('actual_price')->default(0)->required();
$form->number('in_stock')->help(admin_trans('goods.helps.in_stock'));
$form->number('sales_volume');
$form->number('buy_limit_num')->help(admin_trans('goods.helps.buy_limit_num'));
$form->editor('buy_prompt');
$form->editor('description');
$form->textarea('other_ipu_cnf')->help(admin_trans('goods.helps.other_ipu_cnf'));
$form->textarea('wholesale_price_cnf')->help(admin_trans('goods.helps.wholesale_price_cnf'));
$form->textarea('api_hook');
$form->number('ord')->default(1)->help(admin_trans('dujiaoka.ord'));
$form->switch('is_open')->default(GoodsModel::STATUS_OPEN);
});
}
}

View File

@ -0,0 +1,96 @@
<?php
namespace App\Admin\Controllers;
use App\Admin\Actions\Post\BatchRestore;
use App\Admin\Actions\Post\Restore;
use App\Admin\Repositories\GoodsGroup;
use Dcat\Admin\Form;
use Dcat\Admin\Grid;
use Dcat\Admin\Show;
use Dcat\Admin\Http\Controllers\AdminController;
use App\Models\GoodsGroup as GoodsGroupModel;
class GoodsGroupController extends AdminController
{
/**
* Make a grid builder.
*
* @return Grid
*/
protected function grid()
{
return Grid::make(new GoodsGroup(), function (Grid $grid) {
$grid->model()->orderBy('id', 'DESC');
$grid->column('id')->sortable();
$grid->column('gp_name')->editable();
$grid->column('is_open')->switch();
$grid->column('ord')->editable();
$grid->column('created_at');
$grid->column('updated_at')->sortable();
$grid->disableViewButton();
$grid->filter(function (Grid\Filter $filter) {
$filter->equal('id');
$filter->scope(admin_trans('dujiaoka.trashed'))->onlyTrashed();
});
$grid->actions(function (Grid\Displayers\Actions $actions) {
if (request('_scope_') == admin_trans('dujiaoka.trashed')) {
$actions->append(new Restore(GoodsGroupModel::class));
}
});
$grid->batchActions(function (Grid\Tools\BatchActions $batch) {
if (request('_scope_') == admin_trans('dujiaoka.trashed')) {
$batch->add(new BatchRestore(GoodsGroupModel::class));
}
});
});
}
/**
* Make a show builder.
*
* @param mixed $id
*
* @return Show
*/
protected function detail($id)
{
return Show::make($id, new GoodsGroup(), function (Show $show) {
$show->field('id');
$show->field('gp_name');
$show->field('is_open')->as(function ($isOpen) {
if ($isOpen == GoodsGroupModel::STATUS_OPEN) {
return admin_trans('dujiaoka.status_open');
} else {
return admin_trans('dujiaoka.status_close');
}
});
$show->field('ord');
$show->field('created_at');
$show->field('updated_at');
});
}
/**
* Make a form builder.
*
* @return Form
*/
protected function form()
{
return Form::make(new GoodsGroup(), function (Form $form) {
$form->display('id');
$form->text('gp_name');
$form->switch('is_open')->default(GoodsGroupModel::STATUS_OPEN);
$form->number('ord')->default(1)->help(admin_trans('dujiaoka.ord'));
$form->display('created_at');
$form->display('updated_at');
$form->disableViewButton();
$form->footer(function ($footer) {
// 去掉`查看`checkbox
$footer->disableViewCheck();
});
});
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Admin\Controllers;
use App\Admin\Charts\DashBoard;
use App\Admin\Charts\PayoutRateCard;
use App\Admin\Charts\PopularGoodsCard;
use App\Admin\Charts\SalesCard;
use App\Admin\Charts\SuccessOrderCard;
use App\Http\Controllers\Controller;
use Dcat\Admin\Layout\Column;
use Dcat\Admin\Layout\Content;
use Dcat\Admin\Layout\Row;
class HomeController extends Controller
{
public function index(Content $content)
{
return $content
->header(admin_trans('dujiaoka.dashboard'))
->description(admin_trans('dujiaoka.dashboard_description'))
->body(function (Row $row) {
$row->column(6, function (Column $column) {
$column->row(self::title());
$column->row(new DashBoard());
});
$row->column(6, function (Column $column) {
$column->row(function (Row $row) {
$row->column(6, new SuccessOrderCard());
$row->column(6, new PayoutRateCard());
});
$column->row(new SalesCard());
});
});
}
public static function title()
{
return view('admin.dashboard.title');
}
}

View File

@ -0,0 +1,156 @@
<?php
namespace App\Admin\Controllers;
use App\Admin\Actions\Post\BatchRestore;
use App\Admin\Actions\Post\Restore;
use App\Admin\Repositories\Order;
use App\Models\Coupon;
use App\Models\Goods;
use App\Models\Pay;
use Dcat\Admin\Admin;
use Dcat\Admin\Form;
use Dcat\Admin\Grid;
use Dcat\Admin\Show;
use Dcat\Admin\Http\Controllers\AdminController;
use App\Models\Order as OrderModel;
class OrderController extends AdminController
{
/**
* Make a grid builder.
*
* @return Grid
*/
protected function grid()
{
return Grid::make(new Order(['goods', 'coupon', 'pay']), function (Grid $grid) {
$grid->model()->orderBy('id', 'DESC');
$grid->column('id')->sortable();
$grid->column('order_sn')->copyable();
$grid->column('title');
$grid->column('type')->using(OrderModel::getTypeMap())
->label([
OrderModel::AUTOMATIC_DELIVERY => Admin::color()->success(),
OrderModel::MANUAL_PROCESSING => Admin::color()->info(),
]);
$grid->column('email')->copyable();
$grid->column('goods.gd_name', admin_trans('order.fields.goods_id'));
$grid->column('goods_price');
$grid->column('buy_amount');
$grid->column('total_price');
$grid->column('coupon.coupon', admin_trans('order.fields.coupon_id'));
$grid->column('coupon_discount_price');
$grid->column('wholesale_discount_price');
$grid->column('actual_price');
$grid->column('pay.pay_name', admin_trans('order.fields.pay_id'));
$grid->column('buy_ip');
$grid->column('search_pwd')->copyable();
$grid->column('trade_no')->copyable();
$grid->column('status')
->select(OrderModel::getStatusMap());
$grid->column('created_at');
$grid->column('updated_at')->sortable();
$grid->disableCreateButton();
$grid->filter(function (Grid\Filter $filter) {
$filter->equal('order_sn');
$filter->like('title');
$filter->equal('status')->select(OrderModel::getStatusMap());
$filter->equal('email');
$filter->equal('trade_no');
$filter->equal('type')->select(OrderModel::getTypeMap());
$filter->equal('goods_id')->select(Goods::query()->pluck('gd_name', 'id'));
$filter->equal('coupon_id')->select(Coupon::query()->pluck('coupon', 'id'));
$filter->equal('pay_id')->select(Pay::query()->pluck('pay_name', 'id'));
$filter->whereBetween('created_at', function ($q) {
$start = $this->input['start'] ?? null;
$end = $this->input['end'] ?? null;
$q->where('created_at', '>=', $start)
->where('created_at', '<=', $end);
})->datetime();
$filter->scope(admin_trans('dujiaoka.trashed'))->onlyTrashed();
});
$grid->actions(function (Grid\Displayers\Actions $actions) {
if (request('_scope_') == admin_trans('dujiaoka.trashed')) {
$actions->append(new Restore(OrderModel::class));
}
});
$grid->batchActions(function (Grid\Tools\BatchActions $batch) {
if (request('_scope_') == admin_trans('dujiaoka.trashed')) {
$batch->add(new BatchRestore(OrderModel::class));
}
});
});
}
/**
* Make a show builder.
*
* @param mixed $id
*
* @return Show
*/
protected function detail($id)
{
return Show::make($id, new Order(['goods', 'coupon', 'pay']), function (Show $show) {
$show->field('id');
$show->field('order_sn');
$show->field('title');
$show->field('email');
$show->field('goods.gd_name', admin_trans('order.fields.goods_id'));
$show->field('goods_price');
$show->field('buy_amount');
$show->field('coupon.coupon', admin_trans('order.fields.coupon_id'));
$show->field('coupon_discount_price');
$show->field('wholesale_discount_price');
$show->field('total_price');
$show->field('actual_price');
$show->field('buy_ip');
$show->field('info')->unescape()->as(function ($info) {
return "<textarea class=\"form-control field_wholesale_price_cnf _normal_\" rows=\"10\" cols=\"30\">" . $info . "</textarea>";
});
$show->field('pay.pay_name', admin_trans('order.fields.pay_id'));
$show->field('status')->using(OrderModel::getStatusMap());
$show->field('search_pwd');
$show->field('trade_no');
$show->field('type')->using(OrderModel::getTypeMap());
$show->field('created_at');
$show->field('updated_at');
$show->disableEditButton();
});
}
/**
* Make a form builder.
*
* @return Form
*/
protected function form()
{
return Form::make(new Order(['goods', 'coupon', 'pay']), function (Form $form) {
$form->display('id');
$form->display('order_sn');
$form->text('title');
$form->display('goods.gd_name', admin_trans('order.fields.goods_id'));
$form->display('goods_price');
$form->display('buy_amount');
$form->display('coupon.coupon', admin_trans('order.fields.coupon_id'));
$form->display('coupon_discount_price');
$form->display('wholesale_discount_price');
$form->display('total_price');
$form->display('actual_price');
$form->display('email');
$form->textarea('info');
$form->display('buy_ip');
$form->display('pay.pay_name', admin_trans('order.fields.pay_id'));
$form->radio('status')->options(OrderModel::getStatusMap());
$form->text('search_pwd');
$form->display('trade_no');
$form->radio('type')->options(OrderModel::getTypeMap());
$form->display('created_at');
$form->display('updated_at');
});
}
}

View File

@ -0,0 +1,130 @@
<?php
namespace App\Admin\Controllers;
use App\Admin\Actions\Post\BatchRestore;
use App\Admin\Actions\Post\Restore;
use App\Admin\Repositories\Pay;
use Dcat\Admin\Form;
use Dcat\Admin\Grid;
use Dcat\Admin\Show;
use Dcat\Admin\Http\Controllers\AdminController;
use App\Models\Pay as PayModel;
class PayController extends AdminController
{
/**
* Make a grid builder.
*
* @return Grid
*/
protected function grid()
{
return Grid::make(new Pay(), function (Grid $grid) {
$grid->column('id')->sortable();
$grid->column('pay_name');
$grid->column('pay_check');
$grid->column('pay_method')->select(PayModel::getMethodMap());
$grid->column('merchant_id')->limit(20);
$grid->column('merchant_key')->limit(20);
$grid->column('merchant_pem')->limit(20);
$grid->column('pay_client')->select(PayModel::getClientMap());
$grid->column('pay_handleroute');
$grid->column('is_open')->switch();
$grid->column('created_at');
$grid->column('updated_at')->sortable();
$grid->disableDeleteButton();
$grid->filter(function (Grid\Filter $filter) {
$filter->equal('id');
$filter->equal('pay_check');
$filter->like('pay_name');
$filter->scope(admin_trans('dujiaoka.trashed'))->onlyTrashed();
});
$grid->actions(function (Grid\Displayers\Actions $actions) {
if (request('_scope_') == admin_trans('dujiaoka.trashed')) {
$actions->append(new Restore(PayModel::class));
}
});
$grid->batchActions(function (Grid\Tools\BatchActions $batch) {
if (request('_scope_') == admin_trans('dujiaoka.trashed')) {
$batch->add(new BatchRestore(PayModel::class));
}
});
});
}
/**
* Make a show builder.
*
* @param mixed $id
*
* @return Show
*/
protected function detail($id)
{
return Show::make($id, new Pay(), function (Show $show) {
$show->field('id');
$show->field('pay_name');
$show->field('merchant_id');
$show->field('merchant_key');
$show->field('merchant_pem');
$show->field('pay_check');
$show->field('pay_client')->as(function ($payClient) {
if ($payClient == PayModel::PAY_CLIENT_PC) {
return admin_trans('pay.fields.pay_client_pc');
} else {
return admin_trans('pay.fields.pay_client_mobile');
}
});
$show->field('pay_handleroute');
$show->field('pay_method')->as(function ($payMethod) {
if ($payMethod == PayModel::METHOD_JUMP) {
return admin_trans('pay.fields.method_jump');
} else {
return admin_trans('pay.fields.method_scan');
}
});
$show->field('is_open')->as(function ($isOpen) {
if ($isOpen == PayModel::STATUS_OPEN) {
return admin_trans('dujiaoka.status_open');
} else {
return admin_trans('dujiaoka.status_close');
}
});
$show->field('created_at');
$show->field('updated_at');
});
}
/**
* Make a form builder.
*
* @return Form
*/
protected function form()
{
return Form::make(new Pay(), function (Form $form) {
$form->display('id');
$form->text('pay_name')->required();
$form->text('merchant_id')->required();
$form->textarea('merchant_key');
$form->textarea('merchant_pem')->required();
$form->text('pay_check')->required();
$form->radio('pay_client')
->options(PayModel::getClientMap())
->default(PayModel::PAY_CLIENT_PC)
->required();
$form->radio('pay_method')
->options(PayModel::getMethodMap())
->default(PayModel::METHOD_JUMP)
->required();
$form->text('pay_handleroute')->required();
$form->switch('is_open')->default(PayModel::STATUS_OPEN);
$form->display('created_at');
$form->display('updated_at');
$form->disableDeleteButton();
});
}
}

View File

@ -0,0 +1,38 @@
<?php
/**
* The file was created by Assimon.
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
namespace App\Admin\Controllers;
use App\Admin\Forms\SystemSetting;
use Dcat\Admin\Http\Controllers\AdminController;
use Dcat\Admin\Layout\Content;
use Dcat\Admin\Widgets\Card;
class SystemSettingController extends AdminController
{
/**
* 系统设置
*
* @param Content $content
* @return Content
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function systemSetting(Content $content)
{
return $content
->title(admin_trans('menu.titles.system_setting'))
->body(new Card(new SystemSetting()));
}
}

View File

@ -0,0 +1,85 @@
<?php
/**
* The file was created by Assimon.
*
* @author ZhangYiQiu<me@zhangyiqiu.net>
* @copyright ZhangYiQiu<me@zhangyiqiu.net>
* @link http://zhangyiqiu.net/
*/
namespace App\Admin\Forms;
use App\Models\BaseModel;
use Dcat\Admin\Widgets\Form;
use Illuminate\Support\Facades\Cache;
use Illuminate\Mail\MailServiceProvider;
use Illuminate\Support\Facades\Mail;
class EmailTest extends Form
{
/**
* Handle the form request.
*
* @param array $input
*
* @return mixed
*/
public function handle(array $input)
{
$to = $input['to'];
$title = $input['title'];
$body = $input['body'];
$sysConfig = cache('system-setting');
$mailConfig = [
'driver' => $sysConfig['driver'] ?? 'smtp',
'host' => $sysConfig['host'] ?? '',
'port' => $sysConfig['port'] ?? '465',
'username' => $sysConfig['username'] ?? '',
'from' => [
'address' => $sysConfig['from_address'] ?? '',
'name' => $sysConfig['from_name'] ?? '独角发卡'
],
'password' => $sysConfig['password'] ?? '',
'encryption' => $sysConfig['encryption'] ?? 'ssl'
];
// 覆盖 mail 配置
config([
'mail' => array_merge(config('mail'), $mailConfig)
]);
// 重新注册驱动
(new MailServiceProvider(app()))->register();
try
{
Mail::send(['html' => 'email.mail'], ['body' => $body], function ($message) use ($to, $title){
$message->to($to)->subject($title);
});
}
catch(\Exception $e)
{
return $this
->response()
->error($e->getMessage());
}
return $this
->response()
->success(admin_trans('email-test.labels.success'));
}
/**
* Build a form here.
*/
public function form()
{
$this->tab(admin_trans('menu.titles.email_test'), function () {
$this->text('to', admin_trans('email-test.labels.to'))->required();
$this->text('title', admin_trans('email-test.labels.title'))->default('这是一条测试邮件')->required();
$this->editor('body', admin_trans('email-test.labels.body'))->default("这是一条测试邮件的正文内容<br/><br/>正文比较长<br/><br/>非常长<br/><br/>测试测试测试")->required();
});
}
public function default()
{
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace App\Admin\Forms;
use App\Models\Carmis;
use App\Models\Goods;
use Dcat\Admin\Widgets\Form;
use Illuminate\Support\Facades\Storage;
class ImportCarmis extends Form
{
/**
* Handle the form request.
*
* @param array $input
*
* @return mixed
*/
public function handle(array $input)
{
if (empty($input['carmis_list']) && empty($input['carmis_txt'])) {
return $this->response()->error(admin_trans('carmis.rule_messages.carmis_list_and_carmis_txt_can_not_be_empty'));
}
$carmisContent = "";
if (!empty($input['carmis_txt'])) {
$carmisContent = Storage::disk('public')->get($input['carmis_txt']);
}
if (!empty($input['carmis_list'])) {
$carmisContent = $input['carmis_list'];
}
$carmisData = [];
$tempList = explode(PHP_EOL, $carmisContent);
foreach ($tempList as $val) {
if (trim($val) != "") {
$carmisData[] = [
'goods_id' => $input['goods_id'],
'carmi' => trim($val),
'status' => Carmis::STATUS_UNSOLD,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
];
}
}
if ($input['remove_duplication'] == 1) {
$carmisData = assoc_unique($carmisData, 'carmi');
}
Carmis::query()->insert($carmisData);
// 删除文件
Storage::disk('public')->delete($input['carmis_txt']);
return $this
->response()
->success(admin_trans('carmis.rule_messages.import_carmis_success'))
->location('/carmis');
}
/**
* Build a form here.
*/
public function form()
{
$this->confirm(admin_trans('carmis.fields.are_you_import_sure'));
$this->select('goods_id')->options(
Goods::query()->where('type', Goods::AUTOMATIC_DELIVERY)->pluck('gd_name', 'id')
)->required();
$this->textarea('carmis_list')
->rows(20)
->help(admin_trans('carmis.helps.carmis_list'));
$this->file('carmis_txt')
->disk('public')
->uniqueName()
->accept('txt')
->maxSize(5120)
->help(admin_trans('carmis.helps.carmis_list'));
$this->switch('remove_duplication');
}
}

View File

@ -0,0 +1,102 @@
<?php
namespace App\Admin\Forms;
use App\Models\BaseModel;
use Dcat\Admin\Widgets\Form;
use Illuminate\Support\Facades\Cache;
class SystemSetting extends Form
{
/**
* Handle the form request.
*
* @param array $input
*
* @return mixed
*/
public function handle(array $input)
{
Cache::put('system-setting', $input);
return $this
->response()
->success(admin_trans('system-setting.rule_messages.save_system_setting_success'));
}
/**
* Build a form here.
*/
public function form()
{
$this->tab(admin_trans('system-setting.labels.base_setting'), function () {
$this->text('title', admin_trans('system-setting.fields.title'))->required();
$this->image('img_logo', admin_trans('system-setting.fields.img_logo'));
$this->text('text_logo', admin_trans('system-setting.fields.text_logo'));
$this->text('keywords', admin_trans('system-setting.fields.keywords'));
$this->textarea('description', admin_trans('system-setting.fields.description'));
$this->select('template', admin_trans('system-setting.fields.template'))
->options(config('dujiaoka.templates'))
->required();
$this->select('language', admin_trans('system-setting.fields.language'))
->options(config('dujiaoka.language'))
->required();
$this->text('manage_email', admin_trans('system-setting.fields.manage_email'));
$this->number('order_expire_time', admin_trans('system-setting.fields.order_expire_time'))
->default(5)
->required();
$this->switch('is_open_anti_red', admin_trans('system-setting.fields.is_open_anti_red'))
->default(BaseModel::STATUS_CLOSE);
$this->switch('is_open_img_code', admin_trans('system-setting.fields.is_open_img_code'))
->default(BaseModel::STATUS_CLOSE);
$this->switch('is_open_search_pwd', admin_trans('system-setting.fields.is_open_search_pwd'))
->default(BaseModel::STATUS_CLOSE);
$this->switch('is_open_google_translate', admin_trans('system-setting.fields.is_open_google_translate'))
->default(BaseModel::STATUS_CLOSE);
$this->editor('notice', admin_trans('system-setting.fields.notice'));
$this->textarea('footer', admin_trans('system-setting.fields.footer'));
});
$this->tab(admin_trans('system-setting.labels.order_push_setting'), function () {
$this->switch('is_open_server_jiang', admin_trans('system-setting.fields.is_open_server_jiang'))
->default(BaseModel::STATUS_CLOSE);
$this->text('server_jiang_token', admin_trans('system-setting.fields.server_jiang_token'));
$this->switch('is_open_telegram_push', admin_trans('system-setting.fields.is_open_telegram_push'))
->default(BaseModel::STATUS_CLOSE);
$this->text('telegram_bot_token', admin_trans('system-setting.fields.telegram_bot_token'));
$this->text('telegram_userid', admin_trans('system-setting.fields.telegram_userid'));
$this->switch('is_open_bark_push', admin_trans('system-setting.fields.is_open_bark_push'))
->default(BaseModel::STATUS_CLOSE);
$this->switch('is_open_bark_push_url', admin_trans('system-setting.fields.is_open_bark_push_url'))
->default(BaseModel::STATUS_CLOSE);
$this->text('bark_server', admin_trans('system-setting.fields.bark_server'));
$this->text('bark_token', admin_trans('system-setting.fields.bark_token'));
$this->switch('is_open_qywxbot_push', admin_trans('system-setting.fields.is_open_qywxbot_push'))
->default(BaseModel::STATUS_CLOSE);
$this->text('qywxbot_key', admin_trans('system-setting.fields.qywxbot_key'));
});
$this->tab(admin_trans('system-setting.labels.mail_setting'), function () {
$this->text('driver', admin_trans('system-setting.fields.driver'))->default('smtp')->required();
$this->text('host', admin_trans('system-setting.fields.host'));
$this->text('port', admin_trans('system-setting.fields.port'))->default(587);
$this->text('username', admin_trans('system-setting.fields.username'));
$this->text('password', admin_trans('system-setting.fields.password'));
$this->text('encryption', admin_trans('system-setting.fields.encryption'));
$this->text('from_address', admin_trans('system-setting.fields.from_address'));
$this->text('from_name', admin_trans('system-setting.fields.from_name'));
});
$this->tab(admin_trans('system-setting.labels.geetest'), function () {
$this->text('geetest_id', admin_trans('system-setting.fields.geetest_id'));
$this->text('geetest_key', admin_trans('system-setting.fields.geetest_key'));
$this->switch('is_open_geetest', admin_trans('system-setting.fields.is_open_geetest'))->default(BaseModel::STATUS_CLOSE);
});
$this->confirm(
admin_trans('dujiaoka.warning_title'),
admin_trans('system-setting.rule_messages.change_reboot_php_worker')
);
}
public function default()
{
return Cache::get('system-setting');
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Admin\Repositories;
use App\Models\Carmis as Model;
use Dcat\Admin\Repositories\EloquentRepository;
class Carmis extends EloquentRepository
{
/**
* Model.
*
* @var string
*/
protected $eloquentClass = Model::class;
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Admin\Repositories;
use App\Models\Coupon as Model;
use Dcat\Admin\Repositories\EloquentRepository;
class Coupon extends EloquentRepository
{
/**
* Model.
*
* @var string
*/
protected $eloquentClass = Model::class;
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Admin\Repositories;
use App\Models\Emailtpl as Model;
use Dcat\Admin\Repositories\EloquentRepository;
class Emailtpl extends EloquentRepository
{
/**
* Model.
*
* @var string
*/
protected $eloquentClass = Model::class;
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Admin\Repositories;
use App\Models\Goods as Model;
use Dcat\Admin\Repositories\EloquentRepository;
class Goods extends EloquentRepository
{
/**
* Model.
*
* @var string
*/
protected $eloquentClass = Model::class;
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Admin\Repositories;
use App\Models\GoodsGroup as Model;
use Dcat\Admin\Repositories\EloquentRepository;
class GoodsGroup extends EloquentRepository
{
/**
* Model.
*
* @var string
*/
protected $eloquentClass = Model::class;
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Admin\Repositories;
use App\Models\Order as Model;
use Dcat\Admin\Repositories\EloquentRepository;
class Order extends EloquentRepository
{
/**
* Model.
*
* @var string
*/
protected $eloquentClass = Model::class;
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Admin\Repositories;
use App\Models\Pay as Model;
use Dcat\Admin\Repositories\EloquentRepository;
class Pay extends EloquentRepository
{
/**
* Model.
*
* @var string
*/
protected $eloquentClass = Model::class;
}

26
app/Admin/bootstrap.php Normal file
View File

@ -0,0 +1,26 @@
<?php
use Dcat\Admin\Admin;
use Dcat\Admin\Grid;
use Dcat\Admin\Form;
use Dcat\Admin\Grid\Filter;
use Dcat\Admin\Show;
/**
* Dcat-admin - admin builder based on Laravel.
* @author jqh <https://github.com/jqhph>
*
* Bootstraper for Admin.
*
* Here you can remove builtin form field:
*
* extend custom field:
* Dcat\Admin\Form::extend('php', PHPEditor::class);
* Dcat\Admin\Grid\Column::extend('php', PHPEditor::class);
* Dcat\Admin\Grid\Filter::extend('php', PHPEditor::class);
*
* Or require js and css assets:
* Admin::css('/packages/prettydocs/css/styles.css');
* Admin::js('/packages/prettydocs/js/main.js');
*
*/

25
app/Admin/routes.php Normal file
View File

@ -0,0 +1,25 @@
<?php
use Illuminate\Routing\Router;
use Illuminate\Support\Facades\Route;
use Dcat\Admin\Admin;
Admin::routes();
Route::group([
'prefix' => config('admin.route.prefix'),
'namespace' => config('admin.route.namespace'),
'middleware' => config('admin.route.middleware'),
], function (Router $router) {
$router->get('/', 'HomeController@index');
$router->resource('goods', 'GoodsController');
$router->resource('goods-group', 'GoodsGroupController');
$router->resource('carmis', 'CarmisController');
$router->resource('coupon', 'CouponController');
$router->resource('emailtpl', 'EmailtplController');
$router->resource('pay', 'PayController');
$router->resource('order', 'OrderController');
$router->get('import-carmis', 'CarmisController@importCarmis');
$router->get('system-setting', 'SystemSettingController@systemSetting');
$router->get('email-test', 'EmailTestController@emailTest');
});

42
app/Console/Kernel.php Normal file
View File

@ -0,0 +1,42 @@
<?php
namespace App\Console;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
class Kernel extends ConsoleKernel
{
/**
* The Artisan commands provided by your application.
*
* @var array
*/
protected $commands = [
//
];
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
// $schedule->command('inspire')
// ->hourly();
}
/**
* Register the commands for the application.
*
* @return void
*/
protected function commands()
{
$this->load(__DIR__.'/Commands');
require base_path('routes/console.php');
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Events;
use App\Models\Goods;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class GoodsDeleted
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $goods;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(Goods $goods)
{
$this->goods = $goods;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Events;
use App\Models\GoodsGroup;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class GoodsGroupDeleted
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $goodsGroup;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(GoodsGroup $goodsGroup)
{
$this->goodsGroup = $goodsGroup;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Events;
use App\Models\Order;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class OrderUpdated
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $order;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(Order $order)
{
$this->order = $order;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace App\Exceptions;
class AppException extends \Exception
{
public function __construct($message = "", $code = 0, \Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* @var array
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* @var array
*/
protected $dontFlash = [
'password',
'password_confirmation',
];
/**
* Report or log an exception.
*
* @param \Exception $exception
* @return void
*
* @throws \Exception
*/
public function report(Exception $exception)
{
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @return \Symfony\Component\HttpFoundation\Response
*
* @throws \Exception
*/
public function render($request, Exception $exception)
{
if ($exception instanceof AppException) {
$layout = dujiaoka_config_get('template', 'layui');
$tplPath = $layout . '/errors/error';
return view($tplPath, ['title' => __('dujiaoka.error_title'), 'content' => $exception->getMessage(), 'url' => ""]);
}
return parent::render($request, $exception);
}
}

View File

@ -0,0 +1,15 @@
<?php
namespace App\Exceptions;
class RuleValidationException extends \Exception
{
public function __construct($message = "", $code = 400, \Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
}

236
app/Helpers/functions.php Normal file
View File

@ -0,0 +1,236 @@
<?php
/**
* The file was created by Assimon.
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
use App\Exceptions\AppException;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Storage;
if (! function_exists('replace_mail_tpl')) {
/**
* 替换邮件模板
*
* @param array $mailtpl 模板
* @param array $data 内容
* @return array|false|mixed
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
function replace_mail_tpl($mailtpl = [], $data = [])
{
if (!$mailtpl) {
return false;
}
if ($data) {
foreach ($data as $key => $val) {
$title = str_replace('{' . $key . '}', $val, isset($title) ? $title : $mailtpl['tpl_name']);
$content = str_replace('{' . $key . '}', $val, isset($content) ? $content : $mailtpl['tpl_content']);
}
return ['tpl_name' => $title, 'tpl_content' => $content];
}
return $mailtpl;
}
}
if (! function_exists('dujiaoka_config_get')) {
/**
* 系统配置获取
*
* @param string $key 要获取的key
* @param $default 默认
* @return mixed|null
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
function dujiaoka_config_get(string $key, $default = null)
{
$sysConfig = Cache::get('system-setting');
return $sysConfig[$key] ?? $default;
}
}
if (! function_exists('format_wholesale_price')) {
/**
* 格式化批发价
*
* @param string $wholesalePriceArr 批发价配置
* @return array|null
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
function format_wholesale_price(string $wholesalePriceArr): ?array
{
$waitArr = explode(PHP_EOL, $wholesalePriceArr);
$formatData = [];
foreach ($waitArr as $key => $val) {
if ($val != "") {
$explodeFormat = explode('=', delete_html_code($val));
if (count($explodeFormat) != 2) {
return null;
}
$formatData[$key]['number'] = $explodeFormat[0];
$formatData[$key]['price'] = $explodeFormat[1];
}
}
sort($formatData);
return $formatData;
}
}
if (! function_exists('delete_html_code')) {
/**
* 去除html内容
* @param string $str 需要去掉的字符串
* @return string
*/
function delete_html_code(string $str): string
{
$str = trim($str); //清除字符串两边的空格
$str = preg_replace("/\t/", "", $str); //使用正则表达式替换内容,如:空格,换行,并将替换为空。
$str = preg_replace("/\r\n/", "", $str);
$str = preg_replace("/\r/", "", $str);
$str = preg_replace("/\n/", "", $str);
$str = preg_replace("/ /", "", $str);
$str = preg_replace("/ /", "", $str); //匹配html中的空格
return trim($str); //返回字符串
}
}
if (! function_exists('format_charge_input')) {
/**
* 格式化代充框
*
* @param string $charge
* @return array|null
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
function format_charge_input(string $charge): ?array
{
$inputArr = explode(PHP_EOL, $charge);
$formatData = [];
foreach ($inputArr as $key => $val) {
if ($val != "") {
$explodeFormat = explode('=', delete_html_code($val));
if (count($explodeFormat) < 3) {
return null;
}
$formatData[$key]['field'] = $explodeFormat[0];
$formatData[$key]['desc'] = $explodeFormat[1];
$formatData[$key]['rule'] = filter_var($explodeFormat[2], FILTER_VALIDATE_BOOLEAN);
if(count($explodeFormat) > 3){
$formatData[$key]['placeholder'] = $explodeFormat[3];
}else{
$formatData[$key]['placeholder'] = $formatData[$key]['desc'];
}
}
}
return $formatData;
}
}
if (! function_exists('site_url')) {
/**
* 获取顶级域名 带协议
* @return string
*/
function site_url()
{
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
$domainName = $_SERVER['HTTP_HOST'] . '/';
return $protocol . $domainName;
}
}
if (! function_exists('md5_signquery')) {
function md5_signquery(array $parameter, string $signKey)
{
ksort($parameter); //重新排序$data数组
reset($parameter); //内部指针指向数组中的第一个元素
$sign = '';
$urls = '';
foreach ($parameter as $key => $val) {
if ($val == '') continue;
if ($key != 'sign') {
if ($sign != '') {
$sign .= "&";
$urls .= "&";
}
$sign .= "$key=$val"; //拼接为url参数形式
$urls .= "$key=" . urlencode($val); //拼接为url参数形式
}
}
$sign = md5($sign . $signKey);//密码追加进入开始MD5签名
$query = $urls . '&sign=' . $sign; //创建订单所需的参数
return $query;
}
}
if (! function_exists('signquery_string')) {
function signquery_string(array $data)
{
ksort($data); //排序post参数
reset($data); //内部指针指向数组中的第一个元素
$sign = ''; //加密字符串初始化
foreach ($data as $key => $val) {
if ($val == '' || $key == 'sign') continue; //跳过这些不签名
if ($sign) $sign .= '&'; //第一个字符串签名不加& 其他加&连接起来参数
$sign .= "$key=$val"; //拼接为url参数形式
}
return $sign;
}
}
if (!function_exists('picture_ulr')) {
/**
* 生成前台图片链接 不存在使用默认图
* @param string $file 图片地址
* @param false $getHost 是否只获取图片前缀域名
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\UrlGenerator|string
*/
function picture_ulr($file, $getHost = false)
{
if ($getHost) return Storage::disk('admin')->url('');
return $file ? Storage::disk('admin')->url($file) : url('assets/common/images/default.jpg');
}
}
if (!function_exists('assoc_unique')) {
function assoc_unique($arr, $key)
{
$tmp_arr = array();
foreach ($arr as $k => $v) {
if (in_array($v[$key], $tmp_arr)) {//搜索$v[$key]是否在$tmp_arr数组中存在若存在返回true
unset($arr[$k]);
} else {
$tmp_arr[] = $v[$key];
}
}
sort($arr); //sort函数对数组进行排序
return $arr;
}
}

View File

@ -0,0 +1,54 @@
<?php
/**
* The file was created by Assimon.
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
namespace App\Http\Controllers;
class BaseController extends Controller
{
/**
* 渲染模板
*
* @param string $tpl 模板名称
* @param array $data 数据
* @param array $pageTitle 页面标题
*
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
protected function render(string $tpl, $data = [], string $pageTitle = '')
{
$layout = dujiaoka_config_get('template', 'unicorn');
$tplPath = $layout . '/' .$tpl;
return view($tplPath, $data)->with('page_title', $pageTitle);
}
/**
* 错误提示
*
* @param string $content 提示内容
* @param string $jumpUri 跳转url
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
protected function err(string $content, $jumpUri = '')
{
$layout = dujiaoka_config_get('template', 'unicorn');
$tplPath = $layout . '/errors/error';
return view($tplPath, ['title' => __('dujiaoka.error_title'), 'content' => $content, 'url' => $jumpUri])
->with('page_title', __('dujiaoka.error_title'));
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}

View File

@ -0,0 +1,189 @@
<?php
namespace App\Http\Controllers\Home;
use App\Exceptions\RuleValidationException;
use App\Http\Controllers\BaseController;
use App\Models\Pay;
use Germey\Geetest\Geetest;
use Illuminate\Database\DatabaseServiceProvider;
use Illuminate\Database\QueryException;
use Illuminate\Encryption\Encrypter;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
class HomeController extends BaseController
{
/**
* 商品服务层.
* @var \App\Service\PayService
*/
private $goodsService;
/**
* 支付服务层
* @var \App\Service\PayService
*/
private $payService;
public function __construct()
{
$this->goodsService = app('Service\GoodsService');
$this->payService = app('Service\PayService');
}
/**
* 首页.
*
* @param Request $request
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function index(Request $request)
{
$goods = $this->goodsService->withGroup();
return $this->render('static_pages/home', ['data' => $goods], __('dujiaoka.page-title.home'));
}
/**
* 商品详情
*
* @param int $id
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function buy(int $id)
{
try {
$goods = $this->goodsService->detail($id);
$this->goodsService->validatorGoodsStatus($goods);
// 有没有优惠码可以展示
if (count($goods->coupon)) {
$goods->open_coupon = 1;
}
$formatGoods = $this->goodsService->format($goods);
// 加载支付方式.
$client = Pay::PAY_CLIENT_PC;
if (app('Jenssegers\Agent')->isMobile()) {
$client = Pay::PAY_CLIENT_MOBILE;
}
$formatGoods->payways = $this->payService->pays($client);
return $this->render('static_pages/buy', $formatGoods, $formatGoods->gd_name);
} catch (RuleValidationException $ruleValidationException) {
return $this->err($ruleValidationException->getMessage());
}
}
/**
* 极验行为验证
*
* @param Request $request
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function geetest(Request $request)
{
$data = [
'user_id' => @Auth::user()?@Auth::user()->id:'UnLoginUser',
'client_type' => 'web',
'ip_address' => \Illuminate\Support\Facades\Request::ip()
];
$status = Geetest::preProcess($data);
session()->put('gtserver', $status);
session()->put('user_id', $data['user_id']);
return Geetest::getResponseStr();
}
/**
* 安装页面
*
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function install(Request $request)
{
return view('common/install');
}
/**
* 执行安装
*
* @param Request $request
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function doInstall(Request $request)
{
try {
$dbConfig = config('database');
$mysqlDB = [
'host' => $request->input('db_host'),
'port' => $request->input('db_port'),
'database' => $request->input('db_database'),
'username' => $request->input('db_username'),
'password' => $request->input('db_password'),
];
$dbConfig['connections']['mysql'] = array_merge($dbConfig['connections']['mysql'], $mysqlDB);
// Redis
$redisDB = [
'host' => $request->input('redis_host'),
'password' => $request->input('redis_password', 'null'),
'port' => $request->input('redis_port'),
];
$dbConfig['redis']['default'] = array_merge($dbConfig['redis']['default'], $redisDB);
config(['database' => $dbConfig]);
DB::purge();
// db测试
DB::connection()->select('select 1 limit 1');
// redis测试
Redis::set('dujiaoka_com', 'ok');
Redis::get('dujiaoka_com');
// 获得文件模板
$envExamplePath = base_path() . DIRECTORY_SEPARATOR . '.env.example';
$envPath = base_path() . DIRECTORY_SEPARATOR . '.env';
$installLock = base_path() . DIRECTORY_SEPARATOR . 'install.lock';
$installSql = database_path() . DIRECTORY_SEPARATOR . 'sql' . DIRECTORY_SEPARATOR . 'install.sql';
$envTemp = file_get_contents($envExamplePath);
$postData = $request->all();
// 临时写入key
$postData['app_key'] = 'base64:' . base64_encode(
Encrypter::generateKey(config('app.cipher'))
);
foreach ($postData as $key => $item) {
$envTemp = str_replace('{' . $key . '}', $item, $envTemp);
}
// 写入配置
file_put_contents($envPath, $envTemp);
// 导入sql
DB::unprepared(file_get_contents($installSql));
// 写入安装锁
file_put_contents($installLock, 'install ok');
return 'success';
} catch (\RedisException $exception) {
return 'Redis配置错误 :' . $exception->getMessage();
} catch (QueryException $exception) {
return '数据库配置错误 :' . $exception->getMessage();
} catch (\Exception $exception) {
return $exception->getMessage();
}
}
}

View File

@ -0,0 +1,259 @@
<?php
namespace App\Http\Controllers\Home;
use App\Exceptions\RuleValidationException;
use App\Http\Controllers\BaseController;
use App\Models\Order;
use App\Service\OrderProcessService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cookie;
use Illuminate\Support\Facades\DB;
/**
* 订单控制器
*
* Class OrderController
* @package App\Http\Controllers\Home
* @author: Assimon
* @email: Ashang@utf8.hk
* @blog: https://utf8.hk
* Date: 2021/5/30
*/
class OrderController extends BaseController
{
/**
* 订单服务层
* @var \App\Service\OrderService
*/
private $orderService;
/**
* 订单处理层.
* @var OrderProcessService
*/
private $orderProcessService;
public function __construct()
{
$this->orderService = app('Service\OrderService');
$this->orderProcessService = app('Service\OrderProcessService');
}
/**
* 创建订单
*
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @throws \Illuminate\Validation\ValidationException
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function createOrder(Request $request)
{
DB::beginTransaction();
try {
$this->orderService->validatorCreateOrder($request);
$goods = $this->orderService->validatorGoods($request);
$this->orderService->validatorLoopCarmis($request);
// 设置商品
$this->orderProcessService->setGoods($goods);
// 优惠码
$coupon = $this->orderService->validatorCoupon($request);
// 设置优惠码
$this->orderProcessService->setCoupon($coupon);
$otherIpt = $this->orderService->validatorChargeInput($goods, $request);
$this->orderProcessService->setOtherIpt($otherIpt);
// 数量
$this->orderProcessService->setBuyAmount($request->input('by_amount'));
// 支付方式
$this->orderProcessService->setPayID($request->input('payway'));
// 下单邮箱
$this->orderProcessService->setEmail($request->input('email'));
// ip地址
$this->orderProcessService->setBuyIP($request->getClientIp());
// 查询密码
$this->orderProcessService->setSearchPwd($request->input('search_pwd', ''));
// 创建订单
$order = $this->orderProcessService->createOrder();
DB::commit();
// 设置订单cookie
$this->queueCookie($order->order_sn);
return redirect(url('/bill', ['orderSN' => $order->order_sn]));
} catch (RuleValidationException $exception) {
DB::rollBack();
return $this->err($exception->getMessage());
}
}
/**
* 设置订单cookie.
* @param string $orderSN 订单号.
*/
private function queueCookie(string $orderSN) : void
{
// 设置订单cookie
$cookies = Cookie::get('dujiaoka_orders');
if (empty($cookies)) {
Cookie::queue('dujiaoka_orders', json_encode([$orderSN]));
} else {
$cookies = json_decode($cookies, true);
array_push($cookies, $orderSN);
Cookie::queue('dujiaoka_orders', json_encode($cookies));
}
}
/**
* 结账
*
* @param string $orderSN
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function bill(string $orderSN)
{
$order = $this->orderService->detailOrderSN($orderSN);
if (empty($order)) {
return $this->err(__('dujiaoka.prompt.order_does_not_exist'));
}
if ($order->status == Order::STATUS_EXPIRED) {
return $this->err(__('dujiaoka.prompt.order_is_expired'));
}
return $this->render('static_pages/bill', $order, __('dujiaoka.page-title.bill'));
}
/**
* 订单状态监测
*
* @param string $orderSN 订单号
* @return \Illuminate\Http\JsonResponse
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function checkOrderStatus(string $orderSN)
{
$order = $this->orderService->detailOrderSN($orderSN);
// 订单不存在或者已经过期
if (!$order || $order->status == Order::STATUS_EXPIRED) {
return response()->json(['msg' => 'expired', 'code' => 400001]);
}
// 订单已经支付
if ($order->status == Order::STATUS_WAIT_PAY) {
return response()->json(['msg' => 'wait....', 'code' => 400000]);
}
// 成功
if ($order->status > Order::STATUS_WAIT_PAY) {
return response()->json(['msg' => 'success', 'code' => 200]);
}
}
/**
* 通过订单号展示订单详情
*
* @param string $orderSN 订单号.
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function detailOrderSN(string $orderSN)
{
$order = $this->orderService->detailOrderSN($orderSN);
// 订单不存在或者已经过期
if (!$order) {
return $this->err(__('dujiaoka.prompt.order_does_not_exist'));
}
return $this->render('static_pages/orderinfo', ['orders' => [$order]], __('dujiaoka.page-title.order-detail'));
}
/**
* 订单号查询
*
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function searchOrderBySN(Request $request)
{
return $this->detailOrderSN($request->input('order_sn'));
}
/**
* 通过邮箱查询
*
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function searchOrderByEmail(Request $request)
{
if (
!$request->has('email') ||
(
dujiaoka_config_get('is_open_search_pwd', \App\Models\BaseModel::STATUS_CLOSE) == \App\Models\BaseModel::STATUS_OPEN &&
!$request->has('search_pwd')
)
) {
return $this->err(__('dujiaoka.prompt.server_illegal_request'));
}
$orders = $this->orderService->withEmailAndPassword($request->input('email'), $request->input('search_pwd',''));
if (!$orders) {
return $this->err(__('dujiaoka.prompt.no_related_order_found'));
}
return $this->render('static_pages/orderinfo', ['orders' => $orders], __('dujiaoka.page-title.order-detail'));
}
/**
* 通过浏览器缓存查询
* @param Request $request
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function searchOrderByBrowser(Request $request)
{
$cookies = Cookie::get('dujiaoka_orders');
if (empty($cookies)) {
return $this->err(__('dujiaoka.prompt.no_related_order_found_for_cache'));
}
$orderSNS = json_decode($cookies, true);
$orders = $this->orderService->byOrderSNS($orderSNS);
return $this->render('static_pages/orderinfo', ['orders' => $orders], __('dujiaoka.page-title.order-detail'));
}
/**
* 订单查询页
*
* @param Request $request
* @return mixed
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function orderSearch(Request $request)
{
return $this->render('static_pages/searchOrder', [], __('dujiaoka.page-title.order-search'));
}
}

View File

@ -0,0 +1,111 @@
<?php
namespace App\Http\Controllers\Pay;
use App\Exceptions\RuleValidationException;
use App\Http\Controllers\PayController;
use Illuminate\Http\Request;
use Yansongda\Pay\Pay;
class AlipayController extends PayController
{
/**
* 支付宝支付网关
*
* @param string $payway
* @param string $orderSN
*/
public function gateway(string $payway, string $orderSN)
{
try {
// 加载网关
$this->loadGateWay($orderSN, $payway);
$config = [
'app_id' => $this->payGateway->merchant_id,
'ali_public_key' => $this->payGateway->merchant_key,
'private_key' => $this->payGateway->merchant_pem,
'notify_url' => url($this->payGateway->pay_handleroute . '/notify_url'),
'return_url' => url('detail-order-sn', ['orderSN' => $this->order->order_sn]),
'http' => [ // optional
'timeout' => 10.0,
'connect_timeout' => 10.0,
],
];
$order = [
'out_trade_no' => $this->order->order_sn,
'total_amount' => (float)$this->order->actual_price,
'subject' => $this->order->order_sn
];
switch ($payway){
case 'zfbf2f':
case 'alipayscan':
try{
$result = Pay::alipay($config)->scan($order)->toArray();
$result['payname'] = $this->order->order_sn;
$result['actual_price'] = (float)$this->order->actual_price;
$result['orderid'] = $this->order->order_sn;
$result['jump_payuri'] = $result['qr_code'];
return $this->render('static_pages/qrpay', $result, __('dujiaoka.scan_qrcode_to_pay'));
} catch (\Exception $e) {
return $this->err(__('dujiaoka.prompt.abnormal_payment_channel') . $e->getMessage());
}
case 'aliweb':
try{
$result = Pay::alipay($config)->web($order);
return $result;
} catch (\Exception $e) {
return $this->err(__('dujiaoka.prompt.abnormal_payment_channel') . $e->getMessage());
}
case 'aliwap':
try{
$result = Pay::alipay($config)->wap($order);
return $result;
} catch (\Exception $e) {
return $this->err(__('dujiaoka.prompt.abnormal_payment_channel') . $e->getMessage());
}
}
} catch (RuleValidationException $exception) {
return $this->err($exception->getMessage());
}
}
/**
* 异步通知
*/
public function notifyUrl(Request $request)
{
$orderSN = $request->input('out_trade_no');
$order = $this->orderService->detailOrderSN($orderSN);
if (!$order) {
return 'error';
}
$payGateway = $this->payService->detail($order->pay_id);
if (!$payGateway) {
return 'error';
}
if($payGateway->pay_handleroute != '/pay/alipay'){
return 'fail';
}
$config = [
'app_id' => $payGateway->merchant_id,
'ali_public_key' => $payGateway->merchant_key,
'private_key' => $payGateway->merchant_pem,
];
$pay = Pay::alipay($config);
try{
// 验证签名
$result = $pay->verify();
if ($result->trade_status == 'TRADE_SUCCESS' || $result->trade_status == 'TRADE_FINISHED') {
$this->orderProcessService->completedOrder($result->out_trade_no, $result->total_amount, $result->trade_no);
}
return 'success';
} catch (\Exception $exception) {
return 'fail';
}
}
}

View File

@ -0,0 +1,143 @@
<?php
namespace App\Http\Controllers\Pay;
use App\Exceptions\RuleValidationException;
use App\Http\Controllers\PayController;
use Illuminate\Http\Request;
class CoinbaseController extends PayController
{
public function gateway(string $payway, string $orderSN)
{
try {
// 加载网关
$this->loadGateWay($orderSN, $payway);
//构造要请求的参数数组,无需改动
switch ($payway) {
case 'coinbase':
default:
try {
$createOrderUrl="https://api.commerce.coinbase.com/charges";
$price_amount = sprintf('%.2f', (float)$this->order->actual_price);// 只取小数点后两位
$fees = (double)$this->payGateway->merchant_id;//手续费费率 比如 0.05
if($fees>0.00)
{
$price_amount =(double)$price_amount * (1.00+$fees);// 价格 * 1 + 0.05
}
$redirect_url = url('detail-order-sn', ['orderSN' => $this->order->order_sn]); //同步地址
$cancel_url = url('detail-order-sn', ['orderSN' => $this->order->order_sn]); //同步地址
$config = [
'name'=>$this->order->title,
'description'=>$this->order->title.'需付款'.$price_amount.'元',
'pricing_type' => 'fixed_price',
'local_price' => [
'amount' => $price_amount,
'currency' => 'CNY'
],
'metadata' => [
'customer_id' => $this->order->order_sn,
'customer_name' => $this->order->title
],
'redirect_url' =>$redirect_url,
'cancel_url'=> $cancel_url
];
$header = array();
$header[] = 'Content-Type:application/json';
$header[] = 'X-CC-Api-Key:'.$this->payGateway->merchant_key; //APP key
$header[] = 'X-CC-Version: 2018-03-22';
$ch = curl_init(); //使用curl请求
curl_setopt($ch, CURLOPT_URL, $createOrderUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($config));
$coinbase_json = curl_exec($ch);
curl_close($ch);
$coinbase_date=json_decode($coinbase_json,true);
if(is_array($coinbase_date))
{
$payment_url = $coinbase_date['data']['hosted_url'];
}
else
{
return 'fail|Coinbase支付接口请求失败';
}
return redirect()->away($payment_url);
} catch (\Exception $e) {
throw new RuleValidationException(__('dujiaoka.prompt.abnormal_payment_channel') . $e->getMessage());
}
break;
}
} catch (RuleValidationException $exception) {
return $this->err($exception->getMessage());
}
}
public function notifyUrl(Request $request)
{
$payload = file_get_contents( 'php://input' );
$sig = $_SERVER['HTTP_X_CC_WEBHOOK_SIGNATURE'];
$data = json_decode( $payload, true );
$event_data = $data['event']['data'];
$order = $this->orderService->detailOrderSN($event_data['metadata']['customer_id']);//
if (!$order) {
return 'fail';
}
$payGateway = $this->payService->detail($order->pay_id);
if (!$payGateway) {
return 'fail';
}
if($payGateway->pay_handleroute != 'pay/coinbase'){
return 'fail';
}
$secret = $payGateway->merchant_pem;//共享密钥
$sig2 = hash_hmac( 'sha256', $payload, $secret );
$result_str=array("confirmed","resolved");//返回的结果字符串数组
if (!empty( $payload ) && ($sig === $sig2))
{
foreach ($event_data['payments'] as $payment) {
//if ((strtolower($payment['status']) === 'confirmed')||(strtolower($payment['status']) === 'resolved')) {
if(in_array(strtolower($payment['status']),$result_str)){
$return_pay_amount = $payment['value']['local']['amount'];
$return_currency=$payment['value']['local']['currency'];
$return_status=strtolower($payment['status']);
}
}
if($return_currency !== 'CNY')
{
return 'error|Notify: Wrong currency:'.$return_currency;
}
$bccomp = bccomp($order->actual_price, $return_pay_amount, 2); //如果订单金额 大于 实际支付金额 返回1抛出异常
if ($bccomp == 1) {
throw new \Exception(__('Coinbase付款金额不足'));
}
$return_merchant_order_id = $event_data['metadata']['customer_id'];//卡网订单号
$tradeid = $event_data['code'];//Coinbase订单号
//if($return_status === 'confirmed'||$return_status === 'resolved')
if(in_array(strtolower($payment['status']),$result_str)) {
$this->orderProcessService->completedOrder($return_merchant_order_id, $order->actual_price, $tradeid);// 卡网订单号,订单金额(不能传入支付金额,否则抛出订单金额不一致异常),收款平台订单号
return "{\"status\": 200}";
} else {
//不合法的数据
return 'fail';
//返回失败 继续补单
}
} else {
//不合法的数据
return 'fail|wrong sig';
//返回失败 继续补单
}
}
}

View File

@ -0,0 +1,104 @@
<?php
/**
* The file was created by Assimon.
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
namespace App\Http\Controllers\Pay;
use App\Exceptions\RuleValidationException;
use App\Http\Controllers\PayController;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Http\Request;
class EpusdtController extends PayController
{
public function gateway(string $payway, string $orderSN)
{
try {
// 加载网关
$this->loadGateWay($orderSN, $payway);
//构造要请求的参数数组,无需改动
$parameter = [
"amount" => (float)$this->order->actual_price,//原价
"order_id" => $this->order->order_sn, //可以是用户ID,站内商户订单号,用户名
'redirect_url' => route('epusdt-return', ['order_id' => $this->order->order_sn]),
'notify_url' => url($this->payGateway->pay_handleroute . '/notify_url'),
];
$parameter['signature'] = $this->epusdtSign($parameter, $this->payGateway->merchant_id);
$client = new Client([
'headers' => [ 'Content-Type' => 'application/json' ]
]);
$response = $client->post($this->payGateway->merchant_pem, ['body' => json_encode($parameter)]);
$body = json_decode($response->getBody()->getContents(), true);
if (!isset($body['status_code']) || $body['status_code'] != 200) {
return $this->err(__('dujiaoka.prompt.abnormal_payment_channel') . $body['message']);
}
return redirect()->away($body['data']['payment_url']);
} catch (RuleValidationException $exception) {
} catch (GuzzleException $exception) {
return $this->err($exception->getMessage());
}
}
private function epusdtSign(array $parameter, string $signKey)
{
ksort($parameter);
reset($parameter); //内部指针指向数组中的第一个元素
$sign = '';
$urls = '';
foreach ($parameter as $key => $val) {
if ($val == '') continue;
if ($key != 'signature') {
if ($sign != '') {
$sign .= "&";
$urls .= "&";
}
$sign .= "$key=$val"; //拼接为url参数形式
$urls .= "$key=" . urlencode($val); //拼接为url参数形式
}
}
$sign = md5($sign . $signKey);//密码追加进入开始MD5签名
return $sign;
}
public function notifyUrl(Request $request)
{
$data = $request->all();
$order = $this->orderService->detailOrderSN($data['order_id']);
if (!$order) {
return 'fail';
}
$payGateway = $this->payService->detail($order->pay_id);
if (!$payGateway) {
return 'fail';
}
if($payGateway->pay_handleroute != 'pay/epusdt'){
return 'fail';
}
$signature = $this->epusdtSign($data, $payGateway->merchant_id);
if ($data['signature'] != $signature) { //不合法的数据
return 'fail'; //返回失败 继续补单
} else {
//合法的数据
//业务处理
$this->orderProcessService->completedOrder($data['order_id'], $data['amount'], $data['trade_id']);
return 'ok';
}
}
public function returnUrl(Request $request)
{
$oid = $request->get('order_id');
// 异步通知还没到就跳转了所以这里休眠2秒
sleep(2);
return redirect(url('detail-order-sn', ['orderSN' => $oid]));
}
}

View File

@ -0,0 +1,83 @@
<?php
namespace App\Http\Controllers\Pay;
use App\Exceptions\RuleValidationException;
use App\Http\Controllers\PayController;
use Illuminate\Http\Request;
class MapayController extends PayController
{
public function gateway(string $payway, string $orderSN)
{
try {
// 加载网关
$this->loadGateWay($orderSN, $payway);
//构造要请求的参数数组,无需改动
$parameter = array(
"id" => (int)$this->payGateway->merchant_id,//平台ID号
"price" => (float)$this->order->actual_price,//原价
"pay_id" => $this->order->order_sn, //可以是用户ID,站内商户订单号,用户名
"param" => $this->payGateway->pay_check,//自定义参数
"act" => 0,//是否开启认证版的免挂机功能
"outTime" => 120,//二维码超时设置
"page" => 1,//付款页面展示方式
'return_url' => url('detail-order-sn', ['orderSN' => $this->order->order_sn]),
'notify_url' => url($this->payGateway->pay_handleroute . '/notify_url'),
"pay_type" => 0,//支付宝使用官方接口
"chart" => 'utf-8'//字符编码方式
//其他业务参数根据在线开发文档,添加参数.文档地址:https://codepay.fateqq.com/apiword/
//如"参数名"=>"参数值"
);
switch ($payway){
case 'mqq':
$parameter['type'] = 2;
break;
case 'mzfb':
$parameter['type'] = 1;
break;
case 'mwx':
default:
$parameter['type'] = 3;
break;
}
$quri = md5_signquery($parameter, $this->payGateway->merchant_pem);
$payurl = $this->payGateway->merchant_key . $quri; //支付页面
return redirect()->away($payurl);
} catch (RuleValidationException $exception) {
return $this->err($exception->getMessage());
}
}
public function notifyUrl(Request $request)
{
$data = $request->post();
$order = $this->orderService->detailOrderSN($data['pay_id']);
if (!$order) {
return 'fail';
}
$payGateway = $this->payService->detail($order->pay_id);
if (!$payGateway) {
return 'fail';
}
if($payGateway->pay_handleroute != '/pay/mapay'){
return 'fail';
}
$query = signquery_string($data);
if (!$data['pay_no'] || md5($query . $payGateway->merchant_pem ) != $data['sign']) { //不合法的数据
return 'fail'; //返回失败 继续补单
} else { //合法的数据
//业务处理
$this->orderProcessService->completedOrder($data['pay_id'], $data['money'], $data['pay_id']);
return 'success';
}
}
}

View File

@ -0,0 +1,70 @@
<?php
namespace App\Http\Controllers\Pay;
use App\Exceptions\RuleValidationException;
use App\Http\Controllers\PayController;
use Illuminate\Http\Request;
use Xhat\Payjs\Facades\Payjs;
class PayjsController extends PayController
{
public function gateway(string $payway, string $orderSN)
{
try {
// 加载网关
$this->loadGateWay($orderSN, $payway);
// 构造订单基础信息
$data = [
'body' => $this->order->order_sn, // 订单标题
'total_fee' => bcmul($this->order->actual_price, 100, 0), // 订单金额
'out_trade_no' => $this->order->order_sn, // 订单号
'notify_url' => url($this->payGateway->pay_handleroute . '/notify_url'),
];
config(['payjs.mchid' => $this->payGateway->merchant_id, 'payjs.key' => $this->payGateway->merchant_pem]);
switch ($payway){
case 'payjswescan':
try{
$payres = Payjs::native($data);
if ($payres['return_code'] != 1) {
throw new RuleValidationException($payres['return_msg']);
}
$result['payname'] = $this->payGateway->pay_name;
$result['actual_price'] = (float)$this->order->actual_price;
$result['orderid'] = $this->order->order_sn;
$result['qr_code'] = $payres['code_url'];
return $this->render('static_pages/qrpay', $result, __('dujiaoka.scan_qrcode_to_pay'));
} catch (\Exception $e) {
throw new RuleValidationException(__('dujiaoka.prompt.abnormal_payment_channel') . $e->getMessage());
}
break;
}
} catch (RuleValidationException $exception) {
return $this->err($exception->getMessage());
}
}
public function notifyUrl(Request $request)
{
$orderSN = $request->input('out_trade_no');
$order = $this->orderService->detailOrderSN($orderSN);
if (!$order) {
return 'error';
}
$payGateway = $this->payService->detail($order->pay_id);
if (!$payGateway) {
return 'error';
}
if($payGateway->pay_handleroute != '/pay/payjs'){
return 'fail';
}
config(['payjs.mchid' => $payGateway->merchant_id, 'payjs.key' => $payGateway->merchant_pem]);
$notify_info = Payjs::notify();
$totalFee = bcdiv($notify_info['total_fee'], 100, 2);
$this->orderProcessService->completedOrder($notify_info['out_trade_no'], $totalFee, $notify_info['payjs_order_id']);
return 'success';
}
}

View File

@ -0,0 +1,148 @@
<?php
namespace App\Http\Controllers\Pay;
use AmrShawky\LaravelCurrency\Facade\Currency;
use App\Exceptions\RuleValidationException;
use App\Http\Controllers\PayController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use PayPal\Api\Amount;
use PayPal\Api\Details;
use PayPal\Api\Item;
use PayPal\Api\ItemList;
use PayPal\Api\Payer;
use PayPal\Api\Payment;
use PayPal\Api\PaymentExecution;
use PayPal\Api\RedirectUrls;
use PayPal\Api\Transaction;
use PayPal\Auth\OAuthTokenCredential;
use PayPal\Exception\PayPalConnectionException;
use PayPal\Rest\ApiContext;
class PaypalPayController extends PayController
{
const Currency = 'USD'; //货币单位
public function gateway(string $payway, string $orderSN)
{
try {
// 加载网关
$this->loadGateWay($orderSN, $payway);
$paypal = new ApiContext(
new OAuthTokenCredential(
$this->payGateway->merchant_key,
$this->payGateway->merchant_pem
)
);
$paypal->setConfig(['mode' => 'live']);
$product = $this->order->title;
// 得到汇率
$total = Currency::convert()
->from('CNY')
->to('USD')
->amount($this->order->actual_price)
->round(2)
->get();
$shipping = 0;
$description = $this->order->title;
$payer = new Payer();
$payer->setPaymentMethod('paypal');
$item = new Item();
$item->setName($product)->setCurrency(self::Currency)->setQuantity(1)->setPrice($total);
$itemList = new ItemList();
$itemList->setItems([$item]);
$details = new Details();
$details->setShipping($shipping)->setSubtotal($total);
$amount = new Amount();
$amount->setCurrency(self::Currency)->setTotal($total)->setDetails($details);
$transaction = new Transaction();
$transaction->setAmount($amount)->setItemList($itemList)->setDescription($description)->setInvoiceNumber($this->order->order_sn);
$redirectUrls = new RedirectUrls();
$redirectUrls->setReturnUrl(route('paypal-return', ['success' => 'ok', 'orderSN' => $this->order->order_sn]))->setCancelUrl(route('paypal-return', ['success' => 'no', 'orderSN' => $this->order->order_sn]));
$payment = new Payment();
$payment->setIntent('sale')->setPayer($payer)->setRedirectUrls($redirectUrls)->setTransactions([$transaction]);
$payment->create($paypal);
$approvalUrl = $payment->getApprovalLink();
return redirect($approvalUrl);
} catch (PayPalConnectionException $payPalConnectionException) {
return $this->err($payPalConnectionException->getMessage());
} catch (RuleValidationException $exception) {
return $this->err($exception->getMessage());
}
}
/**
*paypal 同步回调
*/
public function returnUrl(Request $request)
{
$success = $request->input('success');
$paymentId = $request->input('paymentId');
$payerID = $request->input('PayerID');
$orderSN = $request->input('orderSN');
if ($success == 'no' || empty($paymentId) || empty($payerID)) {
// 取消支付
redirect(url('detail-order-sn', ['orderSN' => $payerID]));
}
$order = $this->orderService->detailOrderSN($orderSN);
if (!$order) {
return 'error';
}
$payGateway = $this->payService->detail($order->pay_id);
if (!$payGateway) {
return 'error';
}
if($payGateway->pay_handleroute != '/pay/paypal'){
return 'error';
}
$paypal = new ApiContext(
new OAuthTokenCredential(
$payGateway->merchant_key,
$payGateway->merchant_pem
)
);
$paypal->setConfig(['mode' => 'live']);
$payment = Payment::get($paymentId, $paypal);
$execute = new PaymentExecution();
$execute->setPayerId($payerID);
try {
$payment->execute($execute, $paypal);
$this->orderProcessService->completedOrder($orderSN, $order->actual_price, $paymentId);
Log::info("paypal支付成功", ['支付成功支付ID【' . $paymentId . '】,支付人ID【' . $payerID . '】']);
} catch (\Exception $e) {
Log::error("paypal支付失败", ['支付失败支付ID【' . $paymentId . '】,支付人ID【' . $payerID . '】']);
}
return redirect(url('detail-order-sn', ['orderSN' => $orderSN]));
}
/**
* 异步通知
* TODO: 暂未实现,但是好像只实现同步回调即可。这个可以放在后面实现
*/
public function notifyUrl(Request $request)
{
//获取回调结果
$json_data = $this->get_JsonData();
if(!empty($json_data)){
Log::debug("paypal notify info:\r\n" . json_encode($json_data));
}else{
Log::debug("paypal notify fail:参加为空");
}
}
private function get_JsonData()
{
$json = file_get_contents('php://input');
if ($json) {
$json = str_replace("'", '', $json);
$json = json_decode($json,true);
}
return $json;
}
}

View File

@ -0,0 +1,113 @@
<?php
namespace App\Http\Controllers\Pay;
use App\Exceptions\RuleValidationException;
use App\Http\Controllers\PayController;
use Illuminate\Http\Request;
class PaysapiController extends PayController
{
const PAY_URI = 'https://pay.bearsoftware.net.cn/';
public function gateway(string $payway, string $orderSN)
{
try {
// 加载网关
$this->loadGateWay($orderSN, $payway);
//从网页传入price:支付价格, istype:支付渠道1-支付宝2-微信支付
$price = (float)$this->order->actual_price;
$orderuid = $this->order->email; //此处传入您网站用户的用户名方便在paysapi后台查看是谁付的款强烈建议加上。可忽略。
//校验传入的表单确保价格为正常价格整数1位小数2位小数都可以支付渠道只能是1或者2orderuid长度不要超过33个中英文字。
//此处就在您服务器生成新订单并把创建的订单号传入到下面的orderid中。
$goodsname = $this->order->order_sn;
$orderid = $this->order->order_sn; //每次有任何参数变化,订单号就变一个吧。
$uid = $this->payGateway->merchant_id; //"此处填写PaysApi的uid";
$token = $this->payGateway->merchant_pem; //"此处填写PaysApi的Token";
$return_url = route('paysapi-return', ['order_id' => $this->order->order_sn]);
$notify_url = url($this->payGateway->pay_handleroute . '/notify_url');
switch ($payway){
case 'pszfb':
$istype = 1;
break;
case 'pswx':
default:
$istype = 2;
break;
}
$key = md5($goodsname. $istype . $notify_url . $orderid . $orderuid . $price . $return_url . $token . $uid);
//经常遇到有研发问为啥key值返回错误大多数原因1.参数的排列顺序不对2.上面的参数少传了但是这里的key值又带进去计算了导致服务端key算出来和你的不一样。
$html = "
<html><head>
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">
<title>loading pay...</title>
<style type=\"text/css\">
body {margin:0;padding:0;}
p {position:absolute;
left:50%;top:50%;
width:330px;height:30px;
margin:-35px 0 0 -160px;
padding:20px;font:bold 14px/30px \"宋体\", Arial;
text-indent:22px;border:1px solid #c5d0dc;}
#waiting {font-family:Arial;}
</style>
<script>
function open_without_referrer(link){
document.body.appendChild(document.createElement('iframe')).src='javascript:\"<script>top.location.replace(\''+link+'\')<\/script>\"';
}
</script>
</head>
<body style=\"\">
<form id=\"alipaysubmit\" name=\"alipaysubmit\" action=\"".self::PAY_URI."\" method=\"post\">
<input type=\"hidden\" name=\"goodsname\" value=\"".$goodsname."\">
<input type=\"hidden\" name=\"istype\" value=\"".$istype."\">
<input type=\"hidden\" name=\"key\" value=\"".$key."\">
<input type=\"hidden\" name=\"notify_url\" value=\"".$notify_url."\">
<input type=\"hidden\" name=\"orderid\" value=\"".$orderid."\">
<input type=\"hidden\" name=\"orderuid\" value=\"".$orderuid."\">
<input type=\"hidden\" name=\"price\" value=\"".$price."\">
<input type=\"hidden\" name=\"return_url\" value=\"".$return_url."\">
<input type=\"hidden\" name=\"uid\" value=\"".$uid."\">
<input type=\"submit\" value=\"正在跳转\">
</form><script>document.forms['alipaysubmit'].submit();</script></body></html>
";
return $html;
} catch (RuleValidationException $exception) {
return $this->err($exception->getMessage());
}
}
public function notifyUrl(Request $request)
{
$data = $request->post();
$order = $this->orderService->detailOrderSN($data['orderid']);
if (!$order) {
return 'error';
}
$payGateway = $this->payService->detail($order->pay_id);
if (!$payGateway) {
return 'error';
}
if($payGateway->pay_handleroute != '/pay/paysapi'){
return 'error';
}
$temps = md5($data['orderid'] . $data['orderuid'] . $data['paysapi_id'] . $data['price'] . $data['realprice'] . $payGateway->merchant_pem);
if ($temps != $data['key']){
return 'fail';
}else{
//校验key成功是自己人。执行自己的业务逻辑加余额订单付款成功装备购买成功等等。
//业务处理
$this->orderProcessService->completedOrder($data['orderid'], $data['price'], $data['paysapi_id']);
return 'success';
}
}
public function returnUrl(Request $request)
{
$oid = $request->input('order_id');
sleep(1);
return redirect(url('detail-order-sn', ['orderSN' => $oid]));
}
}

View File

@ -0,0 +1,540 @@
<?php
namespace App\Http\Controllers\Pay;
use App\Exceptions\RuleValidationException;
use App\Http\Controllers\PayController;
use Illuminate\Http\Request;
use GuzzleHttp\Client;
use Illuminate\Support\Facades\Redis;
use URL;
class StripeController extends PayController
{
public function gateway(string $payway, string $orderSN)
{
// 加载网关
$this->loadGateWay($orderSN, $payway);
//构造要请求的参数数组,无需改动
switch ($payway) {
case 'wx':
case 'alipay':
default:
try {
\Stripe\Stripe::setApiKey($this->payGateway->merchant_id);
$amount = bcmul($this->order->actual_price, 100, 2);
$price = $this->order->actual_price;
$usd = bcmul($this->getUsdCurrency($this->order->actual_price), 100, 2);
$orderid = $this->order->order_sn;
$pk = $this->payGateway->merchant_id;
$return_url = site_url() . $this->payGateway->pay_handleroute . '/return_url/?orderid=' . $this->order->order_sn;
$html = "<html class=\"js cssanimations\">
<head lang=\"en\">
<meta charset=\"UTF-8\">
<title>收银台</title>
<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<meta name=\"format-detection\" content=\"telephone=no\">
<meta name=\"renderer\" content=\"webkit\">
<meta http-equiv=\"Cache-Control\" content=\"no-siteapp\">
<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/amazeui@2.7.2/dist/css/amazeui.min.css\">
<script src=\"https://cdn.jsdelivr.net/npm/jquery@2.1.4/dist/jquery.min.js\"></script>
<script src=\"https://cdn.jsdelivr.net/npm/jquery.qrcode@1.0.3/jquery.qrcode.min.js\"></script>
<script src=\"https://cdn.jsdelivr.net/npm/amazeui@2.7.2/dist/js/amazeui.min.js\"></script>
<script src=\"https://js.stripe.com/v3/\"></script>
<style>
@media only screen and (min-width: 641px) {
.am-offcanvas {
display: block;
position: static;
background: none;
}
.am-offcanvas-bar {
position: static;
width: auto;
background: none;
-webkit-transform: translate3d(0, 0, 0);
-ms-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
.am-offcanvas-bar:after {
content: none;
}
}
@media only screen and (max-width: 640px) {
.am-offcanvas-bar .am-nav > li > a {
color: #ccc;
border-radius: 0;
border-top: 1px solid rgba(0, 0, 0, .3);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .05)
}
.am-offcanvas-bar .am-nav > li > a:hover {
background: #404040;
color: #fff
}
.am-offcanvas-bar .am-nav > li.am-nav-header {
color: #777;
background: #404040;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .05);
text-shadow: 0 1px 0 rgba(0, 0, 0, .5);
border-top: 1px solid rgba(0, 0, 0, .3);
font-weight: 400;
font-size: 75%
}
.am-offcanvas-bar .am-nav > li.am-active > a {
background: #1a1a1a;
color: #fff;
box-shadow: inset 0 1px 3px rgba(0, 0, 0, .3)
}
.am-offcanvas-bar .am-nav > li + li {
margin-top: 0;
}
}
.my-head {
margin-top: 40px;
text-align: center;
}
.am-tab-panel {
text-align: center;
margin-top: 50px;
margin-bottom: 50px;
}
.my-footer {
border-top: 1px solid #eeeeee;
padding: 10px 0;
margin-top: 10px;
text-align: center;
}
.panel-title {
display: inline;
font-weight: bold;
}
.display-table {
display: table;
}
.display-tr {
display: table-row;
}
.display-td {
display: table-cell;
vertical-align: middle;
width: 61%;
}
.StripeElement {
box-sizing: border-box;
height: 40px;
padding: 10px 12px;
border: 1px solid transparent;
border-radius: 4px;
background-color: white;
box-shadow: 0 1px 3px 0 #e6ebf1;
-webkit-transition: box-shadow 150ms ease;
transition: box-shadow 150ms ease;
}
.StripeElement--focus {
box-shadow: 0 1px 3px 0 #cfd7df;
}
.StripeElement--invalid {
border-color: #fa755a;
}
.StripeElement--webkit-autofill {
background-color: #fefde5 !important;
}
.form-row {
width: 70%;
float: left;
}
.wrapper {
width: 670px;
margin: 0 auto;
}
label {
font-weight: 500;
font-size: 14px;
display: block;
margin-bottom: 8px;
}
.button {
border: none;
border-radius: 4px;
outline: none;
text-decoration: none;
color: #fff;
background: #32325d;
white-space: nowrap;
display: inline-block;
height: 40px;
line-height: 40px;
padding: 0 14px;
box-shadow: 0 4px 6px rgba(50, 50, 93, .11), 0 1px 3px rgba(0, 0, 0, .08);
border-radius: 4px;
font-size: 16px;
font-weight: 600;
letter-spacing: 0.025em;
text-decoration: none;
-webkit-transition: all 150ms ease;
transition: all 150ms ease;
margin-top: 10px;
}
</style>
</head>
<body>
<header class=\"am-g my-head\">
<div class=\"am-u-sm-12 am-article\">
<h1 class=\"am-article-title\">收银台</h1>
</div>
</header>
<hr class=\"am-article-divider\">
<div class=\"am-container\">
<h2>付款信息
<div class=\"am-topbar-right\"{$price}</div>
</h2>
<p><small>订单编号:$orderid</small></p>
<div class=\"am-tabs\" data-am-tabs=\"\">
<ul class=\"am-tabs-nav am-nav am-nav-tabs\">
<li class=\"am-active\"><a href=\"#alipay\">Alipay 支付宝</a></li>
<li class=\"request-wechat-pay\"><a href=\"#wcpay\">微信支付</a></li>
<li class=\"request-card-pay\"><a href=\"#cardpay\">银行卡支付</a></li>
</ul>
<div class=\"am-tabs-bd am-tabs-bd-ofv\"
style=\"touch-action: pan-y; user-select: none; -webkit-user-drag: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\">
<div class=\"am-tab-panel am-active\" id=\"alipay\">
<a class=\"am-btn am-btn-lg am-btn-warning am-btn-primary\" id=\"alipaybtn\" href=\"#\">进入支付宝付款</a>
<p></p>
</div>
<div class=\"am-tab-panel am-fade\" id=\"wcpay\">
<div class=\"text-align:center; margin:0 auto; width:60%\">
<div class=\"wcpay-qrcode\" style=\"text-align: center; \" data-requested=\"0\">
正在加载中...
</div>
</div>
</div>
<div class=\"am-tab-panel am-fade\" id=\"cardpay\">
<div class=\"text-align:center; margin:0 auto; width:60%\">
<div class=\"wrapper cardpay_content\" style=\"max-width:500px\">
<div class=\"am-alert am-alert-danger\" style=\"display:none\">支付失败,请更换卡片或检查输入信息</div>
<form action=\"/pay/stripe/charge\" method=\"post\" id=\"payment-form\">
<div class=\"form-row\">
<label for=\"card-element\">
<p class='am-alert am-alert-secondary'>借记卡或信用卡</p>
</label>
<div id=\"card-element\">
<!-- A Stripe Element will be inserted here. -->
</div>
<!-- Used to display form errors. -->
<div id=\"card-errors\" role=\"alert\"></div>
</div>
<div class=\"form-row\">
<button class=\"button\">支付</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
var stripe = Stripe('$pk');
var source = '';
// Create a Stripe client.
// Create an instance of Elements.
var elements = stripe.elements();
// Custom styling can be passed to options when creating an Element.
// (Note that this demo uses a wider set of styles than the guide below.)
var style = {
base: {
color: '#32325d',
fontFamily: '\"Helvetica Neue\", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '16px',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
};
// Create an instance of the card Element.
var card = elements.create('card', {style: style});
// Add an instance of the card Element into the `card-element` <div>.
card.mount('#card-element');
// Handle real-time validation errors from the card Element.
card.on('change', function (event) {
var displayError = document.getElementById('card-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
});
// Handle form submission.
var form = document.getElementById('payment-form');
form.addEventListener('submit', function (event) {
event.preventDefault();
$(\".button\").attr(\"disabled\",\"true\");
$(\".button\").html(\"请稍后\");
stripe.createToken(card).then(function (result) {
if (result.error) {
// Inform the user if there was an error.
var errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
} else {
// Send the token to your server.
stripeTokenHandler(result.token);
}
});
});
// Submit the form with the token ID.
function stripeTokenHandler(token) {
// Insert the token ID into the form so it gets submitted to the server
var form = document.getElementById('payment-form');
var hiddenInput = document.createElement('input');
var hiddenInput1 = document.createElement('input');
hiddenInput.setAttribute('type', 'hidden');
hiddenInput.setAttribute('name', 'stripeToken');
hiddenInput.setAttribute('value', token.id);
hiddenInput1.setAttribute('type', 'hidden');
hiddenInput1.setAttribute('name', 'orderid');
hiddenInput1.setAttribute('value', '$orderid');
form.appendChild(hiddenInput);
form.appendChild(hiddenInput1);
// Submit the form
//form.submit();
$.ajax({
url: '/pay/stripe/charge/?orderid=$orderid&stripeToken=' + token.id,
type: 'GET',
success: function (result) {
if (result == \"success\") {
$(\".cardpay_content\").html(\"\");
$(\".cardpay_content\").html(\"<p class='am-alert am-alert-success'>支付成功,正在跳转页面</p>\");
window.setTimeout(function () {
location.href = \"/detail-order-sn/$orderid\"
}, 800);
} else {
$(\".am-alert\").show();
$(\".button\").removeAttr(\"disabled\");
$(\".button\").html(\"支付\");
setTimeout(\" $('.am-alert').hide();\", 3000);
}
}
});
}
(function () {
stripe.createSource({
type: 'alipay',
amount: $amount,
currency: 'cny',
// 这里你需要渲染出一些用户的信息,不然后期没法知道是谁在付钱
owner: {
name: '$orderid',
},
redirect: {
return_url: '$return_url',
},
}).then(function (result) {
$(\"#alipaybtn\").attr(\"href\", result.source.redirect.url);
});
})();
function paymentcheck() {
$.ajax({
url: '/pay/stripe/check/?orderid=$orderid&source=' + source,
type: 'GET',
success: function (result) {
if (result == \"success\") {
$(\".wcpay-qrcode\").html(\"\");
$(\".wcpay-qrcode\").html(\"<p class='am-alert am-alert-success'>支付成功,正在跳转页面</p>\");
window.setTimeout(function () {
location.href = \"/detail-order-sn/$orderid\"
}, 800);
} else {
setTimeout(\"paymentcheck()\", 1000);
}
}
});
}
$(\".request-wechat-pay\").click(function () {
if ($(\".wcpay-qrcode\").data(\"requested\") == 0) {
stripe.createSource({
type: 'wechat',
amount: $usd,
currency: 'usd',
owner: {
name: '$orderid'
},
}).then(function (result) {
if (result.source.id) {
$(\".wcpay-qrcode\").html(\"<p class='am-alert am-alert-success'>打开微信 - 扫一扫</p>\");
$(\".wcpay-qrcode\").qrcode(result.source.wechat.qr_code_url);
$(\".wcpay-qrcode\").data(\"requested\", 1);
$(\".wcpay-qrcode\").data(\"sid\", result.source.id);
$(\".wcpay-qrcode\").data(\"scs\", result.source.client_secret);
source = result.source.id;
setTimeout(\"paymentcheck()\", 3000);
} else {
alert(\"微信支付加载失败\");
$(\".wcpay-qrcode\").html(\"<p class='am-alert am-alert-danger'>加载失败,请刷新页面。</p>\");
}
// handle result.error or result.source
});
}
});
</script>
</body>
</html>";
return $html;
} catch (\Exception $e) {
throw new RuleValidationException(__('dujiaoka.prompt.abnormal_payment_channel') . $e->getMessage());
}
break;
}
}
public function returnUrl(Request $request)
{
$data = $request->all();
$cacheord = $this->orderService->detailOrderSN($data['orderid']);
if (!$cacheord) {
return redirect(url('detail-order-sn', ['orderSN' => $data['orderid']]));
}
$payGateway = $this->payService->detail($cacheord->pay_id);
\Stripe\Stripe::setApiKey($payGateway -> merchant_pem);
$source_object = \Stripe\Source::retrieve($data['source']);
//die($source_object);
if ($source_object->status == 'chargeable') {
\Stripe\Charge::create([
'amount' => $source_object->amount,
'currency' => $source_object->currency,
'source' => $data['source'],
]);
if ($source_object->owner->name == $data['orderid']) {
$this->orderProcessService->completedOrder($data['orderid'], $source_object->amount / 100, $source_object->id);
}
}
return redirect(url('detail-order-sn', ['orderSN' => $data['orderid']]));
}
public function check(Request $request)
{
$data = $request->all();
$cacheord = $this->orderService->detailOrderSN($data['orderid']);
if (!$cacheord) {
//可能已异步回调成功,跳转
return 'fail';
} else {
$payGateway = $this->payService->detail($cacheord->pay_id);
\Stripe\Stripe::setApiKey($payGateway -> merchant_pem);
$source_object = \Stripe\Source::retrieve($data['source']);
if ($source_object->status == 'chargeable') {
\Stripe\Charge::create([
'amount' => $source_object->amount,
'currency' => $source_object->currency,
'source' => $data['source'],
]);
}
if ($source_object->status == 'consumed' && $source_object->owner->name == $data['orderid']) {
$this->orderProcessService->completedOrder($data['orderid'], $cacheord->actual_price, $source_object->id);
return 'success';
} else {
return 'fail';
}
}
}
public function charge(Request $request)
{
$data = $request->all();
$cacheord = $this->orderService->detailOrderSN($data['orderid']);
if (!$cacheord) {
//可能已异步回调成功,跳转
return 'fail';
} else {
try {
$payGateway = $this->payService->detail($cacheord->pay_id);
\Stripe\Stripe::setApiKey($payGateway -> merchant_pem);
$result = \Stripe\Charge::create([
'amount' => bcmul($this->getUsdCurrency($cacheord->actual_price), 100,0),
'currency' => 'usd',
'source' => $data['stripeToken'],
]);
if ($result->status == 'succeeded') {
$this->orderProcessService->completedOrder($data['orderid'], $cacheord->actual_price, $data['stripeToken']);
return 'success';
}
return $result;
} catch (\Exception $e) {
return $e->getMessage();
}
}
}
/**
* 根据RMB获取美元
* @param $cny
* @return float|int
* @throws \Exception
*/
public function getUsdCurrency($cny)
{
$client = new Client();
$res = $client->get('https://m.cmbchina.com/api/rate/fx-rate');
$fxrate = json_decode($res->getBody(), true);
$data = $fxrate['body']['data'];
if (!isset($data)) {
throw new \Exception('汇率接口异常');
}
$dfFxrate = 0.13;
foreach ($data as $item) {
if ($item['ccyNbr'] == "美元") {
$dfFxrate = bcdiv(100, $item['rtcOfr'], 2);
break;
}
}
return bcmul($cny , $dfFxrate , 2);
}
}

View File

@ -0,0 +1,105 @@
<?php
/**
* The file was created by LightCountry.
*
* @author https://github.com/LightCountry
* @copyright https://github.com/LightCountry
* @link https://github.com/LightCountry/TokenPay
*/
namespace App\Http\Controllers\Pay;
use App\Exceptions\RuleValidationException;
use App\Http\Controllers\PayController;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Http\Request;
class TokenPayController extends PayController
{
public function gateway(string $payway, string $orderSN)
{
try {
// 加载网关
$this->loadGateWay($orderSN, $payway);
//构造要请求的参数数组,无需改动
$parameter = [
"ActualAmount" => (float)$this->order->actual_price,//原价
"OutOrderId" => $this->order->order_sn,
"OrderUserKey" => $this->order->email,
"Currency" => $this->payGateway->merchant_id,
'RedirectUrl' => route('tokenpay-return', ['order_id' => $this->order->order_sn]),
'NotifyUrl' => url($this->payGateway->pay_handleroute . '/notify_url'),
];
$parameter['Signature'] = $this->VerifySign($parameter, $this->payGateway->merchant_key);
$client = new Client([
'headers' => [ 'Content-Type' => 'application/json' ]
]);
$response = $client->post($this->payGateway->merchant_pem.'/CreateOrder', ['body' => json_encode($parameter)]);
$body = json_decode($response->getBody()->getContents(), true);
if (!isset($body['success']) || $body['success'] != true) {
return $this->err(__('dujiaoka.prompt.abnormal_payment_channel') . $body['message']);
}
return redirect()->away($body['data']);
} catch (RuleValidationException $exception) {
} catch (GuzzleException $exception) {
return $this->err($exception->getMessage());
}
}
private function VerifySign(array $parameter, string $signKey)
{
ksort($parameter);
reset($parameter); //内部指针指向数组中的第一个元素
$sign = '';
$urls = '';
foreach ($parameter as $key => $val) {
if ($key != 'Signature') {
if ($sign != '') {
$sign .= "&";
$urls .= "&";
}
$sign .= "$key=$val"; //拼接为url参数形式
$urls .= "$key=" . urlencode($val); //拼接为url参数形式
}
}
$sign = md5($sign . $signKey);//密码追加进入开始MD5签名
return $sign;
}
public function notifyUrl(Request $request)
{
$data = $request->all();
$order = $this->orderService->detailOrderSN($data['OutOrderId']);
if (!$order) {
return 'fail';
}
$payGateway = $this->payService->detail($order->pay_id);
if (!$payGateway) {
return 'fail';
}
if($payGateway->pay_handleroute != 'pay/tokenpay'){
return 'fail';
}
//合法的数据
$signature = $this->VerifySign($data, $payGateway->merchant_key);
if ($data['Signature'] != $signature) { //不合法的数据
return 'fail'; //返回失败 继续补单
} else {
//合法的数据
//业务处理
$this->orderProcessService->completedOrder($data['OutOrderId'], $data['ActualAmount'], $data['Id']);
return 'ok';
}
}
public function returnUrl(Request $request)
{
$oid = $request->get('order_id');
// 异步通知还没到就跳转了所以这里休眠2秒
sleep(2);
return redirect(url('detail-order-sn', ['orderSN' => $oid]));
}
}

View File

@ -0,0 +1,94 @@
<?php
/**
* VpayController.php
* V免签
* Author iLay1678
* Created on 2020/5/1 11:59
*/
namespace App\Http\Controllers\Pay;
use App\Exceptions\RuleValidationException;
use App\Http\Controllers\PayController;
use Illuminate\Http\Request;
class VpayController extends PayController
{
public function gateway(string $payway, string $orderSN)
{
try {
// 加载网关
$this->loadGateWay($orderSN, $payway);
//构造要请求的参数数组,无需改动
$parameter = array(
"payId" => date('YmdHis') . rand(1, 65535),//平台ID号
"price" => (float)$this->order->actual_price,//原价
'param' => $this->order->order_sn,
'returnUrl' => route('vpay-return', ['order_id' => $this->order->order_sn]),
'notifyUrl' => url($this->payGateway->pay_handleroute . '/notify_url'),
"isHtml" => 1,
);
switch ($payway) {
case 'vzfb':
$parameter['type'] = 2;
break;
case 'vwx':
default:
$parameter['type'] = 1;
break;
}
$parameter['sign'] = md5($parameter['payId'] . $parameter['param'] . $parameter['type'] . $parameter['price'] . $this->payGateway->merchant_id);
$payurl = $this->payGateway->merchant_pem . 'createOrder?' . http_build_query($parameter); //支付页面
return redirect()->away($payurl);
} catch (RuleValidationException $exception) {
return $this->err($exception->getMessage());
}
}
public function notifyUrl(Request $request)
{
$data = $request->all();
$order = $this->orderService->detailOrderSN($data['param']);
if (!$order) {
return 'fail';
}
$payGateway = $this->payService->detail($order->pay_id);
if($payGateway->pay_handleroute != 'pay/vpay'){
return 'fail';
}
if (!$payGateway) {
return 'fail';
}
$key = $payGateway->merchant_id;//通讯密钥
$payId = $data['payId'];//商户订单号
$param = $data['param'];//创建订单的时候传入的参数
$type = $data['type'];//支付方式 微信支付为1 支付宝支付为2
$price = $data['price'];//订单金额
$reallyPrice = $data['reallyPrice'];//实际支付金额
$sign = $data['sign'];//校验签名,计算方式 = md5(payId + param + type + price + reallyPrice + 通讯密钥)
//开始校验签名
$_sign = md5($payId . $param . $type . $price . $reallyPrice . $key);
if ($_sign != $sign) { //不合法的数据
return 'fail'; //返回失败 继续补单
} else { //合法的数据
//业务处理
$this->orderProcessService->completedOrder($param, $price, $payId);
return 'success';
}
}
public function returnUrl(Request $request)
{
$oid = $request->get('order_id');
// 异步通知还没到就跳转了所以这里休眠2秒
sleep(2);
return redirect(url('detail-order-sn', ['orderSN' => $oid]));
}
}

View File

@ -0,0 +1,89 @@
<?php
namespace App\Http\Controllers\Pay;
use App\Exceptions\RuleValidationException;
use App\Http\Controllers\PayController;
use Yansongda\Pay\Pay;
class WepayController extends PayController
{
public function gateway(string $payway, string $orderSN)
{
try {
// 加载网关
$this->loadGateWay($orderSN, $payway);
$config = [
'app_id' => $this->payGateway->merchant_id,
'mch_id' => $this->payGateway->merchant_key,
'key' => $this->payGateway->merchant_pem,
'notify_url' => url($this->payGateway->pay_handleroute . '/notify_url'),
'return_url' => url('detail-order-sn', ['orderSN' => $this->order->order_sn]),
'http' => [ // optional
'timeout' => 10.0,
'connect_timeout' => 10.0,
],
];
$order = [
'out_trade_no' => $this->order->order_sn,
'total_fee' => bcmul($this->order->actual_price, 100, 0),
'body' => $this->order->order_sn
];
switch ($payway){
case 'wescan':
try{
$result = Pay::wechat($config)->scan($order)->toArray();
$result['qr_code'] = $result['code_url'];
$result['payname'] =$this->payGateway->pay_name;
$result['actual_price'] = (float)$this->order->actual_price;
$result['orderid'] = $this->order->order_sn;
return $this->render('static_pages/qrpay', $result, __('dujiaoka.scan_qrcode_to_pay'));
} catch (\Exception $e) {
throw new RuleValidationException(__('dujiaoka.prompt.abnormal_payment_channel') . $e->getMessage());
}
break;
}
} catch (RuleValidationException $exception) {
return $this->err($exception->getMessage());
}
}
/**
* 异步通知
*/
public function notifyUrl()
{
$xml = file_get_contents('php://input');
$arr = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
$oid = $arr['out_trade_no'];
$order = $this->orderService->detailOrderSN($oid);
if (!$order) {
return 'error';
}
$payGateway = $this->payService->detail($order->pay_id);
if (!$payGateway) {
return 'error';
}
if($payGateway->pay_handleroute != '/pay/wepay'){
return 'error';
}
$config = [
'app_id' => $payGateway->merchant_id,
'mch_id' => $payGateway->merchant_key,
'key' => $payGateway->merchant_pem,
];
$pay = Pay::wechat($config);
try{
// 验证签名
$result = $pay->verify();
$total_fee = bcdiv($result->total_fee, 100, 2);
$this->orderProcessService->completedOrder($result->out_trade_no, $total_fee, $result->transaction_id);
return 'success';
} catch (\Exception $exception) {
return 'fail';
}
}
}

View File

@ -0,0 +1,103 @@
<?php
namespace App\Http\Controllers\Pay;
use App\Exceptions\RuleValidationException;
use App\Http\Controllers\PayController;
use Illuminate\Http\Request;
class YipayController extends PayController
{
public function gateway(string $payway, string $orderSN)
{
try {
// 加载网关
$this->loadGateWay($orderSN, $payway);
//组装支付参数
$parameter = [
'pid' => $this->payGateway->merchant_id,
'type' => $payway,
'out_trade_no' => $this->order->order_sn,
'return_url' => route('yipay-return', ['order_id' => $this->order->order_sn]),
'notify_url' => url($this->payGateway->pay_handleroute . '/notify_url'),
'name' => $this->order->order_sn,
'money' => (float)$this->order->actual_price,
'sign' => $this->payGateway->merchant_pem,
'sign_type' =>'MD5'
];
ksort($parameter); //重新排序$data数组
reset($parameter); //内部指针指向数组中的第一个元素
$sign = '';
foreach ($parameter as $key => $val) {
if ($key == "sign" || $key == "sign_type" || $val == "") continue;
if ($key != 'sign') {
if ($sign != '') {
$sign .= "&";
}
$sign .= "$key=$val"; //拼接为url参数形式
}
}
$sign = md5($sign . $this->payGateway->merchant_pem);//密码追加进入开始MD5签名
$parameter['sign'] = $sign;
//待请求参数数组
$sHtml = "<form id='alipaysubmit' name='alipaysubmit' action='" . $this->payGateway->merchant_key . "' method='get'>";
foreach($parameter as $key => $val) {
$sHtml.= "<input type='hidden' name='".$key."' value='".$val."'/>";
}
//submit按钮控件请不要含有name属性
$sHtml = $sHtml."<input type='submit' value=''></form>";
$sHtml = $sHtml."<script>document.forms['alipaysubmit'].submit();</script>";
return $sHtml;
} catch (RuleValidationException $exception) {
return $this->err($exception->getMessage());
}
}
public function notifyUrl(Request $request)
{
$data = $request->all();
$order = $this->orderService->detailOrderSN($data['out_trade_no']);
if (!$order) {
return 'fail';
}
$payGateway = $this->payService->detail($order->pay_id);
if (!$payGateway) {
return 'fail';
}
if($payGateway->pay_handleroute != '/pay/yipay'){
return 'fail';
}
ksort($data); //重新排序$data数组
reset($data); //内部指针指向数组中的第一个元素
$sign = '';
foreach ($data as $key => $val) {
if ($key == "sign" || $key == "sign_type" || $val == "") continue;
if ($key != 'sign') {
if ($sign != '') {
$sign .= "&";
}
$sign .= "$key=$val"; //拼接为url参数形式
}
}
if (!$data['trade_no'] || md5($sign . $payGateway->merchant_pem) != $data['sign']) { //不合法的数据
return 'fail'; //返回失败 继续补单
} else {
//合法的数据
//业务处理
$this->orderProcessService->completedOrder($data['out_trade_no'], $data['money'], $data['trade_no']);
return 'success';
}
}
public function returnUrl(Request $request)
{
$oid = $request->get('order_id');
// 有些易支付太垃了异步通知还没到就跳转了导致订单显示待支付其实已经支付了所以这里休眠2秒
sleep(2);
return redirect(url('detail-order-sn', ['orderSN' => $oid]));
}
}

View File

@ -0,0 +1,130 @@
<?php
namespace App\Http\Controllers;
use App\Exceptions\RuleValidationException;
use App\Models\Order;
use App\Service\OrderProcessService;
class PayController extends BaseController
{
/**
* 支付网关
* @var \App\Models\Pay
*/
protected $payGateway;
/**
* 订单
* @var \App\Models\Order
*/
protected $order;
/**
* 订单服务层
* @var \App\Service\OrderService
*/
protected $orderService;
/**
* 支付服务层
* @var \App\Service\PayService
*/
protected $payService;
/**
* 订单处理层.
* @var OrderProcessService
*/
protected $orderProcessService;
public function __construct()
{
$this->orderService = app('Service\OrderService');
$this->payService = app('Service\PayService');
$this->orderProcessService = app('Service\OrderProcessService');
}
/**
* 订单检测
*
* @param string $orderSN
* @throws RuleValidationException
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function checkOrder(string $orderSN)
{
// 订单
$this->order = $this->orderService->detailOrderSN($orderSN);
if (!$this->order) {
throw new RuleValidationException(__('dujiaoka.prompt.order_does_not_exist'));
}
// 订单过期
if ($this->order->status == Order::STATUS_EXPIRED) {
throw new RuleValidationException(__('dujiaoka.prompt.order_is_expired'));
}
// 已经支付了
if ($this->order->status > Order::STATUS_WAIT_PAY) {
throw new RuleValidationException(__('dujiaoka.prompt.order_already_paid'));
}
}
/**
* 加载支付网关
*
* @param string $orderSN 订单号
* @param string $payCheck 支付标识
* @throws RuleValidationException
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function loadGateWay(string $orderSN, string $payCheck)
{
$this->checkOrder($orderSN);
// 支付配置
$this->payGateway = $this->payService->detailByCheck($payCheck);
if (!$this->payGateway) {
throw new RuleValidationException(__('dujiaoka.prompt.pay_gateway_does_not_exist'));
}
// 临时保存支付方式
$this->order->pay_id = $this->payGateway->id;
$this->order->save();
}
/**
* 网关处理.
*
* @param string $handle 跳转方法
* @param string $payway 支付标识
* @param string $orderSN 订单.
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function redirectGateway(string $handle,string $payway, string $orderSN)
{
try {
$this->checkOrder($orderSN);
$bccomp = bccomp($this->order->actual_price, 0.00, 2);
// 如果订单金额为0 代表无需支付,直接成功
if ($bccomp == 0) {
$this->orderProcessService->completedOrder($this->order->order_sn, 0.00);
return redirect(url('detail-order-sn', ['orderSN' => $this->order->order_sn]));
}
return redirect(url(urldecode($handle), ['payway' => $payway, 'orderSN' => $orderSN]));
} catch (RuleValidationException $exception) {
return $this->err($exception->getMessage());
}
}
}

89
app/Http/Kernel.php Normal file
View File

@ -0,0 +1,89 @@
<?php
namespace App\Http;
use App\Http\Middleware\DujiaoBoot;
use App\Http\Middleware\InstallCheck;
use App\Http\Middleware\PayGateWay;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array
*/
protected $middleware = [
\App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
\App\Http\Middleware\DujiaoSystem::class,
];
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'dujiaoka.boot' => DujiaoBoot::class,
'dujiaoka.pay_gate_way' => PayGateWay::class,
'install.check' => InstallCheck::class,
];
/**
* The priority-sorted list of middleware.
*
* This forces non-global middleware to always be in the given order.
*
* @var array
*/
protected $middlewarePriority = [
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\Authenticate::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param \Illuminate\Http\Request $request
* @return string|null
*/
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode as Middleware;
class CheckForMaintenanceMode extends Middleware
{
/**
* The URIs that should be reachable while maintenance mode is enabled.
*
* @var array
*/
protected $except = [
//
];
}

View File

@ -0,0 +1,58 @@
<?php
namespace App\Http\Middleware;
use App\Models\BaseModel;
use Closure;
use Germey\Geetest\GeetestServiceProvider;
class DujiaoBoot
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
// 安装检查
$installLock = base_path() . DIRECTORY_SEPARATOR . 'install.lock';
if (!file_exists($installLock)) {
return redirect(url('install'));
}
// 浏览器检测
$userAgent = $request->header('user-agent');
$nowUri = site_url() . $request->path();
$tplPath = 'common/notencent';
if (
(strpos($userAgent, 'QQ/')
||
strpos($userAgent, 'MicroMessenger') !== false)
&&
dujiaoka_config_get('is_open_anti_red', BaseModel::STATUS_OPEN) == BaseModel::STATUS_OPEN
) {
return response()->view($tplPath, ['nowUri' => $nowUri]);
}
// 语言检测
$lang = dujiaoka_config_get('language', 'zh_CN');
app()->setLocale($lang);
// 极验
$geetest = dujiaoka_config_get('is_open_geetest', BaseModel::STATUS_CLOSE);
if ($geetest == BaseModel::STATUS_OPEN) {
$geetestConfig = [
'key' => dujiaoka_config_get('geetest_key'),
'id' => dujiaoka_config_get('geetest_id'),
'lang' => $lang
];
// 覆盖 配置
config([
'geetest' => array_merge(config('mail'), $geetestConfig)
]);
// 重新注册服务
(new GeetestServiceProvider(app()))->register();
}
return $next($request);
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Middleware;
use App\Providers\AppServiceProvider;
use Closure;
class DujiaoSystem
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
// 检测https
if ($request->getScheme() == 'https') {
$httpsConfig = [
'https' => true
];
config([
'admin' => array_merge(config('admin'), $httpsConfig)
]);
(new AppServiceProvider(app()))->register();
}
return $next($request);
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
class EncryptCookies extends Middleware
{
/**
* The names of the cookies that should not be encrypted.
*
* @var array
*/
protected $except = [
//
];
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Http\Middleware;
use Closure;
class InstallCheck
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
// 安装检查
$installLock = base_path() . DIRECTORY_SEPARATOR . 'install.lock';
if (file_exists($installLock)) {
return redirect(url('/'));
}
return $next($request);
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Http\Middleware;
use Closure;
class PayGateWay
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
return $next($request);
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Http\Middleware;
use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
* @return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->check()) {
return redirect(RouteServiceProvider::HOME);
}
return $next($request);
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
class TrimStrings extends Middleware
{
/**
* The names of the attributes that should not be trimmed.
*
* @var array
*/
protected $except = [
'password',
'password_confirmation',
];
}

View File

@ -0,0 +1,23 @@
<?php
namespace App\Http\Middleware;
use Fideloper\Proxy\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* @var array|string
*/
protected $proxies;
/**
* The headers that should be used to detect proxies.
*
* @var int
*/
protected $headers = Request::HEADER_X_FORWARDED_ALL;
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* Indicates whether the XSRF-TOKEN cookie should be set on the response.
*
* @var bool
*/
protected $addHttpCookie = true;
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array
*/
protected $except = [
'pay/*',
];
}

87
app/Jobs/ApiHook.php Normal file
View File

@ -0,0 +1,87 @@
<?php
namespace App\Jobs;
use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ApiHook implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* 任务最大尝试次数。
*
* @var int
*/
public $tries = 2;
/**
* 任务运行的超时时间。
*
* @var int
*/
public $timeout = 30;
/**
* @var Order
*/
private $order;
/**
* 商品服务层.
* @var \App\Service\PayService
*/
private $goodsService;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Order $order)
{
$this->order = $order;
$this->goodsService = app('Service\GoodsService');
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$goodInfo = $this->goodsService->detail($this->order->goods_id);
// 判断是否有配置支付回调
if(empty($goodInfo->api_hook)){
return;
}
$postdata = [
'title' => $this->order->title,
'order_sn' => $this->order->order_sn,
'email' => $this->order->email,
'actual_price' => $this->order->actual_price,
'order_info' => $this->order->info,
'good_id' => $goodInfo->id,
'gd_name' => $goodInfo->gd_name
];
$opts = [
'http' => [
'method' => 'POST',
'header' => 'Content-type: application/json',
'content' => json_encode($postdata,JSON_UNESCAPED_UNICODE)
]
];
$context = stream_context_create($opts);
file_get_contents($goodInfo->api_hook, false, $context);
}
}

86
app/Jobs/BarkPush.php Normal file
View File

@ -0,0 +1,86 @@
<?php
namespace App\Jobs;
use App\Models\Order;
use GuzzleHttp\Client;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\BaseModel;
class BarkPush implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* 任务最大尝试次数。
*
* @var int
*/
public $tries = 2;
/**
* 任务运行的超时时间。
*
* @var int
*/
public $timeout = 30;
/**
* @var Order
*/
private $order;
/**
* 商品服务层.
* @var \App\Service\PayService
*/
private $goodsService;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Order $order)
{
$this->order = $order;
$this->goodsService = app('Service\GoodsService');
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$goodInfo = $this->goodsService->detail($this->order->goods_id);
$client = new Client();
$apiUrl = dujiaoka_config_get('bark_server') .'/'. dujiaoka_config_get('bark_token');
$params = [
"title" => __('dujiaoka.prompt.new_order_push').'('.$this->order->actual_price.'元)',
"body" => __('order.fields.order_id') .': '.$this->order->id."\n"
. __('order.fields.order_sn') .': '.$this->order->order_sn."\n"
. __('order.fields.pay_id') .': '.$this->order->pay->pay_name."\n"
. __('order.fields.title') .': '.$this->order->title."\n"
. __('order.fields.actual_price') .': '.$this->order->actual_price."\n"
. __('order.fields.email') .': '.$this->order->email."\n"
. __('goods.fields.gd_name') .': '.$goodInfo->gd_name."\n"
. __('goods.fields.in_stock') .': '.$goodInfo->in_stock."\n"
. __('order.fields.order_created') .': '.$this->order->created_at,
"icon"=>url('assets/common/images/default.jpg'),
"level"=>"timeSensitive",
"group"=>dujiaoka_config_get('text_logo', '独角数卡')
];
if (dujiaoka_config_get('is_open_bark_push_url', 0) == BaseModel::STATUS_OPEN) {
$params["url"] = url('detail-order-sn/'.$this->order->order_sn);
}
$client->post($apiUrl,['form_params' => $params, 'verify' => false]);
}
}

57
app/Jobs/CouponBack.php Normal file
View File

@ -0,0 +1,57 @@
<?php
namespace App\Jobs;
use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class CouponBack implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* 任务最大尝试次数。
*
* @var int
*/
public $tries = 3;
/**
* 任务可以执行的最大秒数 (超时时间)
*
* @var int
*/
public $timeout = 20;
private $order;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Order $order)
{
$this->order = $order;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
// 如果订单有使用优惠码
if ($this->order->coupon_id) {
// 优惠码次数+1
app('Service\CouponService')->retIncrByID($this->order->coupon_id);
// 设置订单优惠码已回退
app('Service\OrderService')->couponIsBack($this->order->order_sn);
}
}
}

84
app/Jobs/MailSend.php Normal file
View File

@ -0,0 +1,84 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Mail\MailServiceProvider;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail;
class MailSend implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* 任务最大尝试次数。
*
* @var int
*/
public $tries = 2;
/**
* 任务运行的超时时间。
*
* @var int
*/
public $timeout = 30;
private $to;
private $content;
private $title;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(string $to, string $title, string $content)
{
$this->to = $to;
$this->title = $title;
$this->content = $content;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$body = $this->content;
$title = $this->title;
$sysConfig = cache('system-setting');
$mailConfig = [
'driver' => $sysConfig['driver'] ?? 'smtp',
'host' => $sysConfig['host'] ?? '',
'port' => $sysConfig['port'] ?? '465',
'username' => $sysConfig['username'] ?? '',
'from' => [
'address' => $sysConfig['from_address'] ?? '',
'name' => $sysConfig['from_name'] ?? '独角发卡'
],
'password' => $sysConfig['password'] ?? '',
'encryption' => $sysConfig['encryption'] ?? ''
];
$to = $this->to;
// 覆盖 mail 配置
config([
'mail' => array_merge(config('mail'), $mailConfig)
]);
// 重新注册驱动
(new MailServiceProvider(app()))->register();
Mail::send(['html' => 'email.mail'], ['body' => $body], function ($message) use ($to, $title){
$message->to($to)->subject($title);
});
}
}

63
app/Jobs/OrderExpired.php Normal file
View File

@ -0,0 +1,63 @@
<?php
namespace App\Jobs;
use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class OrderExpired implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* 任务最大尝试次数。
*
* @var int
*/
public $tries = 3;
/**
* 任务可以执行的最大秒数 (超时时间)
*
* @var int
*/
public $timeout = 20;
/**
* 订单号
* @var string
*/
private $orderSN;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(string $orderSN)
{
$this->orderSN = $orderSN;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
// 如果x分钟后还没支付就算过期
$order = app('Service\OrderService')->detailOrderSN($this->orderSN);
if ($order && $order->status == Order::STATUS_WAIT_PAY) {
app('Service\OrderService')->expiredOrderSN($this->orderSN);
// 回退优惠券
CouponBack::dispatch($order);
}
}
}

72
app/Jobs/ServerJiang.php Normal file
View File

@ -0,0 +1,72 @@
<?php
namespace App\Jobs;
use App\Models\Order;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class ServerJiang implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* 任务最大尝试次数。
*
* @var int
*/
public $tries = 2;
/**
* 任务运行的超时时间。
*
* @var int
*/
public $timeout = 30;
/**
* @var Order
*/
private $order;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Order $order)
{
$this->order = $order;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$postdata = http_build_query([
'text' => __('dujiaoka.prompt.new_order_push') . ":{$this->order['ord_title']}",
'desp' => "
- ". __('order.fields.title') ."{$this->order->title}
- ". __('order.fields.order_sn') ."{$this->order->order_sn}
- ". __('order.fields.email') ."{$this->order->email}
- ". __('order.fields.actual_price') ."{$this->order->actual_price}
"
]);
$opts = [
'http' => [
'method' => 'POST',
'header' => 'Content-type: application/x-www-form-urlencoded',
'content' => $postdata
]
];
$context = stream_context_create($opts);
$apiToken = dujiaoka_config_get('server_jiang_token');
file_get_contents('https://sctapi.ftqq.com/' . $apiToken . '.send', false, $context);
}
}

81
app/Jobs/TelegramPush.php Normal file
View File

@ -0,0 +1,81 @@
<?php
namespace App\Jobs;
use App\Models\Order;
use GuzzleHttp\Client;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class TelegramPush implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* 任务最大尝试次数。
*
* @var int
*/
public $tries = 2;
/**
* 任务运行的超时时间。
*
* @var int
*/
public $timeout = 30;
/**
* @var Order
*/
private $order;
/**
* 商品服务层.
* @var \App\Service\PayService
*/
private $goodsService;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Order $order)
{
$this->order = $order;
$this->goodsService = app('Service\GoodsService');
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$goodInfo = $this->goodsService->detail($this->order->goods_id);
$formatText = '*'. __('dujiaoka.prompt.new_order_push').'('.$this->order->actual_price.'元)*%0A'
. __('order.fields.order_id') .': `'.$this->order->id.'`%0A'
. __('order.fields.order_sn') .': `'.$this->order->order_sn.'`%0A'
. __('order.fields.pay_id') .': `'.$this->order->pay->pay_name.'`%0A'
. __('order.fields.title') .': '.$this->order->title.'%0A'
. __('order.fields.actual_price') .': '.$this->order->actual_price.'%0A'
. __('order.fields.email') .': `'.$this->order->email.'`%0A'
. __('goods.fields.gd_name') .': `'.$goodInfo->gd_name.'`%0A'
. __('goods.fields.in_stock') .': `'.$goodInfo->in_stock.'`%0A'
. __('order.fields.order_created') .': '.$this->order->created_at;
$client = new Client([
'timeout' => 30,
'proxy'=> ''
]);
$apiUrl = 'https://api.telegram.org/bot' . dujiaoka_config_get('telegram_bot_token') .
'/sendMessage?chat_id=' . dujiaoka_config_get('telegram_userid') . '&parse_mode=Markdown&text='.$formatText;
$client->post($apiUrl);
}
}

View File

@ -0,0 +1,82 @@
<?php
namespace App\Jobs;
use App\Models\Order;
use GuzzleHttp\Client;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class WorkWeiXinPush implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* 任务最大尝试次数。
*
* @var int
*/
public $tries = 1;
/**
* 任务运行的超时时间。
*
* @var int
*/
public $timeout = 30;
/**
* @var Order
*/
private $order;
/**
* 商品服务层.
* @var \App\Service\PayService
*/
private $goodsService;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Order $order)
{
$this->order = $order;
$this->goodsService = app('Service\GoodsService');
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$goodInfo = $this->goodsService->detail($this->order->goods_id);
$client = new Client();
$apiUrl = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key='. dujiaoka_config_get('qywxbot_key');
$params = [
"msgtype"=>"markdown",
"markdown"=>[
"content"=>__('dujiaoka.prompt.new_order_push').'(<font color="warning">'.$this->order->actual_price."</font>元)\n"
.'>'.__('order.fields.order_id') .': <font color="comment">'.$this->order->id."</font>\n"
.'>'.__('order.fields.order_sn') .': <font color="comment">'.$this->order->order_sn."</font>\n"
.'>'.__('order.fields.pay_id') .': <font color="comment">'.$this->order->pay->pay_name."</font>\n"
.'>'.__('order.fields.title') .': <font color="comment">'.$this->order->title."</font>\n"
.'>'.__('order.fields.actual_price') .': <font color="comment">'.$this->order->actual_price."</font>\n"
.'>'.__('order.fields.email') .': <font color="comment">'.$this->order->email."</font>\n"
.'>'.__('goods.fields.gd_name') .': <font color="comment">'.$goodInfo->gd_name."</font>\n"
.'>'.__('goods.fields.in_stock') .': <font color="comment">'.$goodInfo->in_stock."</font>\n"
.'>'.__('order.fields.order_created') .': <font color="comment">'.$this->order->created_at."</font>"
]
];
$client->post($apiUrl,['json' => $params, 'verify' => false]);
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Listeners;
use App\Models\Carmis;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use App\Events\GoodsDeleted as GoodsDeletedEvent;
class GoodsDeleted
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle(GoodsDeletedEvent $event)
{
Carmis::query()->where('goods_id', $event->goods->id)->delete();
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Listeners;
use App\Models\Goods;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use App\Events\GoodsGroupDeleted as GoodsGroupDeletedEvent;
class GoodsGroupDeleted
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param GoodsGroupDeletedEvent $event
* @return void
*/
public function handle(GoodsGroupDeletedEvent $event)
{
Goods::query()->where('group_id', $event->goodsGroup->id)->delete();
}
}

View File

@ -0,0 +1,80 @@
<?php
namespace App\Listeners;
use App\Jobs\MailSend;
use App\Models\Emailtpl;
use App\Models\Order;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use App\Events\OrderUpdated as OrderUpdatedEvent;
class OrderUpdated
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle(OrderUpdatedEvent $event)
{
$sysCache = cache('system-setting');
// 当代充商品状态,将会对顾客进行订单内容推送
$order = [
'created_at' => date('Y-m-d H:i'),
'ord_title' => $event->order->title,
'webname' => $sysCache['text_logo'] ?? '独角数卡',
'weburl' => config('app.url'),
'order_id' => $event->order->order_sn,
'ord_price' => $event->order->actual_price,
'ord_info' => str_replace(PHP_EOL, '<br/>', $event->order->info)
];
$to = $event->order->email;
// 邮件
if ($event->order->type == Order::MANUAL_PROCESSING) {
switch ($event->order->status) {
case Order::STATUS_PENDING:
$mailtpl = Emailtpl::query()->where('tpl_token', 'pending_order')->first()->toArray();
self::sendMailToOrderStatus($mailtpl, $order, $to);
break;
case Order::STATUS_COMPLETED:
$mailtpl = Emailtpl::query()->where('tpl_token', 'completed_order')->first()->toArray();
self::sendMailToOrderStatus($mailtpl, $order, $to);
break;
case Order::STATUS_FAILURE:
$mailtpl = Emailtpl::query()->where('tpl_token', 'failed_order')->first()->toArray();
self::sendMailToOrderStatus($mailtpl, $order, $to);
break;
}
}
}
/**
* 邮件发送
*
* @param array $mailtpl 模板
* @param array $order 内容
* @param string $to 接受者
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
private static function sendMailToOrderStatus(array $mailtpl, array $order, string $to) :void
{
$info = replace_mail_tpl($mailtpl, $order);
MailSend::dispatch($to, $info['tpl_name'], $info['tpl_content']);
}
}

40
app/Models/BaseModel.php Normal file
View File

@ -0,0 +1,40 @@
<?php
/**
* The file was created by Assimon.
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class BaseModel extends Model
{
const STATUS_OPEN = 1; // 状态开启
const STATUS_CLOSE = 0; // 状态关闭
const AUTOMATIC_DELIVERY = 1; // 自动发货
const MANUAL_PROCESSING = 2; // 人工处理
/**
* map
*
* @return array
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public static function getIsOpenMap()
{
return [
self::STATUS_OPEN => admin_trans('dujiaoka.status_open'),
self::STATUS_CLOSE => admin_trans('dujiaoka.status_close')
];
}
}

55
app/Models/Carmis.php Normal file
View File

@ -0,0 +1,55 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\SoftDeletes;
class Carmis extends BaseModel
{
use SoftDeletes;
protected $table = 'carmis';
/**
* 未售出
*/
const STATUS_UNSOLD = 1;
/**
* 已售出
*/
const STATUS_SOLD = 2;
/**
* 获取组建映射
*
* @return array
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public static function getStatusMap()
{
return [
self::STATUS_UNSOLD => admin_trans('carmis.fields.status_unsold'),
self::STATUS_SOLD => admin_trans('carmis.fields.status_sold')
];
}
/**
* 关联商品
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function goods()
{
return $this->belongsTo(Goods::class, 'goods_id');
}
}

58
app/Models/Coupon.php Normal file
View File

@ -0,0 +1,58 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\SoftDeletes;
class Coupon extends BaseModel
{
use SoftDeletes;
protected $table = 'coupons';
/**
* 一次性使用
*/
const TYPE_ONE_TIME = 1;
/**
* 重复使用
*/
const TYPE_REPEAT = 2;
/**
* 未使用
*/
const STATUS_UNUSED = 1;
/**
* 已使用
*/
const STATUS_USE = 2;
/**
* 关联商品
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function goods()
{
return $this->belongsToMany(Goods::class, 'coupons_goods', 'coupons_id', 'goods_id');
}
public static function getStatusUseMap()
{
return [
self::STATUS_USE => admin_trans('coupon.fields.status_use'),
self::STATUS_UNUSED => admin_trans('coupon.fields.status_unused'),
];
}
}

15
app/Models/Emailtpl.php Normal file
View File

@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Model;
class Emailtpl extends Model
{
use SoftDeletes;
protected $table = 'emailtpls';
}

97
app/Models/Goods.php Normal file
View File

@ -0,0 +1,97 @@
<?php
namespace App\Models;
use App\Events\GoodsDeleted;
use Illuminate\Database\Eloquent\SoftDeletes;
class Goods extends BaseModel
{
use SoftDeletes;
protected $table = 'goods';
protected $dispatchesEvents = [
'deleted' => GoodsDeleted::class
];
/**
* 关联分类
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function group()
{
return $this->belongsTo(GoodsGroup::class, 'group_id');
}
/**
* 关联优惠券
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function coupon()
{
return $this->belongsToMany(Coupon::class, 'coupons_goods', 'goods_id', 'coupons_id');
}
/**
* 关联卡密
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function carmis()
{
return $this->hasMany(Carmis::class, 'goods_id');
}
/**
* 库存读取器,将自动发货的库存更改为未出售卡密的数量
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function getInStockAttribute()
{
if (isset($this->attributes['carmis_count'])
&&
$this->attributes['type'] == self::AUTOMATIC_DELIVERY
) {
$this->attributes['in_stock'] = $this->attributes['carmis_count'];
}
return $this->attributes['in_stock'];
}
/**
* 获取组建映射
*
* @return array
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public static function getGoodsTypeMap()
{
return [
self::AUTOMATIC_DELIVERY => admin_trans('goods.fields.automatic_delivery'),
self::MANUAL_PROCESSING => admin_trans('goods.fields.manual_processing')
];
}
}

34
app/Models/GoodsGroup.php Normal file
View File

@ -0,0 +1,34 @@
<?php
namespace App\Models;
use App\Events\GoodsGroupDeleted;
use Illuminate\Database\Eloquent\SoftDeletes;
class GoodsGroup extends BaseModel
{
use SoftDeletes;
protected $table = 'goods_group';
protected $dispatchesEvents = [
'deleted' => GoodsGroupDeleted::class
];
/**
* 关联商品
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function goods()
{
return $this->hasMany(Goods::class, 'group_id');
}
}

146
app/Models/Order.php Normal file
View File

@ -0,0 +1,146 @@
<?php
namespace App\Models;
use App\Events\OrderUpdated;
use Illuminate\Database\Eloquent\SoftDeletes;
class Order extends BaseModel
{
use SoftDeletes;
protected $table = 'orders';
/**
* 待支付
*/
const STATUS_WAIT_PAY = 1;
/**
* 待处理
*/
const STATUS_PENDING = 2;
/**
* 处理中
*/
const STATUS_PROCESSING = 3;
/**
* 已完成
*/
const STATUS_COMPLETED = 4;
/**
* 失败
*/
const STATUS_FAILURE = 5;
/**
* 过期
*/
const STATUS_EXPIRED = -1;
/**
* 异常
*/
const STATUS_ABNORMAL = 6;
/**
* 优惠券未回退
*/
const COUPON_BACK_WAIT = 0;
/**
* 优惠券已回退
*/
const COUPON_BACK_OK = 1;
protected $dispatchesEvents = [
'updated' => OrderUpdated::class
];
/**
* 状态映射
*
* @return array
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public static function getStatusMap()
{
return [
self::STATUS_WAIT_PAY => admin_trans('order.fields.status_wait_pay'),
self::STATUS_PENDING => admin_trans('order.fields.status_pending'),
self::STATUS_PROCESSING => admin_trans('order.fields.status_processing'),
self::STATUS_COMPLETED => admin_trans('order.fields.status_completed'),
self::STATUS_FAILURE => admin_trans('order.fields.status_failure'),
self::STATUS_ABNORMAL => admin_trans('order.fields.status_abnormal'),
self::STATUS_EXPIRED => admin_trans('order.fields.status_expired')
];
}
/**
* 类型映射
*
* @return array
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public static function getTypeMap()
{
return [
self::AUTOMATIC_DELIVERY => admin_trans('goods.fields.automatic_delivery'),
self::MANUAL_PROCESSING => admin_trans('goods.fields.manual_processing')
];
}
/**
* 关联商品
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function goods()
{
return $this->belongsTo(Goods::class, 'goods_id');
}
/**
* 关联优惠券
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function coupon()
{
return $this->belongsTo(Coupon::class, 'coupon_id');
}
/**
* 关联支付
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function pay()
{
return $this->belongsTo(Pay::class, 'pay_id');
}
}

57
app/Models/Pay.php Normal file
View File

@ -0,0 +1,57 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Model;
class Pay extends BaseModel
{
use SoftDeletes;
protected $table = 'pays';
/**
* 跳转
*/
const METHOD_JUMP = 1;
/**
* 扫码
*/
const METHOD_SCAN = 2;
/**
* 电脑
*/
const PAY_CLIENT_PC = 1;
/**
* 手机
*/
const PAY_CLIENT_MOBILE = 2;
/**
* 通用
*/
const PAY_CLIENT_ALL = 3;
public static function getMethodMap()
{
return [
self::METHOD_JUMP => admin_trans('pay.fields.method_jump'),
self::METHOD_SCAN => admin_trans('pay.fields.method_scan'),
];
}
public static function getClientMap()
{
return [
self::PAY_CLIENT_PC => admin_trans('pay.fields.pay_client_pc'),
self::PAY_CLIENT_MOBILE => admin_trans('pay.fields.pay_client_mobile'),
self::PAY_CLIENT_ALL => admin_trans('pay.fields.pay_client_all'),
];
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace App\Providers;
use App\Service\CarmisService;
use App\Service\CouponService;
use App\Service\EmailtplService;
use App\Service\GoodsService;
use App\Service\OrderProcessService;
use App\Service\OrderService;
use App\Service\PayService;
use Illuminate\Support\ServiceProvider;
use Jenssegers\Agent\Agent;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->singleton('Service\GoodsService', function () {
return $this->app->make(GoodsService::class);
});
$this->app->singleton('Service\PayService', function () {
return $this->app->make(PayService::class);
});
$this->app->singleton('Service\CarmisService', function () {
return $this->app->make(CarmisService::class);
});
$this->app->singleton('Service\OrderService', function () {
return $this->app->make(OrderService::class);
});
$this->app->singleton('Service\CouponService', function () {
return $this->app->make(CouponService::class);
});
$this->app->singleton('Service\OrderProcessService', function () {
return $this->app->make(OrderProcessService::class);
});
$this->app->singleton('Service\EmailtplService', function () {
return $this->app->make(EmailtplService::class);
});
$this->app->singleton('Jenssegers\Agent', function () {
return $this->app->make(Agent::class);
});
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
// 'App\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
//
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\ServiceProvider;
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Broadcast::routes();
require base_path('routes/channels.php');
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace App\Providers;
use App\Events\GoodsDeleted;
use App\Events\GoodsGroupDeleted;
use App\Events\OrderUpdated;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
Registered::class => [
SendEmailVerificationNotification::class,
],
GoodsGroupDeleted::class => [
\App\Listeners\GoodsGroupDeleted::class,
],
GoodsDeleted::class => [
\App\Listeners\GoodsDeleted::class,
],
OrderUpdated::class => [
\App\Listeners\OrderUpdated::class,
],
];
/**
* Register any events for your application.
*
* @return void
*/
public function boot()
{
parent::boot();
//
}
}

View File

@ -0,0 +1,80 @@
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Route;
class RouteServiceProvider extends ServiceProvider
{
/**
* This namespace is applied to your controller routes.
*
* In addition, it is set as the URL generator's root namespace.
*
* @var string
*/
protected $namespace = 'App\Http\Controllers';
/**
* The path to the "home" route for your application.
*
* @var string
*/
public const HOME = '/home';
/**
* Define your route model bindings, pattern filters, etc.
*
* @return void
*/
public function boot()
{
//
parent::boot();
}
/**
* Define the routes for the application.
*
* @return void
*/
public function map()
{
$this->mapApiRoutes();
$this->mapWebRoutes();
//
}
/**
* Define the "web" routes for the application.
*
* These routes all receive session state, CSRF protection, etc.
*
* @return void
*/
protected function mapWebRoutes()
{
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
}
/**
* Define the "api" routes for the application.
*
* These routes are typically stateless.
*
* @return void
*/
protected function mapApiRoutes()
{
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
}

44
app/Rules/SearchPwd.php Normal file
View File

@ -0,0 +1,44 @@
<?php
namespace App\Rules;
use App\Models\BaseModel;
use Illuminate\Contracts\Validation\Rule;
class SearchPwd implements Rule
{
/**
* Create a new rule instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
if (dujiaoka_config_get('is_open_search_pwd') == BaseModel::STATUS_OPEN && empty($value)) {
return false;
}
return true;
}
/**
* Get the validation error message.
*
* @return string
*/
public function message()
{
return __('dujiaoka.prompt.search_password_can_not_be_empty');
}
}

44
app/Rules/VerifyImg.php Normal file
View File

@ -0,0 +1,44 @@
<?php
namespace App\Rules;
use App\Models\BaseModel;
use Illuminate\Contracts\Validation\Rule;
class VerifyImg implements Rule
{
/**
* Create a new rule instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
* @return bool
*/
public function passes($attribute, $value)
{
if (dujiaoka_config_get('is_open_img_code') == BaseModel::STATUS_OPEN && !captcha_check($value)) {
return false;
}
return true;
}
/**
* Get the validation error message.
*
* @return string
*/
public function message()
{
return __('dujiaoka.prompt.image_verify_code_error');
}
}

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