1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-06-24 20:06:09 +08:00

Merge branch 'develop'

This commit is contained in:
koogua 2021-03-09 11:10:34 +08:00
commit 81c134346c
37 changed files with 496 additions and 179 deletions

View File

@ -1,3 +1,17 @@
### [v1.2.8](https://gitee.com/koogua/course-tencent-cloud/releases/v1.2.8)(2021-03-08)
### 更新
- 数据库迁移脚本整理
- 数据表软删除字段整理
- 微信公众号路由整理
- 退款增加手续费逻辑
- 课程增加不支持退款逻辑
- 会员价格和期限可通过后台配置
- 修复IM通知中字段重命名导致的问题
- 修复购买会员会员标识未改变的问题
- 会员中心订单列表样式调整
### [v1.2.7](https://gitee.com/koogua/course-tencent-cloud/releases/v1.2.7)(2021-02-26)
### 新增

View File

@ -39,8 +39,8 @@ class RefundTask extends Task
$itemInfo = $task->item_info;
$refund = $refundRepo->findById($itemInfo['refund']['id']);
$trade = $tradeRepo->findById($itemInfo['refund']['trade_id']);
$order = $orderRepo->findById($itemInfo['refund']['order_id']);
$trade = $tradeRepo->findById($refund->trade_id);
$order = $orderRepo->findById($refund->order_id);
if (!$refund || !$trade || !$order) {
$task->status = TaskModel::STATUS_FAILED;

View File

@ -282,7 +282,7 @@ class SettingController extends Controller
if ($this->request->isPost()) {
$data = $this->request->getPost('vip', 'string');
$data = $this->request->getPost('vip');
$settingService->updateVipSettings($data);
@ -290,9 +290,9 @@ class SettingController extends Controller
} else {
$vips = $settingService->getVipSettings();
$items = $settingService->getVipSettings();
$this->view->setVar('vips', $vips);
$this->view->setVar('items', $items);
}
}

View File

@ -3,6 +3,7 @@
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\Trade as TradeService;
use Phalcon\Mvc\View;
/**
* @RoutePrefix("/admin/trade")
@ -70,25 +71,26 @@ class TradeController extends Controller
}
/**
* @Post("/{id:[0-9]+}/refund", name="admin.trade.refund")
* @Route("/{id:[0-9]+}/refund", name="admin.trade.refund")
*/
public function refundAction($id)
{
$tradeService = new TradeService();
$refund = $tradeService->refundTrade($id);
if ($this->request->isPost()) {
$location = $this->url->get([
'for' => 'admin.refund.show',
'id' => $refund->id,
]);
$tradeService->refundTrade($id);
$content = [
'location' => $location,
'msg' => '申请退款成功',
];
return $this->jsonSuccess(['msg' => '提交申请成功']);
}
return $this->jsonSuccess($content);
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$trade = $tradeService->getTrade($id);
$confirm = $tradeService->confirmRefund($id);
$this->view->setVar('trade', $trade);
$this->view->setVar('confirm', $confirm);
}
}

View File

