zwesy 2021-11-24 16:20:06 +08:00
commit a93dc86036
12 changed files with 681 additions and 41 deletions

View File

@ -0,0 +1,152 @@
<?php
namespace app\controller\api;
use app\exception\RepositoryException;
use app\repository\BusinessRepository;
use app\repository\RechargeRepository;
use app\service\wx\WechatPay;
use app\model\Recharge as RechargeModel;
use think\facade\Db;
use think\facade\Log;
/**
* 充值
*
* Class Recharge
* @package app\controller\api
*/
class Recharge extends Base
{
protected $noNeedLogin = ['query'];
/**
* 查询是否支付成功
* */
public function query()
{
$orderNum = input("order_num/s");
if (empty($orderNum)) {
return $this->json("4001", "参数错误");
}
$recharge = RechargeRepository::getInstance()->getModel()->where(["order_num" => $orderNum])->lock(true)->find();
if (empty($recharge)) {
return $this->json("4001", "订单不存在");
}
if ($recharge['state'] == RechargeModel::state_on) {
return $this->json();
}
$business = BusinessRepository::getInstance()->getModel()->where(["code" => $recharge['business_code']])->lock(true)->find();
if (empty($business)) {
return $this->json("4001", "商家不存在");
}
//查询 交易成功判断条件: return_code、result_code和trade_state都为SUCCESS
$res = WechatPay::getInstance()->order->queryByOutTradeNumber($orderNum);
if ($res['return_code'] == 'SUCCESS') { // return_code 表示通信状态,不代表支付状态
if (isset($res['result_code']) && $res['result_code'] == 'SUCCESS') {
if (isset($res['trade_state']) && $res['trade_state'] == 'SUCCESS') {
Db::startTrans();
try {
//这里确定支付成功
$total_fee = $res['total_fee'] / 100;
//加余额
$business->save(["balance" => ($business["balance"] + $total_fee)]);
//修改支付状态
$recharge->save([
"money" => $total_fee,
"state" => RechargeModel::state_on,
"update_time" => date("Y-m-d H:i:s"),
"balance" => $business->balance
]);
Db::commit();
return $this->json();
} catch (RepositoryException $e) {
Db::rollback();
return $this->json("5001", $e->getMessage());
} catch (\Exception $e) {
Db::rollback();
return $this->json("5001", "充值失败");
}
}
}
}
return $this->json("4001", "未支付成功");
}
/**
* 微信的回调
*
* @throws \EasyWeChat\Kernel\Exceptions\Exception
*/
public function notify(){
if ($this->request->isPost()) {
$app = WechatPay::getInstance();
$response = $app->handlePaidNotify(function ($message, $fail) {
// $aa = '{"appid":"wxa02e44170bc722cd","bank_type":"OTHERS","cash_fee":"1","fee_type":"CNY","is_subscribe":"N","mch_id":"1605090111","nonce_str":"60f7d8a1e4ac8","openid":"oKrEm0ehgsy2ZTWzEva4tbLuUgFw","out_trade_no":"16268555858753004863","result_code":"SUCCESS","return_code":"SUCCESS","sign":"DB3F6CDCB7FBB3B9DDF7C0CC8BBD5AAD","time_end":"20210721162000","total_fee":"1","trade_type":"JSAPI","transaction_id":"4200001200202107217942681078"}';
// $message = json_decode($aa, true);
$m = json_encode($message, JSON_UNESCAPED_UNICODE);
$recharge = RechargeRepository::getInstance()->getModel()->where(["order_num" => $message['out_trade_no']])->lock(true)->find();
if (empty($recharge)) {
$this->log(sprintf("[微信支付回调][%s][%s]订单支付成功,但系统查无此订单 info:%s", date('Y-m-d H:i:s'), $message['out_trade_no'], $m), 'error');
return true;//订单不存在
}
if ($recharge['state'] == RechargeModel::state_on) {
return true;//订单已经支付
}
$business = BusinessRepository::getInstance()->getModel()->where(["code" => $recharge['business_code']])->lock(true)->find();
if (empty($business)) {
$this->log(sprintf("[微信支付回调][%s][%s]订单支付成功,但商家不存在 info:%s", date('Y-m-d H:i:s'), $message['out_trade_no'], $m), 'error');
return true;
}
if ($message['return_code'] == 'SUCCESS') { // return_code 表示通信状态,不代表支付状态
if (isset($message['result_code']) && $message['result_code'] == 'SUCCESS') {
if (isset($message['trade_state']) && $message['trade_state'] == 'SUCCESS') {
Db::startTrans();
try {
//这里确定支付成功
$total_fee = $message['total_fee'] / 100;
//加余额
$business->save(["balance" => ($business["balance"] + $total_fee)]);
//修改支付状态
$recharge->save([
"money" => $total_fee,
"state" => RechargeModel::state_on,
"update_time" => date("Y-m-d H:i:s"),
"balance" => $business->balance
]);
Db::commit();
//记录日志
$this->log(sprintf("[微信支付回调][%s][%s]订单支付成功 info:%s", date('Y-m-d H:i:s'), $message['out_trade_no'], $m), 'info');
return true;
} catch (RepositoryException $e) {
Db::rollback();
$this->log(sprintf("[微信支付回调][%s][%s]订单支付成功-修改订单状态失败-RepositoryException info:%s", date('Y-m-d H:i:s'), $message['out_trade_no'], $m), 'info');
return $fail('Order status edit failed.');
} catch (\Exception $e) {
Db::rollback();
$this->log(sprintf("[微信支付回调][%s][%s]订单支付成功-修改订单状态失败-Exception info:%s", date('Y-m-d H:i:s'), $message['out_trade_no'], $m), 'info');
return $fail('Order status edit failed.');
}
}
}
}
return $fail('通信失败,请稍后再通知我');
});
$response->send();
}
}
/**
* 记录订单日志
*
* @param string $message
* @param string $type
*/
private function log(string $message, string $type = 'info'): void
{
Log::channel('order')->write($message, $type);
}
}

