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
+53
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,
];
}
}
+52
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,
];
}
}
+176
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
);
}
}
+137
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
);
}
}
+139
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
);
}
}
+116
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
);
}
}
+9
View File
@@ -0,0 +1,9 @@
<?php
namespace App\Admin\Controllers;
use Dcat\Admin\Http\Controllers\AuthController as BaseAuthController;
class AuthController extends BaseAuthController
{
}
+124
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()));
}
}
+116
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');
});
}
}
@@ -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()));
}
}
@@ -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();
});
});
}
}
+160
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);
});
}
}
@@ -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();
});
});
}
}
+44
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');
}
}
+156
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');
});
}
}
+130
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();
});
}
}
@@ -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()));
}
}
+85
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()
{
}
}
+78
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');
}
}
+102
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');
}
}
+18
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;
}
+16
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;
}
+16
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;
}
+16
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;
}
+18
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;
}
+16
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;
}
+16
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
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
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
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');
}
}
+39
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');
}
}
+39
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');
}
}
+39
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');
}
}
+15
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);
}
}
+60
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);
}
}
@@ -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
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;
}
}
+54
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'));
}
}
+13
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;
}
@@ -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();
}
}
}
@@ -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'));
}
}
@@ -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';
}
}
}
@@ -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';
//返回失败 继续补单
}
}
}
@@ -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]));
}
}
@@ -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';
}
}
}
@@ -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';
}
}
@@ -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;
}
}
@@ -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或者2,orderuid长度不要超过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]));
}
}
@@ -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);
}
}
@@ -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]));
}
}
@@ -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]));
}
}
@@ -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';
}
}
}
@@ -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]));
}
}
+130
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
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,
];
}
+21
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');
}
}
}
@@ -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 = [
//
];
}
+58
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);
}
}
+31
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);
}
}
+17
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 = [
//
];
}
+25
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);
}
}
+21
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);
}
}
@@ -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);
}
}
+18
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',
];
}
+23
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;
}
+24
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
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
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
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
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
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
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
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);
}
}
+82
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]);
}
}
+32
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();
}
}
+32
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();
}
}
+80
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
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
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
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
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
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
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
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
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'),
];
}
}
+60
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()
{
}
}
+30
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();
//
}
}
@@ -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');
}
}
+45
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();
//
}
}
+80
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
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
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');
}
}
+54
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\Service;
use App\Models\Carmis;
class CarmisService
{
/**
* 通过商品查询一些数量未使用的卡密
*
* @param int $goodsID 商品id
* @param int $byAmount 数量
* @return array|null
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function withGoodsByAmountAndStatusUnsold(int $goodsID, int $byAmount)
{
$carmis = Carmis::query()
->where('goods_id', $goodsID)
->where('status', Carmis::STATUS_UNSOLD)
->take($byAmount)
->get();
return $carmis ? $carmis->toArray() : null;
}
/**
* 通过id集合设置卡密已售出
*
* @param array $ids 卡密id集合
* @return bool
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function soldByIDS(array $ids): bool
{
return Carmis::query()->whereIn('id', $ids)->where('is_loop', 0)->update(['status' => Carmis::STATUS_SOLD]);
}
}
+80
View File
@@ -0,0 +1,80 @@
<?php
/**
* The file was created by Assimon.
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
namespace App\Service;
use App\Models\Coupon;
class CouponService
{
/**
* 获得优惠码,通过商品关联
*
* @param string $coupon 优惠码
* @param int $goodsID 商品id
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object|null
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function withHasGoods(string $coupon, int $goodsID)
{
$coupon = Coupon::query()->whereHas('goods', function ($query) use ($goodsID) {
$query->where('goods_id', $goodsID);
})->where('is_open', Coupon::STATUS_OPEN)->where('coupon', $coupon)->first();
return $coupon;
}
/**
* 设置优惠券已使用
* @param string $coupon
* @return bool
*/
public function used(string $coupon): bool
{
return Coupon::query()
->where('coupon', $coupon)
->update(['is_use' => Coupon::STATUS_USE]);
}
/**
* 设置优惠券使用次数 -1
* @param string $coupon
* @return int
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function retDecr(string $coupon)
{
return Coupon::query()
->where('coupon', $coupon)
->decrement('ret', 1);
}
/**
* 设置优惠券次数+1
*
* @param int $id
* @return int
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function retIncrByID(int $id)
{
return Coupon::query()->where('id', $id)->increment('ret', 1);
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
/**
* The file was created by Assimon.
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
namespace App\Service;
use App\Models\Emailtpl;
class EmailtplService
{
/**
* 通过邮件标识获得邮件模板
*
* @param string $token 邮件标识
* @return Emailtpl
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function detailByToken(string $token): Emailtpl
{
$tpl = Emailtpl::query()->where('tpl_token', $token)->first();
return $tpl;
}
}
+152
View File
@@ -0,0 +1,152 @@
<?php
/**
* The file was created by Assimon.
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
namespace App\Service;
use App\Exceptions\RuleValidationException;
use App\Models\Carmis;
use App\Models\Goods;
use App\Models\GoodsGroup;
/**
* 商品服务层
*
* Class GoodsService
* @package App\Service
* @author: Assimon
* @email: Ashang@utf8.hk
* @blog: https://utf8.hk
* Date: 2021/5/30
*/
class GoodsService
{
/**
* 获取所有分类并加载该分类下的商品
*
* @return array|null
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function withGroup(): ?array
{
$goods = GoodsGroup::query()
->with(['goods' => function($query) {
$query->withCount(['carmis' => function($query) {
$query->where('status', Carmis::STATUS_UNSOLD);
}])->where('is_open', Goods::STATUS_OPEN)->orderBy('ord', 'DESC');
}])
->where('is_open', GoodsGroup::STATUS_OPEN)
->orderBy('ord', 'DESC')
->get();
// 将自动
return $goods ? $goods->toArray() : null;
}
/**
* 商品详情
*
* @param int $id 商品id
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object|null
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function detail(int $id)
{
$goods = Goods::query()
->with(['coupon'])
->withCount(['carmis' => function($query) {
$query->where('status', Carmis::STATUS_UNSOLD);
}])->where('id', $id)->first();
return $goods;
}
/**
* 格式化商品信息
*
* @param Goods $goods 商品模型
* @return Goods
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function format(Goods $goods)
{
// 格式化批发配置以及输入框配置
$goods->wholesale_price_cnf = $goods->wholesale_price_cnf ?
format_wholesale_price($goods->wholesale_price_cnf) :
null;
// 如果存在其他配置输入框且为代充
$goods->other_ipu = $goods->other_ipu_cnf ?
format_charge_input($goods->other_ipu_cnf) :
null;
return $goods;
}
/**
* 验证商品状态
*
* @param Goods $goods
* @return Goods
* @throws RuleValidationException
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function validatorGoodsStatus(Goods $goods): Goods
{
if (empty($goods)) {
throw new RuleValidationException(__('dujiaoka.prompt.goods_does_not_exist'));
}
// 上架判断.
if ($goods->is_open != Goods::STATUS_OPEN) {
throw new RuleValidationException(__('dujiaoka.prompt.the_goods_is_not_on_the_shelves'));
}
return $goods;
}
/**
* 库存减去
*
* @param int $id 商品id
* @param int $number 出库数量
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function inStockDecr(int $id, int $number = 1): bool
{
return Goods::query()->where('id', $id)->decrement('in_stock', $number);
}
/**
* 商品销量加
*
* @param int $id 商品id
* @param int $number 数量
* @return bool
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function salesVolumeIncr(int $id, int $number = 1): bool
{
return Goods::query()->where('id', $id)->increment('sales_volume', $number);
}
}
+526
View File
@@ -0,0 +1,526 @@
<?php
/**
* The file was created by Assimon.
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
namespace App\Service;
use App\Exceptions\RuleValidationException;
use App\Jobs\ApiHook;
use App\Jobs\MailSend;
use App\Jobs\OrderExpired;
use App\Jobs\ServerJiang;
use App\Jobs\TelegramPush;
use App\Jobs\BarkPush;
use App\Jobs\WorkWeiXinPush;
use App\Models\BaseModel;
use App\Models\Coupon;
use App\Models\Goods;
use App\Models\Order;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
/**
* 订单处理层
*
* Class OrderProcessService
* @package App\Service
* @author: Assimon
* @email: Ashang@utf8.hk
* @blog: https://utf8.hk
* Date: 2021/5/30
*/
class OrderProcessService
{
const PENDING_CACHE_KEY = 'PENDING_ORDERS_LIST';
/**
* 优惠码服务层
* @var \App\Service\CouponService
*/
private $couponService;
/**
* 订单服务层
* @var \App\Service\OrderService
*/
private $orderService;
/**
* 卡密服务层
* @var \App\Service\CarmisService
*/
private $carmisService;
/**
* 邮件服务层
* @var \App\Service\EmailtplService
*/
private $emailtplService;
/**
* 商品服务层.
* @var \App\Service\GoodsService
*/
private $goodsService;
/**
* 商品
* @var Goods
*/
private $goods;
/**
* 优惠码
* @var Coupon;
*/
private $coupon;
/**
* 其他输入框
* @var string
*/
private $otherIpt;
/**
* 购买数量
* @var int
*/
private $buyAmount;
/**
* 购买邮箱
* @var string
*/
private $email;
/**
* 查询密码
* @var string
*/
private $searchPwd;
/**
* 下单id
* @var string
*/
private $buyIP;
/**
* 支付方式
* @var int
*/
private $payID;
public function __construct()
{
$this->couponService = app('Service\CouponService');
$this->orderService = app('Service\OrderService');
$this->carmisService = app('Service\CarmisService');
$this->emailtplService = app('Service\EmailtplService');
$this->goodsService = app('Service\GoodsService');
}
/**
* 设置支付方式
* @param int $payID
*/
public function setPayID(int $payID): void
{
$this->payID = $payID;
}
/**
* 下单ip
* @param mixed $buyIP
*/
public function setBuyIP($buyIP): void
{
$this->buyIP = $buyIP;
}
/**
* 设置查询密码
* @param mixed $searchPwd
*/
public function setSearchPwd($searchPwd): void
{
$this->searchPwd = $searchPwd;
}
/**
* 设置购买数量
* @param mixed $buyAmount
*/
public function setBuyAmount($buyAmount): void
{
$this->buyAmount = $buyAmount;
}
/**
* 设置下单邮箱
* @param mixed $email
*/
public function setEmail($email): void
{
$this->email = $email;
}
/**
* 设置商品
*
* @param Goods $goods
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function setGoods(Goods $goods)
{
$this->goods = $goods;
}
/**
* 设置优惠码.
*
* @param ?Coupon $coupon
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function setCoupon(?Coupon $coupon)
{
$this->coupon = $coupon;
}
/**
* 其他输入框设置.
*
* @param ?string $otherIpt
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function setOtherIpt(?string $otherIpt)
{
$this->otherIpt = $otherIpt;
}
/**
* 计算优惠码价格
*
* @return float
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
private function calculateTheCouponPrice(): float
{
$couponPrice = 0;
// 优惠码优惠价格
if ($this->coupon) {
$couponPrice = $this->coupon->discount;
}
return $couponPrice;
}
/**
* 计算批发优惠
* @return float
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
private function calculateTheWholesalePrice(): float
{
$wholesalePrice = 0; // 优惠单价
$wholesaleTotalPrice = 0; // 优惠总价
if ($this->goods->wholesale_price_cnf) {
$formatWholesalePrice = format_wholesale_price($this->goods->wholesale_price_cnf);
foreach ($formatWholesalePrice as $item) {
if ($this->buyAmount >= $item['number']) {
$wholesalePrice = $item['price'];
}
}
}
if ($wholesalePrice > 0 ) {
$totalPrice = $this->calculateTheTotalPrice(); // 实际原总价
$newTotalPrice = bcmul($wholesalePrice, $this->buyAmount, 2); // 批发价优惠后的总价
$wholesaleTotalPrice = bcsub($totalPrice, $newTotalPrice, 2); // 批发总优惠
}
return $wholesaleTotalPrice;
}
/**
* 订单总价
* @return float
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
private function calculateTheTotalPrice(): float
{
$price = $this->goods->actual_price;
return bcmul($price, $this->buyAmount, 2);
}
/**
* 计算实际需要支付的价格
*
* @param float $totalPrice 总价
* @param float $couponPrice 优惠码优惠价
* @param float $wholesalePrice 批发优惠
* @return float
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
private function calculateTheActualPrice(float $totalPrice, float $couponPrice, float $wholesalePrice): float
{
$actualPrice = bcsub($totalPrice, $couponPrice, 2);
$actualPrice = bcsub($actualPrice, $wholesalePrice, 2);
if ($actualPrice <= 0) {
$actualPrice = 0;
}
return $actualPrice;
}
/**
* 创建订单.
* @return Order
* @throws RuleValidationException
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function createOrder(): Order
{
try {
$order = new Order();
// 生成订单号
$order->order_sn = strtoupper(Str::random(16));
// 设置商品
$order->goods_id = $this->goods->id;
// 标题
$order->title = $this->goods->gd_name . ' x ' . $this->buyAmount;
// 订单类型
$order->type = $this->goods->type;
// 查询密码
$order->search_pwd = $this->searchPwd;
// 邮箱
$order->email = $this->email;
// 支付方式.
$order->pay_id = $this->payID;
// 商品单价
$order->goods_price = $this->goods->actual_price;
// 购买数量
$order->buy_amount = $this->buyAmount;
// 订单详情
$order->info = $this->otherIpt;
// ip地址
$order->buy_ip = $this->buyIP;
// 优惠码优惠价格
$order->coupon_discount_price = $this->calculateTheCouponPrice();
if ($this->coupon) {
$order->coupon_id = $this->coupon->id;
}
// 批发价
$order->wholesale_discount_price = $this->calculateTheWholesalePrice();
// 订单总价
$order->total_price = $this->calculateTheTotalPrice();
// 订单实际需要支付价格
$order->actual_price = $this->calculateTheActualPrice(
$this->calculateTheTotalPrice(),
$this->calculateTheCouponPrice(),
$this->calculateTheWholesalePrice()
);
// 保存订单
$order->save();
// 如果有用到优惠券
if ($this->coupon) {
// 设置优惠码已经使用
$this->couponService->used($this->coupon->coupon);
// 使用次数-1
$this->couponService->retDecr($this->coupon->coupon);
}
// 将订单加入队列 x分钟后过期
$expiredOrderDate = dujiaoka_config_get('order_expire_time', 5);
OrderExpired::dispatch($order->order_sn)->delay(Carbon::now()->addMinutes($expiredOrderDate));
return $order;
} catch (\Exception $exception) {
throw new RuleValidationException($exception->getMessage());
}
}
/**
* 订单成功方法
*
* @param string $orderSN 订单号
* @param float $actualPrice 实际支付金额
* @param string $tradeNo 第三方订单号
* @return Order
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function completedOrder(string $orderSN, float $actualPrice, string $tradeNo = '')
{
DB::beginTransaction();
try {
// 得到订单详情
$order = $this->orderService->detailOrderSN($orderSN);
if (!$order) {
throw new \Exception(__('dujiaoka.prompt.order_does_not_exist'));
}
// 订单已经处理
if ($order->status == Order::STATUS_COMPLETED) {
throw new \Exception(__('dujiaoka.prompt.order_status_completed'));
}
$bccomp = bccomp($order->actual_price, $actualPrice, 2);
// 金额不一致
if ($bccomp != 0) {
throw new \Exception(__('dujiaoka.prompt.order_inconsistent_amounts'));
}
$order->actual_price = $actualPrice;
$order->trade_no = $tradeNo;
// 区分订单类型
// 自动发货
if ($order->type == Order::AUTOMATIC_DELIVERY) {
$completedOrder = $this->processAuto($order);
} else {
$completedOrder = $this->processManual($order);
}
// 销量加上
$this->goodsService->salesVolumeIncr($order->goods_id, $order->buy_amount);
DB::commit();
// 如果开启了server酱
if (dujiaoka_config_get('is_open_server_jiang', 0) == BaseModel::STATUS_OPEN) {
ServerJiang::dispatch($order);
}
// 如果开启了TG推送
if (dujiaoka_config_get('is_open_telegram_push', 0) == BaseModel::STATUS_OPEN) {
TelegramPush::dispatch($order);
}
// 如果开启了Bark推送
if (dujiaoka_config_get('is_open_bark_push', 0) == BaseModel::STATUS_OPEN) {
BarkPush::dispatch($order);
}
// 如果开启了企业微信Bot推送
if (dujiaoka_config_get('is_open_qywxbot_push', 0) == BaseModel::STATUS_OPEN) {
WorkWeiXinPush::dispatch($order);
}
// 回调事件
ApiHook::dispatch($order);
return $completedOrder;
} catch (\Exception $exception) {
DB::rollBack();
throw new RuleValidationException($exception->getMessage());
}
}
/**
* 手动处理的订单.
*
* @param Order $order 订单
* @return Order 订单
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function processManual(Order $order)
{
// 设置订单为待处理
$order->status = Order::STATUS_PENDING;
// 保存订单
$order->save();
// 商品库存减去
$this->goodsService->inStockDecr($order->goods_id, $order->buy_amount);
// 邮件数据
$mailData = [
'created_at' => $order->create_at,
'product_name' => $order->goods->gd_name,
'webname' => dujiaoka_config_get('text_logo', '独角数卡'),
'weburl' => config('app.url') ?? 'http://dujiaoka.com',
'ord_info' => str_replace(PHP_EOL, '<br/>', $order->info),
'ord_title' => $order->title,
'order_id' => $order->order_sn,
'buy_amount' => $order->buy_amount,
'ord_price' => $order->actual_price,
'created_at' => $order->created_at,
];
$tpl = $this->emailtplService->detailByToken('manual_send_manage_mail');
$mailBody = replace_mail_tpl($tpl, $mailData);
$manageMail = dujiaoka_config_get('manage_email', '');
// 邮件发送
MailSend::dispatch($manageMail, $mailBody['tpl_name'], $mailBody['tpl_content']);
return $order;
}
/**
* 处理自动发货.
*
* @param Order $order 订单
* @return Order 订单
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function processAuto(Order $order): Order
{
// 获得卡密
$carmis = $this->carmisService->withGoodsByAmountAndStatusUnsold($order->goods_id, $order->buy_amount);
// 实际可使用的库存已经少于购买数量了
if (count($carmis) != $order->buy_amount) {
$order->info = __('dujiaoka.prompt.order_carmis_insufficient_quantity_available');
$order->status = Order::STATUS_ABNORMAL;
$order->save();
return $order;
}
$carmisInfo = array_column($carmis, 'carmi');
$ids = array_column($carmis, 'id');
$order->info = implode(PHP_EOL, $carmisInfo);
$order->status = Order::STATUS_COMPLETED;
$order->save();
// 将卡密设置为已售出
$this->carmisService->soldByIDS($ids);
// 邮件数据
$mailData = [
'created_at' => $order->create_at,
'product_name' => $order->goods->gd_name,
'webname' => dujiaoka_config_get('text_logo', '独角数卡'),
'weburl' => config('app.url') ?? 'http://dujiaoka.com',
'ord_info' => implode('<br/>', $carmisInfo),
'ord_title' => $order->title,
'order_id' => $order->order_sn,
'buy_amount' => $order->buy_amount,
'ord_price' => $order->actual_price,
];
$tpl = $this->emailtplService->detailByToken('card_send_user_email');
$mailBody = replace_mail_tpl($tpl, $mailData);
// 邮件发送
MailSend::dispatch($order->email, $mailBody['tpl_name'], $mailBody['tpl_content']);
return $order;
}
}
+290
View File
@@ -0,0 +1,290 @@
<?php
/**
* The file was created by Assimon.
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
namespace App\Service;
use App\Exceptions\RuleValidationException;
use App\Models\BaseModel;
use App\Models\Coupon;
use App\Models\Goods;
use App\Models\Carmis;
use App\Models\Order;
use App\Rules\SearchPwd;
use App\Rules\VerifyImg;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class OrderService
{
/**
* 商品服务层.
* @var \App\Service\PayService
*/
private $goodsService;
/**
* 优惠码服务层
* @var \App\Service\CouponService
*/
private $couponService;
public function __construct()
{
$this->goodsService = app('Service\GoodsService');
$this->couponService = app('Service\CouponService');
}
/**
* 验证集合
*
* @param Request $request
* @throws RuleValidationException
* @throws \Illuminate\Validation\ValidationException
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function validatorCreateOrder(Request $request): void
{
$validator = Validator::make($request->all(), [
'gid' => 'required' ,
'email' => ['required', 'email'],
'payway' => ['required', 'integer'],
'search_pwd' => [new SearchPwd()],
'by_amount' => ['required', 'integer', 'min:1'],
'img_verify_code' => [new VerifyImg()],
], [
'by_amount.required' => __('dujiaoka.prompt.buy_amount_format_error'),
'by_amount.integer' => __('dujiaoka.prompt.buy_amount_format_error'),
'by_amount.min' => __('dujiaoka.prompt.buy_amount_format_error'),
'payway.required' => __('dujiaoka.prompt.please_select_mode_of_payment'),
'payway.integer' => __('dujiaoka.prompt.please_select_mode_of_payment'),
'email.required' => __('dujiaoka.prompt.email_format_error'),
'email.email' => __('dujiaoka.prompt.email_format_error'),
'gid.required' => __('dujiaoka.prompt.goods_does_not_exist'),
]);
if ($validator->fails()) {
throw new RuleValidationException($validator->errors()->first());
}
// 极验验证
if (
dujiaoka_config_get('is_open_geetest') == BaseModel::STATUS_OPEN
&&
!Validator::make($request->all(),
['geetest_challenge' => 'geetest',],
[ 'geetest' => __('dujiaoka.prompt.geetest_validate_fail')])
) {
throw new RuleValidationException(__('dujiaoka.prompt.geetest_validate_fail'));
}
}
/**
* 得到商品详情并验证
*
* @param Request $request 请求
* @throws RuleValidationException
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function validatorGoods(Request $request): Goods
{
// 获得商品详情
$goods = $this->goodsService->detail($request->input('gid'));
// 商品状态验证
$this->goodsService->validatorGoodsStatus($goods);
// 如果有限购
if ($goods->buy_limit_num > 0 && $request->input('by_amount') > $goods->buy_limit_num) {
throw new RuleValidationException(__('dujiaoka.prompt.purchase_limit_exceeded'));
}
// 库存不足
if ($request->input('by_amount') > $goods->in_stock) {
throw new RuleValidationException(__('dujiaoka.prompt.inventory_shortage'));
}
return $goods;
}
/**
* 判断是否有循环卡密
*
* @param int $goodsID 商品id
* @return array|null
*
* @author ZhangYiQiu<me@zhangyiqiu.net>
* @copyright ZhangYiQiu<me@zhangyiqiu.net>
* @link http://zhangyiqiu.net/
*/
public function validatorLoopCarmis(Request $request)
{
$carmis = Carmis::query()
->where('goods_id', $request->input('gid'))
->where('status', Carmis::STATUS_UNSOLD)
->where('is_loop', true)
->count();
if($carmis > 0 && $request->input('by_amount') > 1){
throw new RuleValidationException(__('dujiaoka.prompt.loop_carmis_limit'));
}
return $carmis;
}
/**
* 优惠码验证
*
* @param Request $request
* @return Coupon|null
* @throws RuleValidationException
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function validatorCoupon(Request $request):? Coupon
{
// 如果提交了优惠码
if ($request->filled('coupon_code')) {
// 查询优惠码是否存在
$coupon = $this->couponService->withHasGoods($request->input('coupon_code'), $request->input('gid'));
// 此商品没有这个优惠码
if (empty($coupon)) {
throw new RuleValidationException(__('dujiaoka.prompt.coupon_does_not_exist'));
}
// 剩余次数不足
if ($coupon->ret <= 0) {
throw new RuleValidationException(__('dujiaoka.prompt.coupon_lack_of_available_opportunities'));
}
return $coupon;
}
return null;
}
/**
* 代充框验证.
*
* @param Goods $goods
* @param Request $request
* @return string
* @throws RuleValidationException
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function validatorChargeInput(Goods $goods, Request $request): string
{
$otherIpt = '';
// 代充框验证
if ($goods->type == Goods::MANUAL_PROCESSING && !empty($goods->other_ipu_cnf)) {
// 如果有其他输入框 判断其他输入框内容 然后载入信息
$formatIpt = format_charge_input($goods->other_ipu_cnf);
foreach ($formatIpt as $item) {
if ($item['rule'] && !$request->filled($item['field'])) {
$errMessage = $item['desc'] . __('dujiaoka.prompt.can_not_be_empty');
throw new RuleValidationException($errMessage);
}
$otherIpt .= $item['desc'].':'.$request->input($item['field']) . PHP_EOL;
}
}
return $otherIpt;
}
/**
* 通过订单号查询订单
* @param string $orderSN
* @return Order
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function detailOrderSN(string $orderSN):? Order
{
$order = Order::query()->with(['coupon', 'pay', 'goods'])->where('order_sn', $orderSN)->first();
return $order;
}
/**
* 根据订单号过期订单.
*
* @param string $orderSN
* @return bool
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function expiredOrderSN(string $orderSN): bool
{
return Order::query()->where('order_sn', $orderSN)->update(['status' => Order::STATUS_EXPIRED]);
}
/**
* 设置订单优惠码已回退
*
* @param string $orderSN
* @return bool
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function couponIsBack(string $orderSN): bool
{
return Order::query()->where('order_sn', $orderSN)->update(['coupon_ret_back' => Order::COUPON_BACK_OK]);
}
/**
* 通过邮箱和查询密码查询
*
* @param string $email 邮箱
* @param string $searchPwd 查询面面
* @return array|\Illuminate\Database\Concerns\BuildsQueries[]|\Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Query\Builder[]|\Illuminate\Support\Collection
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function withEmailAndPassword(string $email, string $searchPwd = '')
{
return Order::query()
->where('email', $email)
->when(!empty($searchPwd), function ($query) use ($searchPwd) {
$query->where('search_pwd', $searchPwd);
})
->orderBy('created_at', 'DESC')
->take(5)
->get();
}
/**
* 通过订单号集合查询
*
* @param array $orderSNS 订单号集合
* @return \Illuminate\Database\Eloquent\Builder[]|\Illuminate\Database\Eloquent\Collection|\Illuminate\Database\Query\Builder[]|\Illuminate\Support\Collection
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function byOrderSNS(array $orderSNS)
{
return Order::query()
->whereIn('order_sn', $orderSNS)
->orderBy('created_at', 'DESC')
->take(5)
->get();
}
}
+75
View File
@@ -0,0 +1,75 @@
<?php
/**
* The file was created by Assimon.
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
namespace App\Service;
use App\Models\Pay;
class PayService
{
/**
* 加载支付网关
*
* @param string|int $payClient 支付场景客户端
* @return array|null
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function pays(string $payClient = Pay::PAY_CLIENT_PC): ?array
{
$payGateway = Pay::query()
->whereIn('pay_client', [$payClient, Pay::PAY_CLIENT_ALL])
->where('is_open', Pay::STATUS_OPEN)
->get();
return $payGateway ? $payGateway->toArray() : null;
}
/**
* 通过支付标识获得支付配置
*
* @param string $check 支付标识
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object|null
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function detailByCheck(string $check)
{
$gateway = Pay::query()
->where('pay_check', $check)
->where('is_open', Pay::STATUS_OPEN)
->first();
return $gateway;
}
/**
* 通过id查询支付网关
*
* @param int $id 支付网关id
* @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object|null
*
* @author assimon<ashang@utf8.hk>
* @copyright assimon<ashang@utf8.hk>
* @link http://utf8.hk/
*/
public function detail(int $id)
{
$gateway = Pay::query()
->where('id', $id)
->where('is_open', Pay::STATUS_OPEN)
->first();
return $gateway;
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}