@ -91,7 +91,7 @@ class Setting extends Service
{
$vipRepo = new VipRepo();
return $vipRepo->findAll(['deleted' => 0]);
return $vipRepo->findAll();
}
public function getLiveSettings($section)
@ -198,9 +198,13 @@ class Setting extends Service
{
$vipRepo = new VipRepo();
foreach ($items as $id => $price) {
foreach ($items as $id => $item) {
$vip = $vipRepo->findById($id);
$vip->price = $price;
if (!$vip) continue;
$vip->title = sprintf('%s个月', $item['expiry']);
$vip->expiry = (int)$item['expiry'];
$vip->price = (float)$item['price'];
$vip->deleted = (int)$item['deleted'];
$vip->update();
}
}

View File

@ -10,6 +10,7 @@ use App\Repos\Account as AccountRepo;
use App\Repos\Order as OrderRepo;
use App\Repos\Trade as TradeRepo;
use App\Repos\User as UserRepo;
use App\Validators\Refund as RefundValidator;
use App\Validators\Trade as TradeValidator;
class Trade extends Service
@ -93,22 +94,47 @@ class Trade extends Service
return $accountRepo->findById($userId);
}
public function confirmRefund($tradeId)
{
$trade = $this->findOrFail($tradeId);
$orderRepo = new OrderRepo();
$order = $orderRepo->findById($trade->order_id);
$refund = new \App\Services\Refund();
return $refund->preview($order);
}
public function refundTrade($id)
{
$trade = $this->findOrFail($id);
$user = $this->getLoginUser();
$post = $this->request->getPost();
$validator = new TradeValidator();
$validator->checkIfAllowRefund($trade);
$validator = new RefundValidator();
$applyNote = $validator->checkApplyNote($post['apply_note']);
$refundAmount = $validator->checkAmount($trade->amount, $post['refund_amount']);
$applyNote = sprintf('%s - 操作员(%s', $applyNote, $user->id);
$refund = new RefundModel();
$refund->amount = $refundAmount;
$refund->subject = $trade->subject;
$refund->amount = $trade->amount;
$refund->owner_id = $trade->owner_id;
$refund->order_id = $trade->order_id;
$refund->trade_id = $trade->id;
$refund->apply_note = '后台人工申请退款';
$refund->apply_note = $applyNote;
$refund->create();

View File

@ -4,7 +4,7 @@
<div class="kg-order-item">
<p>课程名称:{{ course['title'] }}</p>
<p>优惠价格:{{ '¥%0.2f'|format(course['market_price']) }},会员价格:{{ '¥%0.2f'|format(course['vip_price']) }}</p>
<p>学习期限:{{ date('Y-m-d H:i:s',course['study_expiry_time']) }},退款期限:{{ date('Y-m-d H:i:s',course['refund_expiry_time']) }}</p>
<p>学习期限:{{ date('Y-m-d H:i:s',course['study_expiry_time']) }},退款期限:{% if course['refund_expiry'] > 0 %}{{ date('Y-m-d H:i:s',course['refund_expiry_time']) }}{% else %}不支持{% endif %}</p>
</div>
{% elseif order.item_type == 2 %}
{% set courses = order.item_info['courses'] %}
@ -12,7 +12,7 @@
<div class="kg-order-item">
<p>课程名称:{{ course['title'] }}</p>
<p>优惠价格:{{ '¥%0.2f'|format(course['market_price']) }},会员价格:{{ '¥%0.2f'|format(course['vip_price']) }}</p>
<p>学习期限:{{ date('Y-m-d H:i:s',course['study_expiry_time']) }},退款期限:{{ date('Y-m-d H:i:s',course['refund_expiry_time']) }}</p>
<p>学习期限:{{ date('Y-m-d H:i:s',course['study_expiry_time']) }},退款期限:{% if course['refund_expiry'] > 0 %}{{ date('Y-m-d H:i:s',course['refund_expiry_time']) }}{% else %}不支持{% endif %}</p>
</div>
{% endfor %}
{% elseif order.item_type == 3 %}

View File

@ -6,6 +6,17 @@
<input type="radio" name="enabled" value="0" title="否" {% if alipay.enabled == "0" %}checked="checked"{% endif %}>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">手续费率</label>
<div class="layui-input-block">
<select name="service_rate" lay-verify="number">
{% for value in 1..30 %}
{% set selected = (value == alipay.service_rate) ? 'selected="selected"' : '' %}
<option value="{{ value }}" {{ selected }}>{{ value }}%</option>
{% endfor %}
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">App ID</label>
<div class="layui-input-block">

View File

@ -6,6 +6,17 @@
<input type="radio" name="enabled" value="0" title="否" {% if wxpay.enabled == "0" %}checked="checked"{% endif %}>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">手续费率</label>
<div class="layui-input-block">
<select name="service_rate" lay-verify="number">
{% for value in 1..30 %}
{% set selected = (value == wxpay.service_rate) ? 'selected="selected"' : '' %}
<option value="{{ value }}" {{ selected }}>{{ value }}%</option>
{% endfor %}
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">公众号ID</label>
<div class="layui-input-block">

View File

@ -36,7 +36,7 @@
<fieldset class="layui-elem-field layui-field-title">
<legend>行为奖励规则</legend>
</fieldset>
<table class="layui-table kg-table layui-form" style="width:60%;">
<table class="layui-table kg-table layui-form" style="width:80%;">
<colgroup>
<col width="20%">
<col width="20%">

View File

@ -6,15 +6,39 @@
<fieldset class="layui-elem-field layui-field-title">
<legend>会员设置</legend>
</fieldset>
{% for item in vips %}
<div class="layui-form-item">
<label class="layui-form-label">{{ item.title }}</label>
<div class="layui-input-inline">
<input class="layui-input" type="text" name="vip[{{ item.id }}]" value="{{ item.price }}" lay-verify="number">
</div>
<div class="layui-form-mid layui-word-aux">元</div>
</div>
{% endfor %}
<table class="layui-table kg-table layui-form" style="width:80%;">
<colgroup>
<col width="20%">
<col width="20%">
<col>
</colgroup>
<thead>
<tr>
<th>启用</th>
<th>期限</th>
<th>价格(元)</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr>
<td>
<input type="radio" name="vip[{{ item.id }}][deleted]" value="0" title="是" {% if item.deleted == 0 %}checked="checked"{% endif %}>
<input type="radio" name="vip[{{ item.id }}][deleted]" value="1" title="否" {% if item.deleted == 1 %}checked="checked"{% endif %}>
</td>
<td>
<select name="vip[{{ item.id }}][expiry]" lay-verify="required">
{% for value in 1..60 %}
<option value="{{ value }}" {% if item.expiry == value %}selected="selected"{% endif %}>{{ value }}个月</option>
{% endfor %}
</select>
</td>
<td><input class="layui-input" type="text" name="vip[{{ item.id }}][price]" value="{{ item.price }}" lay-verify="number"></td>
</tr>
{% endfor %}
</tbody>
</table>
<br>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">

View File

@ -0,0 +1,114 @@
{% extends 'templates/layer.volt' %}
{% block content %}
{%- macro item_info(confirm) %}
{% if confirm.item_type == 1 %}
{% set course = confirm.item_info.course %}
{% set expiry_flag = course.refund_expiry_time < time() ? '(已过期)' : '' %}
<div class="kg-order-item">
<p>课程名称:{{ course.title }}</p>
<p>退款期限:{{ date('Y-m-d H:i:s',course.refund_expiry_time) }} {{ expiry_flag }}</p>
<p>退款金额:{{ '¥%0.2f'|format(course.refund_amount) }},退款比例:{{ 100 * course.refund_percent }}%</p>
</div>
{% elseif confirm.item_type == 2 %}
{% set courses = confirm.item_info.courses %}
{% for course in courses %}
{% set expiry_flag = course.refund_expiry_time < time() ? '(已过期)' : '' %}
<div class="kg-order-item">
<p>课程名称:{{ course.title }}</p>
<p>退款期限:{{ date('Y-m-d H:i:s',course.refund_expiry_time) }} {{ expiry_flag }}</p>
<p>退款金额:{{ '¥%0.2f'|format(course.refund_amount) }},退款比例:{{ 100 * course.refund_percent }}%</p>
</div>
{% endfor %}
{% elseif confirm.item_type == 3 %}
{% set course = confirm.item_info.course %}
{% set reward = confirm.item_info.reward %}
<div class="kg-order-item">
<p>课程名称:{{ course.title }}</p>
<p>赞赏金额:{{ '¥%0.2f'|format(reward.price) }}</p>
</div>
{% elseif confirm.item_type == 4 %}
{% set vip = confirm.item_info.vip %}
<div class="kg-order-item">
<p>服务名称:会员服务({{ vip.title }}</p>
<p>会员期限:{{ date('Y-m-d H:i:s',vip.expiry_time) }}</p>
</div>
{% elseif confirm.item_type == 99 %}
<div class="kg-order-item">
<p>服务名称:支付测试</p>
</div>
{% endif %}
{%- endmacro %}
<table class="layui-table kg-table">
<tr>
<td>退款项目</td>
<td>支付金额</td>
<td>手续费({{ confirm.service_rate }}%</td>
<td>退款金额</td>
</tr>
<tr>
<td>{{ item_info(confirm) }}</td>
<td>{{ '¥%0.2f'|format(trade.amount) }}</td>
<td>{{ '¥%0.2f'|format(confirm.service_fee) }}</td>
<td>
<div id="refund-amount-tips">{{ '¥%0.2f'|format(confirm.refund_amount) }}</div>
</td>
</tr>
</table>
<br>
<form class="layui-form" method="post" action="{{ url({'for':'admin.trade.refund','id':trade.id}) }}">
<div class="layui-form-item">
<label class="layui-form-label">退款比例</label>
<div class="layui-input-block">
<div id="slider" style="padding-top:15px;"></div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">退款原因</label>
<div class="layui-input-block">
<input class="layui-input" name="apply_note" lay-verify="required">
</div>
</div>
<div class="layui-form-item kg-center">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交申请</button>
<input type="hidden" name="trade_amount" value="{{ trade.amount }}">
<input type="hidden" name="refund_amount" value="{{ confirm.refund_amount }}">
<input type="hidden" name="service_fee" value="{{ confirm.service_fee }}">
</div>
</form>
{% endblock %}
{% block inline_js %}
<script>
layui.use(['jquery', 'slider', 'layer'], function () {
var $ = layui.jquery;
var slider = layui.slider;
var index = parent.layer.getFrameIndex(window.name);
parent.layer.iframeAuto(index);
slider.render({
elem: '#slider',
value: {{ confirm.service_rate }},
change: function (ratio) {
var $tradeAmount = $('input[name=trade_amount]');
var $refundAmount = $('input[name=refund_amount]');
var $serviceFee = $('input[name=service_fee]');
var $refundAmountTips = $('#refund-amount-tips');
var refundAmount = (parseFloat($tradeAmount.val()) - parseFloat($serviceFee.val())) * ratio / 100;
$refundAmount.val(refundAmount.toFixed(2));
$refundAmountTips.text('¥' + refundAmount.toFixed(2));
}
});
});
</script>
{% endblock %}

View File

@ -9,11 +9,11 @@
<br>
{% set trade_refund_url = url({'for':'admin.trade.refund','id':trade.id}) %}
{% set refund_url = url({'for':'admin.trade.refund','id':trade.id}) %}
<div class="kg-center">
{% if trade.status == 2 %}
<button class="kg-refund layui-btn layui-bg-green" data-url="{{ trade_refund_url }}">申请退款</button>
<button class="kg-refund layui-btn layui-bg-green" data-url="{{ refund_url }}">申请退款</button>
{% endif %}
<button class="kg-back layui-btn layui-bg-gray">返回上页</button>
</div>
@ -32,15 +32,15 @@
<th></th>
</tr>
{% for item in refunds %}
{% set refund_sh_url = url({'for':'admin.refund.status_history','id':item.id}) %}
{% set refund_show_url = url({'for':'admin.refund.show','id':item.id}) %}
{% set history_url = url({'for':'admin.refund.status_history','id':item.id}) %}
{% set show_url = url({'for':'admin.refund.show','id':item.id}) %}
<tr>
<td>{{ item.sn }}</td>
<td>{{ '¥%0.2f'|format(item.amount) }}</td>
<td><a href="javascript:" title="{{ item.apply_note }}">{{ substr(item.apply_note,0,15) }}</td>
<td><a class="kg-status-history" href="javascript:" title="查看历史状态" data-url="{{ refund_sh_url }}">{{ refund_status(item.status) }}</a></td>
<td><a class="kg-status-history" href="javascript:" title="查看历史状态" data-url="{{ history_url }}">{{ refund_status(item.status) }}</a></td>
<td>{{ date('Y-m-d H:i:s',item.create_time) }}</td>
<td><a class="layui-btn layui-btn-sm" href="{{ refund_show_url }}">详情</a></td>
<td><a class="layui-btn layui-btn-sm" href="{{ show_url }}">详情</a></td>
</tr>
{% endfor %}
</table>
@ -69,25 +69,15 @@
layui.use(['jquery', 'layer'], function () {
var $ = layui.jquery;
var layer = layui.layer;
$('.kg-refund').on('click', function () {
var url = $(this).data('url');
var tips = '确定要申请退款吗?';
layer.confirm(tips, function () {
$.ajax({
type: 'POST',
url: url,
success: function (res) {
layer.msg(res.msg, {icon: 1});
setTimeout(function () {
window.location.href = res.location;
}, 3000);
},
error: function (xhr) {
var json = JSON.parse(xhr.responseText);
layer.msg(json.msg, {icon: 2});
}
});
layer.open({
type: 2,
title: '申请退款',
content: [url, 'no'],
area: ['800px', '320px']
});
});

View File

@ -1,4 +1,4 @@
{% set trade_sh_url = url({'for':'admin.trade.status_history','id':trade.id}) %}
{% set trade_history_url = url({'for':'admin.trade.status_history','id':trade.id}) %}
<fieldset class="layui-elem-field layui-field-title">
<legend>交易信息</legend>
@ -16,7 +16,7 @@
<td>{{ trade.sn }}</td>
<td>{{ '¥%0.2f'|format(trade.amount) }}</td>
<td>{{ channel_type(trade.channel) }}</td>
<td><a class="kg-status-history" href="javascript:" title="查看历史状态" data-url="{{ trade_sh_url }}">{{ trade_status(trade.status) }}</a></td>
<td><a class="kg-status-history" href="javascript:" title="查看历史状态" data-url="{{ trade_history_url }}">{{ trade_status(trade.status) }}</a></td>
<td>{{ date('Y-m-d H:i:s',trade.create_time) }}</td>
</tr>
</table>

View File

@ -14,7 +14,7 @@ class WeChatOfficialAccountController extends \Phalcon\Mvc\Controller
use ResponseTrait;
/**
* @Get("/subscribe/status", name="home.wechat.oa.sub_status")
* @Get("/subscribe/status", name="home.wechat_oa.sub_status")
*/
public function subscribeStatusAction()
{
@ -26,7 +26,7 @@ class WeChatOfficialAccountController extends \Phalcon\Mvc\Controller
}
/**
* @Get("/subscribe/qrcode", name="home.wechat.oa.sub_qrcode")
* @Get("/subscribe/qrcode", name="home.wechat_oa.sub_qrcode")
*/
public function subscribeQrCodeAction()
{
@ -38,7 +38,7 @@ class WeChatOfficialAccountController extends \Phalcon\Mvc\Controller
}
/**
* @Get("/notify", name="home.wechat.oa.verify")
* @Get("/notify", name="home.wechat_oa.verify")
*/
public function verifyAction()
{
@ -54,7 +54,7 @@ class WeChatOfficialAccountController extends \Phalcon\Mvc\Controller
}
/**
* @Post("/notify", name="home.wechat.oa.notify")
* @Post("/notify", name="home.wechat_oa.notify")
*/
public function notifyAction()
{

View File

@ -60,9 +60,7 @@ Trait ImGroupTrait
$notice = $validator->checkNotice($noticeId);
if ($notice->item_type != ImNoticeModel::TYPE_GROUP_REQUEST) {
return;
}
if ($notice->item_type != ImNoticeModel::TYPE_GROUP_REQUEST) return;
$groupId = $notice->item_info['group']['id'] ?: 0;
@ -70,7 +68,7 @@ Trait ImGroupTrait
$group = $validator->checkGroup($groupId);
$validator->checkOwner($user->id, $group->user_id);
$validator->checkOwner($user->id, $group->owner_id);
$applicant = $this->getImUser($notice->sender_id);
@ -120,9 +118,7 @@ Trait ImGroupTrait
$notice = $validator->checkNotice($noticeId);
if ($notice->item_type != ImNoticeModel::TYPE_GROUP_REQUEST) {
return;
}
if ($notice->item_type != ImNoticeModel::TYPE_GROUP_REQUEST) return;
$groupId = $notice->item_info['group']['id'] ?: 0;
@ -130,7 +126,7 @@ Trait ImGroupTrait
$group = $validator->checkGroup($groupId);
$validator->checkOwner($user->id, $group->user_id);
$validator->checkOwner($user->id, $group->owner_id);
$itemInfo = $notice->item_info;
@ -282,9 +278,7 @@ Trait ImGroupTrait
$users = $groupRepo->findUsers($group->id);
if ($users->count() == 0) {
return;
}
if ($users->count() == 0) return;
Gateway::$registerAddress = $this->getRegisterAddress();

View File

@ -28,7 +28,11 @@
{%- macro meta_expiry_info(course) %}
<p class="item">
<span class="key">学习期限</span><span class="value">{{ course.study_expiry }}个月</span>
<span class="key">退款期限</span><span class="value">{{ course.refund_expiry }}天</span>
{% if course.refund_expiry > 0 %}
<span class="key">退款期限</span><span class="value">{{ course.refund_expiry }}天</span>
{% else %}
<span class="key">退款期限</span><span class="value">不支持</span>
{% endif %}
</p>
{%- endmacro %}

View File

@ -4,7 +4,7 @@
<div class="order-item">
<p>课程名称:<span>{{ course.title }}</span></p>
<p>优惠价格:<span class="price">{{ '¥%0.2f'|format(course.market_price) }}</span>会员价格:<span class="price">{{ '¥%0.2f'|format(course.vip_price) }}</span></p>
<p>学习期限:<span>{{ date('Y-m-d',course.study_expiry_time) }}</span>退款期限:<span>{{ date('Y-m-d',course.refund_expiry_time) }}</span></p>
<p>学习期限:<span>{{ date('Y-m-d H:i:s',course.study_expiry_time) }}</span>退款期限:<span>{% if course.refund_expiry > 0 %}{{ date('Y-m-d H:i:s',course.refund_expiry_time) }}{% else %}不支持{% endif %}</span></p>
</div>
{% elseif order.item_type == 2 %}
{% set courses = order.item_info.courses %}
@ -12,7 +12,7 @@
<div class="order-item">
<p>课程名称:<span>{{ course.title }}</span></p>
<p>优惠价格:<span class="price">{{ '¥%0.2f'|format(course.market_price) }}</span>会员价格:<span class="price">{{ '¥%0.2f'|format(course.vip_price) }}</span></p>
<p>学习期限:<span>{{ date('Y-m-d',course.study_expiry_time) }}</span>退款期限:<span>{{ date('Y-m-d',course.refund_expiry_time) }}</span></p>
<p>学习期限:<span>{{ date('Y-m-d H:i:s',course.study_expiry_time) }}</span>退款期限:<span>{% if course.refund_expiry > 0 %}{{ date('Y-m-d H:i:s',course.refund_expiry_time) }}{% else %}不支持{% endif %}</span></p>
</div>
{% endfor %}
{% elseif order.item_type == 3 %}

View File

@ -13,13 +13,17 @@
<div class="info">
<p><a href="{{ course_url }}" target="_blank">{{ course.title }}</a></p>
<p>
原始价格 <span class="origin-price">{{ '¥%0.2f'|format(course.origin_price) }}</span>
优惠价格 <span class="price">{{ '¥%0.2f'|format(course.market_price) }}</span>
会员价格 <span class="price">{{ '¥%0.2f'|format(course.vip_price) }}</span>
<span class="key">原始价格</span><span class="value origin-price">{{ '¥%0.2f'|format(course.origin_price) }}</span>
<span class="key">优惠价格</span><span class="price">{{ '¥%0.2f'|format(course.market_price) }}</span>
<span class="key">会员价格</span><span class="price">{{ '¥%0.2f'|format(course.vip_price) }}</span>
</p>
<p>
学习期限 <span class="expiry">{{ course.study_expiry }}个月</span>
退款期限 <span class="expiry">{{ course.refund_expiry }}天</span>
<span class="key">学习期限</span><span class="value">{{ course.study_expiry }}个月</span>
{% if course.refund_expiry > 0 %}
<span class="key">退款期限</span><span class="value">{{ course.refund_expiry }}天</span>
{% else %}
<span class="key">退款期限</span><span class="value">不支持</span>
{% endif %}
</p>
</div>
</div>

View File

@ -7,7 +7,7 @@
{% set order_pay_url = url({'for':'home.order.pay'},{'sn':order.sn}) %}
{% set refund_confirm_url = url({'for':'home.refund.confirm'},{'sn':order.sn}) %}
<table class="layui-table order-table" lay-size="lg">
<table class="layui-table order-table">
<tr>
<td colspan="2">
订单金额:<span class="price">{{ '¥%0.2f'|format(order.amount) }}</span>

View File

@ -24,29 +24,31 @@
{% endif %}
{%- endmacro %}
<table class="layui-table order-table" lay-size="lg">
<table class="layui-table order-table">
<tr>
<td>退款项目</td>
<td>退款金额</td>
<td>订单金额</td>
<td>手续费({{ confirm.service_rate }}%</td>
<td>退款金额</td>
</tr>
<tr>
<td>{{ item_info(confirm) }}</td>
<td><span class="price">{{ '¥%0.2f'|format(confirm.refund_amount) }}</span></td>
<td><span class="price">{{ '¥%0.2f'|format(order.amount) }}</span></td>
<td><span class="price">{{ '¥%0.2f'|format(confirm.service_fee) }}</span></td>
<td><span class="price">{{ '¥%0.2f'|format(confirm.refund_amount) }}</span></td>
</tr>
</table>
<br>
{% if confirm.refund_amount > 0 %}
<form class="layui-form layui-form-pane" method="post" action="{{ url({'for':'home.refund.create'}) }}">
<div class="layui-form-item layui-form-text">
<div class="layui-form-item">
<label class="layui-form-label">退款原因</label>
<div class="layui-input-block">
<textarea class="layui-textarea" name="apply_note" lay-verify="required"></textarea>
<input class="layui-input" name="apply_note" lay-verify="required">
</div>
</div>
<div class="layui-form-item center">
<button class="layui-btn" lay-submit="true" lay-filter="go">申请退款</button>
<button class="layui-btn" lay-submit="true" lay-filter="go">提交申请</button>
<input type="hidden" name="order_sn" value="{{ order.sn }}">
</div>
</form>

View File

@ -11,7 +11,7 @@ class AppInfo
protected $link = 'https://koogua.com';
protected $version = '1.2.7';
protected $version = '1.2.8';
public function __get($name)
{

View File

@ -408,6 +408,7 @@ class Course extends Model
public static function refundExpiryOptions()
{
return [
0 => '0天',
7 => '7天',
14 => '14天',
30 => '30天',

View File

@ -22,7 +22,7 @@ class Vip extends Model
public $title = '';
/**
* 期限(
* 期限(
*
* @var int
*/

View File

@ -35,9 +35,7 @@ class LiveNotify extends Service
'action' => $action,
]));
if (!$this->checkSign($sign, $time)) {
return false;
}
if (!$this->checkSign($sign, $time)) return false;
$result = false;
@ -125,7 +123,7 @@ class LiveNotify extends Service
*/
protected function handleRecord()
{
return true;
}
/**
@ -133,7 +131,7 @@ class LiveNotify extends Service
*/
protected function handleSnapshot()
{
return true;
}
/**
@ -141,7 +139,7 @@ class LiveNotify extends Service
*/
protected function handlePorn()
{
return true;
}
protected function handleStreamBeginNotice(ChapterModel $chapter)
@ -153,9 +151,7 @@ class LiveNotify extends Service
$keyName = "live_notify:{$chapter->id}";
if ($cache->get($keyName)) {
return;
}
if ($cache->get($keyName)) return;
$cache->save($keyName, time(), 86400);
@ -163,9 +159,7 @@ class LiveNotify extends Service
$courseUsers = $courseUserRepo->findByCourseId($chapter->course_id);
if ($courseUsers->count() == 0) {
return;
}
if ($courseUsers->count() == 0) return;
$notice = new LiveBeginNotice();
@ -206,13 +200,9 @@ class LiveNotify extends Service
*/
protected function checkSign($sign, $time)
{
if (!$sign || !$time) {
return false;
}
if (!$sign || !$time) return false;
if ($time < time()) {
return false;
}
if ($time < time()) return false;
$notify = $this->getSettings('live.notify');

View File

@ -8,7 +8,7 @@ use App\Repos\Refund as RefundRepo;
use App\Repos\User as UserRepo;
use App\Repos\WeChatSubscribe as WeChatSubscribeRepo;
use App\Services\Logic\Notice\Sms\RefundFinish as SmsRefundFinishNotice;
use App\Services\Logic\Notice\WeChat\OrderFinish as WeChatRefundFinishNotice;
use App\Services\Logic\Notice\WeChat\RefundFinish as WeChatRefundFinishNotice;
use App\Services\Logic\Service as LogicService;
class RefundFinish extends LogicService

View File

@ -70,11 +70,7 @@ class RefundCreate extends Service
$task = new TaskModel();
$itemInfo = [
'refund' => [
'id' => $refund->id,
'order_id' => $refund->order_id,
'trade_id' => $refund->trade_id,
],
'refund' => ['id' => $refund->id],
];
$task->item_id = $refund->id;

View File

@ -207,15 +207,15 @@ class Alipay extends PayService
/**
* 查询交易(扫码生成订单后可执行)
*
* @param string $outTradeNo
* @param string $tradeNo
* @param string $type
* @return Collection|bool
*/
public function find($outTradeNo, $type = 'wap')
public function find($tradeNo, $type = 'wap')
{
try {
$order = ['out_trade_no' => $outTradeNo];
$order = ['out_trade_no' => $tradeNo];
$result = $this->gateway->find($order, $type);
@ -235,14 +235,14 @@ class Alipay extends PayService
/**
* 关闭交易(扫码生成订单后可执行)
*
* @param string $outTradeNo
* @param string $tradeNo
* @return bool
*/
public function close($outTradeNo)
public function close($tradeNo)
{
try {
$response = $this->gateway->close(['out_trade_no' => $outTradeNo]);
$response = $this->gateway->close(['out_trade_no' => $tradeNo]);
$result = $response->code == '10000';
@ -262,14 +262,14 @@ class Alipay extends PayService
/**
* 撤销交易(未生成订单也可执行)
*
* @param string $outTradeNo
* @param string $tradeNo
* @return bool
*/
public function cancel($outTradeNo)
public function cancel($tradeNo)
{
try {
$response = $this->gateway->cancel(['out_trade_no' => $outTradeNo]);
$response = $this->gateway->cancel(['out_trade_no' => $tradeNo]);
$result = $response->code == '10000';

View File

@ -208,15 +208,15 @@ class Wxpay extends PayService
/**
* 查询交易(扫码生成订单后可执行)
*
* @param string $outTradeNo
* @param string $tradeNo
* @param string $type
* @return Collection|bool
*/
public function find($outTradeNo, $type = 'wap')
public function find($tradeNo, $type = 'wap')
{
try {
$order = ['out_trade_no' => $outTradeNo];
$order = ['out_trade_no' => $tradeNo];
$result = $this->gateway->find($order, $type);
@ -236,14 +236,14 @@ class Wxpay extends PayService
/**
* 关闭交易(扫码生成订单后可执行)
*
* @param string $outTradeNo
* @param string $tradeNo
* @return bool
*/
public function close($outTradeNo)
public function close($tradeNo)
{
try {
$response = $this->gateway->close(['out_trade_no' => $outTradeNo]);
$response = $this->gateway->close(['out_trade_no' => $tradeNo]);
$result = $response->result_code == 'SUCCESS';
@ -263,12 +263,12 @@ class Wxpay extends PayService
/**
* 取消交易
*
* @param string $outTradeNo
* @param string $tradeNo
* @return bool
*/
public function cancel($outTradeNo)
public function cancel($tradeNo)
{
return $this->close($outTradeNo);
return $this->close($tradeNo);
}
/**

View File

@ -3,15 +3,23 @@
namespace App\Services;
use App\Models\Order as OrderModel;
use App\Models\Trade as TradeModel;
use App\Repos\Course as CourseRepo;
use App\Repos\CourseUser as CourseUserRepo;
use App\Repos\Order as OrderRepo;
class Refund extends Service
{
public function preview(OrderModel $order)
{
$result = [];
$result = [
'item_type' => 0,
'item_info' => [],
'refund_amount' => 0.00,
'service_fee' => 0.00,
'service_rate' => 5.00,
];
switch ($order->item_type) {
case OrderModel::ITEM_COURSE:
@ -20,6 +28,15 @@ class Refund extends Service
case OrderModel::ITEM_PACKAGE:
$result = $this->previewPackageRefund($order);
break;
case OrderModel::ITEM_REWARD:
$result = $this->previewRewardRefund($order);
break;
case OrderModel::ITEM_VIP:
$result = $this->previewVipRefund($order);
break;
case OrderModel::ITEM_TEST:
$result = $this->previewTestRefund($order);
break;
}
return $result;
@ -27,19 +44,19 @@ class Refund extends Service
protected function previewCourseRefund(OrderModel $order)
{
/**
* @var array $itemInfo
*/
$itemInfo = $order->item_info;
$itemInfo['course']['cover'] = kg_cos_cover_url($itemInfo['course']['cover']);
$serviceFee = $this->getServiceFee($order);
$serviceRate = $this->getServiceRate($order);
$refundPercent = 0.00;
$refundAmount = 0.00;
if ($itemInfo['course']['refund_expiry_time'] > time()) {
$refundPercent = $this->getCourseRefundPercent($order->item_id, $order->owner_id);
$refundAmount = $order->amount * $refundPercent;
$refundAmount = round(($order->amount - $serviceFee) * $refundPercent, 2);
}
$itemInfo['course']['refund_percent'] = $refundPercent;
@ -49,16 +66,18 @@ class Refund extends Service
'item_type' => $order->item_type,
'item_info' => $itemInfo,
'refund_amount' => $refundAmount,
'service_fee' => $serviceFee,
'service_rate' => $serviceRate,
];
}
protected function previewPackageRefund(OrderModel $order)
{
/**
* @var array $itemInfo
*/
$itemInfo = $order->item_info;
$serviceFee = $this->getServiceFee($order);
$serviceRate = $this->getServiceRate($order);
$totalMarketPrice = 0.00;
foreach ($itemInfo['courses'] as $course) {
@ -80,7 +99,7 @@ class Refund extends Service
if ($course['refund_expiry_time'] > time()) {
$pricePercent = round($course['market_price'] / $totalMarketPrice, 4);
$refundPercent = $this->getCourseRefundPercent($course['id'], $order->owner_id);
$refundAmount = round($order->amount * $pricePercent * $refundPercent, 2);
$refundAmount = round(($order->amount - $serviceFee) * $pricePercent * $refundPercent, 2);
$totalRefundAmount += $refundAmount;
}
@ -92,45 +111,97 @@ class Refund extends Service
'item_type' => $order->item_type,
'item_info' => $itemInfo,
'refund_amount' => $totalRefundAmount,
'service_fee' => $serviceFee,
'service_rate' => $serviceRate,
];
}
protected function previewRewardRefund(OrderModel $order)
{
return $this->previewOtherRefund($order);
}
protected function previewVipRefund(OrderModel $order)
{
return $this->previewOtherRefund($order);
}
protected function previewTestRefund(OrderModel $order)
{
return $this->previewOtherRefund($order);
}
protected function previewOtherRefund(OrderModel $order)
{
$serviceFee = $this->getServiceFee($order);
$serviceRate = $this->getServiceRate($order);
$refundAmount = round($order->amount - $serviceFee, 2);
return [
'item_type' => $order->item_type,
'item_info' => $order->item_info,
'refund_amount' => $refundAmount,
'service_fee' => $serviceFee,
'service_rate' => $serviceRate,
];
}
protected function getServiceFee(OrderModel $order)
{
$serviceRate = $this->getServiceRate($order);
$serviceFee = round($order->amount * $serviceRate / 100, 2);
return $serviceFee >= 0.01 ? $serviceFee : 0.00;
}
protected function getServiceRate(OrderModel $order)
{
$orderRepo = new OrderRepo();
$trade = $orderRepo->findLastTrade($order->id);
$alipay = $this->getSettings('pay.alipay');
$wxpay = $this->getSettings('pay.wxpay');
$serviceRate = 5;
switch ($trade->channel) {
case TradeModel::CHANNEL_ALIPAY:
$serviceRate = $alipay['service_rate'] ?: $serviceRate;
break;
case TradeModel::CHANNEL_WXPAY:
$serviceRate = $wxpay['service_rate'] ?: $serviceRate;
break;
}
return $serviceRate;
}
protected function getCourseRefundPercent($courseId, $userId)
{
$courseRepo = new CourseRepo();
$courseLessons = $courseRepo->findLessons($courseId);
if ($courseLessons->count() == 0) {
return 1.00;
}
if ($courseLessons->count() == 0) return 1.00;
$courseUserRepo = new CourseUserRepo();
$courseUser = $courseUserRepo->findCourseUser($courseId, $userId);
if (!$courseUser) {
return 1.00;
}
if (!$courseUser) return 1.00;
$userLearnings = $courseRepo->findUserLearnings($courseId, $userId, $courseUser->plan_id);
if ($userLearnings->count() == 0) {
return 1.00;
}
if ($userLearnings->count() == 0) return 1.00;
/**
* @var array $consumedUserLearnings
*/
$consumedUserLearnings = $userLearnings->filter(function ($item) {
if ($item->consumed == 1) {
return $item;
}
if ($item->consumed == 1) return $item;
});
if (count($consumedUserLearnings) == 0) {
return 1.00;
}
if (count($consumedUserLearnings) == 0) return 1.00;
$courseLessonIds = kg_array_column($courseLessons->toArray(), 'id');
$consumedUserLessonIds = kg_array_column($consumedUserLearnings, 'chapter_id');

View File

@ -5,6 +5,7 @@ namespace App\Validators;
use App\Exceptions\BadRequest as BadRequestException;
use App\Models\Order as OrderModel;
use App\Models\Refund as RefundModel;
use App\Models\Trade as TradeModel;
use App\Repos\Course as CourseRepo;
use App\Repos\Order as OrderRepo;
use App\Repos\Package as PackageRepo;
@ -154,6 +155,12 @@ class Order extends Validator
$orderRepo = new OrderRepo();
$trade = $orderRepo->findLastTrade($order->id);
if ($trade->status != TradeModel::STATUS_FINISHED) {
throw new BadRequestException('order.refund_not_allowed');
}
$refund = $orderRepo->findLastRefund($order->id);
$scopes = [
@ -174,16 +181,8 @@ class Order extends Validator
$order = $orderRepo->findUserLastFinishedOrder($userId, $courseId, $itemType);
if ($order) {
/**
* @var array $itemInfo
*/
$itemInfo = $order->item_info;
if ($itemInfo['course']['study_expiry_time'] > time()) {
throw new BadRequestException('order.has_bought_course');
}
if ($order && $order->item_info['course']['study_expiry_time'] > time()) {
throw new BadRequestException('order.has_bought_course');
}
}

View File

@ -42,9 +42,15 @@ class Refund extends Validator
public function checkAmount($orderAmount, $refundAmount)
{
if ($orderAmount <= 0 || $refundAmount <= 0) {
throw new BadRequestException('refund.invalid_amount');
}
if ($refundAmount > $orderAmount) {
throw new BadRequestException('refund.invalid_amount');
}
return (float)$refundAmount;
}
public function checkStatus($status)

View File

@ -289,6 +289,7 @@ $error['refund.review_note_too_short'] = '审核备注太短少于2个字符
$error['refund.review_note_too_long'] = '审核备注太长多于255个字符';
$error['refund.cancel_not_allowed'] = '当前不允许取消退款';
$error['refund.review_not_allowed'] = '当前不允许审核退款';
$error['refund.invalid_amount'] = '无效的退款金额';
$error['refund.invalid_status'] = '无效的状态类型';
/**

View File

@ -0,0 +1,39 @@
<?php
use Phinx\Migration\AbstractMigration;
final class Data202103051930 extends AbstractMigration
{
public function up()
{
$rows = [
[
'section' => 'pay.alipay',
'item_key' => 'service_rate',
'item_value' => 5,
],
[
'section' => 'pay.wxpay',
'item_key' => 'service_rate',
'item_value' => 5,
],
];
$this->table('kg_setting')->insert($rows)->save();
}
public function down()
{
$this->getQueryBuilder()
->delete('kg_setting')
->where(['section' => 'pay.alipay', 'item_key' => 'service_rate'])
->execute();
$this->getQueryBuilder()
->delete('kg_setting')
->where(['section' => 'pay.wxpay', 'item_key' => 'service_rate'])
->execute();
}
}

View File

@ -34,6 +34,18 @@
list-style: decimal;
}
.red {
color: red;
}
.green {
color: green;
}
.gray {
color: gray;
}
.loading {
padding: 30px;
text-align: center;

View File

@ -489,11 +489,12 @@
height: 118px;
}
.course-meta .info span {
.course-meta .info .key {
margin-right: 5px;
}
.course-meta .info .value {
margin-right: 5px;
color: #666;
}
@ -1074,9 +1075,13 @@
float: left;
}
.cart-course-card span {
.cart-course-card .key {
margin-right: 5px;
}
.cart-course-card .value {
margin-right: 5px;
color: #666;
margin: 0 5px;
}
.cart-course-card .origin-price {
@ -1683,17 +1688,14 @@
}
.order-card {
padding: 10px 0;
padding-bottom: 20px;
margin-bottom: 20px;
background-color: white;
border-bottom: 1px dashed #e6e6e6;
color: #666;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}
.order-card .header {
padding-bottom: 10px;
margin-bottom: 10px;
border-bottom: 1px solid #e6e6e6;
}
.order-card .header span {

View File

@ -13,9 +13,6 @@ $bin = '/usr/local/bin/php';
$scheduler->php($script, $bin, ['--task' => 'deliver', '--action' => 'main'])
->at('*/3 * * * *');
$scheduler->php($script, $bin, ['--task' => 'notice', '--action' => 'main'])
->at('*/3 * * * *');
$scheduler->php($script, $bin, ['--task' => 'vod_event', '--action' => 'main'])
->at('*/5 * * * *');
@ -34,6 +31,9 @@ $scheduler->php($script, $bin, ['--task' => 'server_monitor', '--action' => 'mai
$scheduler->php($script, $bin, ['--task' => 'close_trade', '--action' => 'main'])
->at('*/13 * * * *');
$scheduler->php($script, $bin, ['--task' => 'notice', '--action' => 'main'])
->everyMinute();
$scheduler->php($script, $bin, ['--task' => 'close_order', '--action' => 'main'])
->hourly(3);