View File

@ -1,35 +0,0 @@
<?php
/**
* @OA\Info(
* description="商城接口",
* version="1.0.0",
* title="大头商城",
* )
*/
/**
* @OA\Tag(
* name="common",
* description="通用",
* )
*
* @OA\Tag(
* name="home",
* description="首页",
* )
*
* @OA\Tag(
* name="goods",
* description="商品相关",
* )
* @OA\Tag(
* name="order",
* description="订单相关",
* )
* @OA\Tag(
* name="user",
* description="用户相关",
* )
*
*/

View File

@ -73,9 +73,7 @@ class Business extends Base
->count();
//商家充值总额
$item->recharge_total_money = Recharge::where(["business_code" => $item->business->code])
->where("state", "=", Recharge::state_on)
->sum("money");
$item->recharge_total_money = $item->business->total_recharge;
});
return $this->json(0, 'success', $list);

View File

@ -0,0 +1,99 @@
<?php
namespace app\controller\manager;
use app\exception\RepositoryException;
use app\model\CouponMain;
use app\repository\CouponRepository;
use Exception;
use think\facade\Db;
use think\response\Json;
use think\response\View;
/**
* 优惠券相关
**/
class Coupon extends Base
{
/**
* 列表
*
* @return Json|View
* @throws Exception
*/
public function index()
{
if ($this->request->isPost()) {
$model = new CouponMain();
$repo = CouponRepository::getInstance($model);
$keyword = $this->request->param('keyword/s', '');
$on_shelf = $this->request->param('on_shelf');
$start_time = $this->request->param('start_time',);
$end_time = $this->request->param('end_time');
$page = $this->request->param('page/d', 1);
$size = $this->request->param('size/d', 30);
$whereMap = [];
$orders = ['id' => 'desc'];
if (!empty($on_shelf) && in_array($on_shelf, [CouponMain::COMMON_ON, CouponMain::COMMON_OFF])) {
$whereMap[] = ['on_shelf', '=', $on_shelf];
}
if (!empty($start_time)) {
$whereMap[] = ['start_time', '>=', $start_time];
}
if (!empty($end_time)) {
$whereMap[] = ['end_time', '<=', $end_time];
}
if (!empty($keyword)) {
$whereMap[] = ['name', 'like', "%" . $keyword . "%"];
}
$list = $repo->findList($whereMap, [], $page, $size, function ($q) {
if (!empty($keyword)) {
return $q::hasWhere('business', function ($q) use ($keyword) {
$q->where('business_name', 'like', "%" . $keyword . "%")->field("code,business_name,business_subtitle,type")
->with('category');
});
}
return $q->with(["business" => function ($query) {
$query->field("code,business_name,business_subtitle,type")
->with('category');
}]);
}, $orders);
$time = time();
$list['list']->each(function ($item) use ($time) {
if (strtotime($item->start_time) > $time) {
$item->state_text = '<span >未开始</span>';
} else if ((strtotime($item->start_time) < $time) && (strtotime($item->end_time) > $time)) {
$item->state_text = '<span class="f_green">进行中</span>';
} else {
$item->state_text = '<span class="f_red">已过期</span>';
}
});
return $this->json(0, 'success', $list);
}
return $this->view();
}
public function shelf()
{
$id = input("id/d", 0);
$on_shelf = input("on_shelf/d", 1);
$model = new CouponMain();
$repo = CouponRepository::getInstance($model);
$coupon = $repo->findById($id);
if (empty($coupon)) {
return $this->json(4001, "优惠券不存在");
}
if (in_array($on_shelf, [CouponMain::COMMON_OFF, CouponMain::COMMON_ON])) {
return $this->json(4001, "状态错误");
}
$coupon->save(["on_shelf"=>$on_shelf]);
return $this->json();
}
}

View File

@ -7,5 +7,8 @@ class Business extends Base
const state_reviewing = 0;
const state_on = 1;
const state_off = 2;
public function category()
{
return $this->hasOne(Category::class, 'id',"type");
}
}

View File

@ -18,4 +18,9 @@ class CouponMain extends Base
const status_off = 1;//停止
const on_shelf_on = 0;//上架状态
const on_shelf_off = 1;//下架状态
public function business()
{
return $this->hasOne(Business::class, 'code',"business_code");
}
}

View File

@ -130,7 +130,7 @@ class Repository
$line = '['.$name.'] '.$e->getMessage();
Log::error($line);
throw new RepositoryException('Repository异常'.(Env::get('app_debug') ? ': '.$line : ''), 5009);
throw new RepositoryException('Repository异常'.$line.(Env::get('app_debug') ? ': '.$line : ''), 5009);
} finally {
if ($exception && $failReturn instanceof RepositoryException) {
if (Env::get('app_debug')) {

View File

@ -1 +1,9 @@
<IfModule mod_rewrite.c>
Options +FollowSymlinks -Multiviews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !^(.*)\.(gif|jpg|jpeg|png|swf|mp4)$ [NC]
RewriteRule ^(.*)$ index.php?s=/$1 [QSA,PT,L]
</IfModule>

View File

@ -0,0 +1,100 @@
layui.use(['laytpl', 'table', 'jquery', 'form', 'miniTab', 'xmSelect','laydate'], function () {
let $ = layui.jquery,
form = layui.form,
table = layui.table,
layer = layui.layer,
xmSelect = layui.xmSelect,
laydate = layui.laydate,
miniTab = layui.miniTab;
/**** index begin ***/
//index页面
if ($('.location-index-page').length > 0) {
miniTab.listen();
// 渲染表格
let listUrl = $('#table-container').data('url');
let insTb = table.render({
elem: '#table-container',
toolbar: '#toolbar-tpl',
defaultToolbar: [{ //自定义头部工具栏右侧图标。如无需自定义,去除该参数即可
title: '搜索'
, layEvent: 'search'
, icon: 'layui-icon-search'
}],
url: listUrl,
method: 'post',
even: true,
limits: [10,20,50,100,200,500,1000],
request: {
pageName: 'page',
limitName: 'size',
},
parseData: function (res) {
return {
"code": res.code, //解析接口状态
"msg": res.msg, //解析提示文本
"count": res.data.total, //解析数据长度
"data": res.data.list //解析数据列表
};
},
page: true,
cols: [[
// {type: 'checkbox'},
{field: 'id' , width: 80, title: 'ID'},
{field: 'name', title: '名称'},
{templet:function(d){
if( d.business != undefined ){
return d.business.business_name!=null?d.business.business_name:''
}
return d.business_name;
}, title: '所属商家'},
{templet:function(d){
if( d.business != undefined ){
return d.business.business_subtitle!=null?d.business.business_subtitle:''
}
return '';
}, title: '商家简称'},
{field: 'money', title: '金额'},
{field: 'deduction_money', title: '扣费'},
{field: 'start_time', title: '开始时间'},
{field: 'end_time', title: '结束时间'},
{templet: '#row-on_shelf', title: '上架状态'},
{field: 'state_text', title: '状态'},
{field: 'create_time', title: '创建时间'},
{templet: '#row-operate', field: 'right', align: 'center', title: '操作', fixed: 'right'}
]],
done: function () {
Tools.setInsTb(insTb);
}
});
// 监听搜索操作
form.on('submit(data-search-btn)', function (data) {
//执行搜索重载
table.reload('table-container', {
page: {curr: 1}
, where: data.field
}, 'data');
return false;
});
//日期时间选择器
laydate.render({
elem: '#start_time'
,type: 'date'
});
//日期时间选择器
laydate.render({
elem: '#end_time'
,type: 'date'
});
}
/*** index end ***/
});

View File

@ -0,0 +1,58 @@
layui.use(['laytpl', 'table', 'jquery', 'form', 'miniTab', 'xmSelect'], function () {
let $ = layui.jquery,
form = layui.form,
table = layui.table,
layer = layui.layer,
xmSelect = layui.xmSelect,
miniTab = layui.miniTab;
/**** index begin ***/
//index页面
if ($('.location-index-page').length > 0) {
miniTab.listen();
// 渲染表格
let listUrl = $('#table-container').data('url');
let insTb = table.render({
elem: '#table-container',
toolbar: '#toolbar-tpl',
defaultToolbar: null,
url: listUrl,
method: 'post',
even: true,
limits: [10,20,50,100,200,500,1000],
request: {
pageName: 'page',
limitName: 'size',
},
parseData: function (res) {
return {
"code": res.code, //解析接口状态
"msg": res.msg, //解析提示文本
"count": res.data.total, //解析数据长度
"data": res.data.list //解析数据列表
};
},
page: true,
cols: [[
// {type: 'checkbox'},
{field: 'id' , width: 80, title: 'ID'},
{field: 'name', title: '名称'},
{field: 'type_name', title: '优惠券类型'},
{templet: '#row-received_map', title: '领取位置'},
{field: 'money', title: '金额'},
{field: 'business_name', title: '商家名称'},
{field: 'business_name', title: '商家名称'},
{templet: '#row-sign_map', title: '签到位置'},
{templet: '#row-state', title: '状态'},
]],
done: function () {
Tools.setInsTb(insTb);
}
});
}
/*** index end ***/
});

View File

@ -0,0 +1,118 @@
{layout name="manager/layout" /}
<style>
.layui-table-cell{
height: auto;
white-space: normal;
}
.layui-table .layui-layer-photos {height: 90px;}
.layui-table img{
max-height: 100%;
object-fit: cover;
margin-left: auto;
margin-right: auto;
display: block;
max-width: 150px;
vertical-align: middle;
border: 0;
}
.f_red{
font-weight: bold;
color: red;
}
.f_green{
font-weight: bold;
color: green;
}
</style>
<div class="layui-row layui-col-space12">
<div class="layui-col-xs12 layui-col-md12">
<div id="echarts-records" style="background-color:#ffffff;min-height:600px;">
<div class="layuimini-container location-index-page">
<div class="layuimini-main">
<fieldset class="table-search-fieldset">
<legend>搜索信息</legend>
<div style="margin: 10px 10px 10px 10px">
<form class="layui-form layui-form-pane" action="">
<div class="layui-form-item">
<div class="layui-inline">
<label class="layui-form-label">关键词</label>
<div class="layui-inline">
<input type="text" name="keyword" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">开始时间</label>
<div class="layui-inline">
<input type="text" name="start_time" class="layui-input" id="start_time" placeholder="yyyy-MM-dd HH:mm:ss">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">结束</label>
<div class="layui-inline">
<input type="text" name="end_time" class="layui-input" id="end_time" placeholder="yyyy-MM-dd HH:mm:ss">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">上下状态</label>
<div class="layui-inline">
<select name="on_shelf" id="">
<option value=""></option>
<option value=""></option>
<option value="1">下架</option>
<option value="0">上架</option>
</select>
</div>
</div>
<div class="layui-inline">
<button type="submit" class="layui-btn layui-btn-primary" lay-submit lay-filter="data-search-btn"><i class="layui-icon"></i> 搜 索</button>
</div>
</div>
</form>
</div>
</fieldset>
<div>
<table id="table-container" class="layui-table" data-url="/manager/coupon/index" lay-filter="table-container"></table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 操作列 -->
<script type="text/html" id="row-operate">
<a class="layui-btn layui-btn-primary layui-btn-xs" data-href="/manager/coupon/info.html?id={{d.id}}" data-title="消费者【{{ d.nick_name }}】领取日志" lay-event="">领取日志</a>
{{# if(d.on_shelf == 1){ }}
<a class="layui-btn layui-btn-primary layui-btn-xs" data-href="/manager/coupon/shelf.html?id={{d.id}}&&on_shelf=0" lay-event="shelf">上架</a>
{{# }else{ }}
<a class="layui-btn layui-btn-primary layui-btn-xs" data-href="/manager/coupon/shelf.html?id={{d.id}}&&on_shelf=1" lay-event="shelf">下架</a>
{{# } }}
</script>
<!-- toolbar -->
<script type="text/html" id="toolbar-tpl">
<a class="layui-btn layui-btn-primary layui-btn-sm" data-table-refresh lay-event="refresh"><i class="fa fa-refresh"></i></a>
</script>
<!-- 列 性别 -->
<script type="text/html" id="row-on_shelf">
{{# if(d.gender==1){ }}
下架
{{# }else{ }}
上架
{{# } }}
</script>
<!-- 列 轮播图 -->
<script type="text/html" id="row-cover">
<div class="layui-layer-photos">
<img src="{{ d.avatar_url }}" layer-src="{{ d.avatar_url }}" alt="">
</div>
</script>
<script src="__MANAGER__/js/coupon/index.js?v={:mt_rand()}"></script>

View File

@ -0,0 +1,134 @@
{layout name="manager/layout" /}
<style>
.layui-table-cell{
height: auto;
white-space: normal;
}
.layui-table .layui-layer-photos {height: 90px;}
.layui-table img{
max-height: 100%;
object-fit: cover;
margin-left: auto;
margin-right: auto;
display: block;
max-width: 150px;
vertical-align: middle;
border: 0;
}
#avatar{
max-width: 100%;
display: block;
margin: auto;
}
</style>
<div class="layui-row layui-col-space12">
<div class="layui-row layui-col-space15">
<div class="layui-col-md2">
<div class="layui-col-md12">
<div class="layui-layer-photos">
<img id="avatar" src="{$consumer.avatar_url}" layer-src="{$consumer.avatar_url}" alt="">
</div>
</div>
</div>
<div class="layui-col-md10">
<div class="layui-col-md12">
<div class="layui-col-md4">
<div class="layui-panel" style="border-radius: 8px;">
<div class="layui-row" style="padding: 5% 0;text-align: center">
<h3><strong>评论数</strong></h3>
<span style="font-size: 36px">{$totalComment ?? 0}</span>
</div>
</div>
</div>
<div class="layui-col-md4">
<div class="layui-panel" style="border-radius: 8px;">
<div class="layui-row" style="padding: 5% 0;text-align: center">
<h3><strong>本月评论数</strong></h3>
<span style="font-size: 36px">{$totalTheMonthComment ?? 0}</span>
</div>
</div>
</div>
<div class="layui-col-md4">
<div class="layui-panel" style="border-radius: 8px;">
<div class="layui-row" style="padding: 5% 0;text-align: center">
<h3><strong>标签</strong></h3>
<span style="font-size: 32px">{$consumer["tag"]["name"] ?? '无'}</span>
</div>
</div>
</div>
<div class="layui-col-md4">
<div class="layui-panel" style="border-radius: 8px;">
<div class="layui-row" style="padding: 5% 0;text-align: center">
<h3><strong>优惠券领取总数</strong></h3>
<span style="font-size: 36px">{$couponTotalCount ?? 0}</span>
</div>
</div>
</div>
<div class="layui-col-md4">
<div class="layui-panel" style="border-radius: 8px;">
<div class="layui-row" style="padding: 5% 0;text-align: center">
<h3><strong>优惠券使用总数</strong></h3>
<span style="font-size: 36px">{$couponUsedTotalCount ?? 0}</span>
</div>
</div>
</div>
<div class="layui-col-md4">
<div class="layui-panel" style="border-radius: 8px;">
<div class="layui-row" style="padding: 5% 0;text-align: center">
<h3><strong>优惠券未使用总数</strong></h3>
<span style="font-size: 36px">{$couponNotUsedTotalCount ?? 0}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="layui-col-xs12 layui-col-md12">
<div id="echarts-records" style="background-color:#ffffff;min-height:600px;">
<div class="layuimini-container location-index-page">
<div class="layuimini-main">
<div>
<table id="table-container" class="layui-table" data-url="/manager/consumer/info.html?id={$consumer.id}&&sign={$sign}" lay-filter="table-container"></table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 操作列 -->
<script type="text/html" id="row-state">
{{ d.time_state }}
|
{{# if(d.is_verificated == 1){ }}
已验证
{{# }else{ }}
未验证
{{# } }}
</script>
<!-- 领取位置 -->
<script type="text/html" id="row-received_map">
<a href="" target="_blank">点击查看</a>
</script>
<!-- 签到位置 -->
<script type="text/html" id="row-sign_map">
<a href="" target="_blank">点击查看</a>
</script>
<!-- toolbar -->
<script type="text/html" id="toolbar-tpl">
<a class="layui-btn layui-btn-primary layui-btn-sm" data-table-refresh lay-event="refresh"><i class="fa fa-refresh"></i></a>
</script>
<script src="__MANAGER__/js/consumer/info.js?v={:mt_rand()}"></script>