1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-06-21 03:17:05 +08:00

疫情期间更新

This commit is contained in:
xiaochong0302 2020-03-25 09:03:53 +08:00
parent 7a4aa88218
commit 1d40766a2a
69 changed files with 1685 additions and 802 deletions

View File

@ -20,7 +20,7 @@ class CleanLogTask extends Task
$this->cleanVodLog(); $this->cleanVodLog();
$this->cleanStorageLog(); $this->cleanStorageLog();
$this->cleanAlipayLog(); $this->cleanAlipayLog();
$this->cleanWechatLog(); $this->cleanWxpayLog();
$this->cleanRefundLog(); $this->cleanRefundLog();
} }
@ -115,9 +115,9 @@ class CleanLogTask extends Task
/** /**
* 清理微信支付服务日志 * 清理微信支付服务日志
*/ */
protected function cleanWechatLog() protected function cleanWxpayLog()
{ {
$this->cleanLog('wechat', 30); $this->cleanLog('wxpay', 30);
} }
/** /**

View File

@ -3,8 +3,8 @@
namespace App\Console\Tasks; namespace App\Console\Tasks;
use App\Models\Trade as TradeModel; use App\Models\Trade as TradeModel;
use App\Services\Alipay as AlipayService; use App\Services\Payment\Alipay as AlipayService;
use App\Services\Wechat as WechatService; use App\Services\Payment\Wxpay as WxpayService;
use Phalcon\Cli\Task; use Phalcon\Cli\Task;
use Phalcon\Mvc\Model\Resultset; use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface; use Phalcon\Mvc\Model\ResultsetInterface;
@ -23,8 +23,8 @@ class CloseTradeTask extends Task
foreach ($trades as $trade) { foreach ($trades as $trade) {
if ($trade->channel == TradeModel::CHANNEL_ALIPAY) { if ($trade->channel == TradeModel::CHANNEL_ALIPAY) {
$this->closeAlipayTrade($trade); $this->closeAlipayTrade($trade);
} elseif ($trade->channel == TradeModel::CHANNEL_WECHAT) { } elseif ($trade->channel == TradeModel::CHANNEL_WXPAY) {
$this->closeWechatTrade($trade); $this->closeWxpayTrade($trade);
} }
} }
} }
@ -36,48 +36,38 @@ class CloseTradeTask extends Task
*/ */
protected function closeAlipayTrade($trade) protected function closeAlipayTrade($trade)
{ {
$service = new AlipayService(); $alipay = new AlipayService();
$alyOrder = $service->findOrder($trade->sn); $success = $alipay->close($trade->sn);
if ($alyOrder) {
if ($alyOrder->trade_status == 'WAIT_BUYER_PAY') {
$service->closeOrder($trade->sn);
}
}
if ($success) {
$trade->status = TradeModel::STATUS_CLOSED; $trade->status = TradeModel::STATUS_CLOSED;
$trade->update(); $trade->update();
} }
}
/** /**
* 关闭微信交易 * 关闭微信交易
* *
* @param TradeModel $trade * @param TradeModel $trade
*/ */
protected function closeWechatTrade($trade) protected function closeWxpayTrade($trade)
{ {
$service = new WechatService(); $wxpay = new WxpayService();
$wxOrder = $service->findOrder($trade->sn); $success = $wxpay->close($trade->sn);
if ($wxOrder) {
if ($wxOrder->trade_state == 'NOTPAY') {
$service->closeOrder($trade->sn);
}
}
if ($success) {
$trade->status = TradeModel::STATUS_CLOSED; $trade->status = TradeModel::STATUS_CLOSED;
$trade->update(); $trade->update();
} }
}
/** /**
* 查找待关闭交易 * 查找待关闭交易
* *
* @param int $limit * @param int $limit
* @return Resultset|ResultsetInterface * @return ResultsetInterface|Resultset|TradeModel[]
*/ */
protected function findTrades($limit = 5) protected function findTrades($limit = 5)
{ {

View File

@ -41,11 +41,13 @@ class LearningTask extends Task
if (!$requestIds) return; if (!$requestIds) return;
foreach ($requestIds as $requestId) { foreach ($requestIds as $requestId) {
$itemKey = $syncer->getItemKey($requestId);
$this->handleLearning($itemKey);
}
$this->redis->sRem($syncKey, ...$requestIds); $itemKey = $syncer->getItemKey($requestId);
$this->handleLearning($itemKey);
$this->redis->sRem($syncKey, $requestId);
}
} }
/** /**

View File

@ -0,0 +1,70 @@
<?php
namespace App\Console\Tasks;
use App\Library\Cache\Backend\Redis as RedisCache;
use App\Services\Smser\Live as LiveSmser;
use Phalcon\Cli\Task;
class LiveNoticeConsumerTask extends Task
{
/**
* @var RedisCache
*/
protected $cache;
/**
* @var \Redis
*/
protected $redis;
public function mainAction()
{
$hour = date('h');
/**
* 限定合理的时间范围
*/
if ($hour < 7 || $hour > 23) {
return;
}
$this->cache = $this->getDI()->get('cache');
$this->redis = $this->cache->getRedis();
$providerTask = new LiveNoticeProviderTask();
$cacheKey = $providerTask->getCacheKey();
$members = $this->redis->sMembers($cacheKey);
if (!$members) return;
$smser = new LiveSmser();
$now = time();
foreach ($members as $member) {
list($chapterId, $userId, $startTime) = explode(':', $member);
$remove = false;
if ($now - $startTime < 3600) {
$smser->handle($chapterId, $userId, $startTime);
$remove = true;
}
if ($now > $startTime) {
$remove = true;
}
if ($remove) {
$this->redis->sRem($cacheKey, $member);
}
}
}
}

View File

@ -0,0 +1,96 @@
<?php
namespace App\Console\Tasks;
use App\Library\Cache\Backend\Redis as RedisCache;
use App\Models\ChapterLive as ChapterLiveModel;
use App\Models\CourseUser as CourseUserModel;
use Phalcon\Cli\Task;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class LiveNoticeProviderTask extends Task
{
/**
* @var RedisCache
*/
protected $cache;
/**
* @var \Redis
*/
protected $redis;
public function mainAction()
{
$this->cache = $this->getDI()->get('cache');
$this->redis = $this->cache->getRedis();
$tasks = $this->findTasks();
if ($tasks->count() == 0) {
return;
}
$values = [];
foreach ($tasks as $task) {
$items = [$task->chapter_id, $task->user_id, $task->start_time];
$values[] = implode(':', $items);
}
$key = $this->getCacheKey();
$lifetime = $this->getLifetime();
$this->redis->sAdd($key, ...$values);
$this->redis->expire($key, $lifetime);
}
/**
* @return ResultsetInterface|Resultset
*/
protected function findTasks()
{
$beginTime = strtotime('today');
$endTime = strtotime('tomorrow');
/**
* 过滤付费和导入用户,减少发送量
*/
$sourceTypes = [
CourseUserModel::SOURCE_CHARGE,
CourseUserModel::SOURCE_IMPORT,
];
$rows = $this->modelsManager->createBuilder()
->columns(['cu.course_id', 'cu.user_id', 'cl.chapter_id', 'cl.start_time'])
->addFrom(ChapterLiveModel::class, 'cl')
->join(CourseUserModel::class, 'cl.course_id = cu.course_id', 'cu')
->inWhere('cu.source_type', $sourceTypes)
->betweenWhere('start_time', $beginTime, $endTime)
->getQuery()->execute();
return $rows;
}
public function getLifetime()
{
$tomorrow = strtotime('tomorrow');
$lifetime = $tomorrow - time();
return $lifetime;
}
public function getCacheKey()
{
return 'live_notice';
}
}

View File

@ -4,16 +4,20 @@ namespace App\Console\Tasks;
use App\Models\CourseUser as CourseUserModel; use App\Models\CourseUser as CourseUserModel;
use App\Models\Order as OrderModel; use App\Models\Order as OrderModel;
use App\Models\Refund as RefundModel;
use App\Models\Task as TaskModel; use App\Models\Task as TaskModel;
use App\Models\Trade as TradeModel;
use App\Repos\Course as CourseRepo; use App\Repos\Course as CourseRepo;
use App\Repos\CourseUser as CourseUserRepo; use App\Repos\CourseUser as CourseUserRepo;
use App\Repos\Order as OrderRepo; use App\Repos\Order as OrderRepo;
use App\Repos\User as UserRepo; use App\Repos\User as UserRepo;
use App\Services\Smser\Order as OrderSmser;
use Phalcon\Cli\Task; use Phalcon\Cli\Task;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Resultset; use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface; use Phalcon\Mvc\Model\ResultsetInterface;
class ProcessOrderTask extends Task class OrderTask extends Task
{ {
const TRY_COUNT = 3; const TRY_COUNT = 3;
@ -30,8 +34,6 @@ class ProcessOrderTask extends Task
foreach ($tasks as $task) { foreach ($tasks as $task) {
try {
/** /**
* @var array $itemInfo * @var array $itemInfo
*/ */
@ -39,6 +41,10 @@ class ProcessOrderTask extends Task
$order = $orderRepo->findById($itemInfo['order']['id']); $order = $orderRepo->findById($itemInfo['order']['id']);
if (!$order) continue;
try {
switch ($order->item_type) { switch ($order->item_type) {
case OrderModel::ITEM_COURSE: case OrderModel::ITEM_COURSE:
$this->handleCourseOrder($order); $this->handleCourseOrder($order);
@ -51,6 +57,12 @@ class ProcessOrderTask extends Task
break; break;
} }
$task->status = TaskModel::STATUS_FINISHED;
$task->update();
$this->handleOrderNotice($order);
} catch (\Exception $e) { } catch (\Exception $e) {
$task->try_count += 1; $task->try_count += 1;
@ -62,6 +74,13 @@ class ProcessOrderTask extends Task
$task->update(); $task->update();
} }
/**
* 任务失败,申请退款
*/
if ($task->status == TaskModel::STATUS_FAILED) {
$this->handleOrderRefund($order);
}
} }
} }
@ -143,6 +162,38 @@ class ProcessOrderTask extends Task
} }
} }
/**
* @param OrderModel $order
*/
protected function handleOrderNotice(OrderModel $order)
{
$smser = new OrderSmser();
$smser->handle($order);
}
/**
* @param OrderModel $order
*/
protected function handleOrderRefund(OrderModel $order)
{
$trade = $this->findFinishedTrade($order->id);
if (!$trade) return;
$refund = new RefundModel();
$refund->subject = $order->subject;
$refund->amount = $order->amount;
$refund->apply_note = '开通失败,自动退款';
$refund->review_note = '自动操作';
$refund->user_id = $order->user_id;
$refund->order_id = $order->id;
$refund->trade_id = $trade->id;
$refund->create();
}
/** /**
* @param int $courseId * @param int $courseId
* @param int $userId * @param int $userId
@ -166,13 +217,30 @@ class ProcessOrderTask extends Task
} }
} }
/**
* @param $orderId
* @return Model|TradeModel
*/
protected function findFinishedTrade($orderId)
{
$status = TradeModel::STATUS_FINISHED;
$result = TradeModel::findFirst([
'conditions' => ['order_id = :order_id: AND status = :status:'],
'bind' => ['order_id' => $orderId, 'status' => $status],
'order' => 'id DESC',
]);
return $result;
}
/** /**
* @param int $limit * @param int $limit
* @return ResultsetInterface|Resultset|TaskModel[] * @return ResultsetInterface|Resultset|TaskModel[]
*/ */
protected function findTasks($limit = 100) protected function findTasks($limit = 100)
{ {
$itemType = TaskModel::TYPE_PROCESS_ORDER; $itemType = TaskModel::TYPE_ORDER;
$status = TaskModel::STATUS_PENDING; $status = TaskModel::STATUS_PENDING;
$tryCount = self::TRY_COUNT; $tryCount = self::TRY_COUNT;

View File

@ -11,8 +11,9 @@ use App\Repos\Order as OrderRepo;
use App\Repos\Refund as RefundRepo; use App\Repos\Refund as RefundRepo;
use App\Repos\Trade as TradeRepo; use App\Repos\Trade as TradeRepo;
use App\Repos\User as UserRepo; use App\Repos\User as UserRepo;
use App\Services\Alipay as AlipayService; use App\Services\Payment\Alipay as AlipayService;
use App\Services\Wechat as WechatService; use App\Services\Payment\Wxpay as WxpayService;
use App\Services\Smser\Refund as RefundSmser;
use Phalcon\Mvc\Model\Resultset; use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface; use Phalcon\Mvc\Model\ResultsetInterface;
@ -49,6 +50,10 @@ class RefundTask extends Task
$trade = $tradeRepo->findById($itemInfo['refund']['trade_id']); $trade = $tradeRepo->findById($itemInfo['refund']['trade_id']);
$order = $orderRepo->findById($itemInfo['refund']['order_id']); $order = $orderRepo->findById($itemInfo['refund']['order_id']);
if (!$refund || !$trade || !$order) {
continue;
}
try { try {
$this->db->begin(); $this->db->begin();
@ -83,6 +88,8 @@ class RefundTask extends Task
$this->db->commit(); $this->db->commit();
$this->handleRefundNotice($refund);
} catch (\Exception $e) { } catch (\Exception $e) {
$this->db->rollback(); $this->db->rollback();
@ -92,8 +99,6 @@ class RefundTask extends Task
if ($task->try_count > self::TRY_COUNT) { if ($task->try_count > self::TRY_COUNT) {
$task->status = TaskModel::STATUS_FAILED; $task->status = TaskModel::STATUS_FAILED;
$refund->status = RefundModel::STATUS_FAILED;
$refund->update();
} }
$task->update(); $task->update();
@ -103,6 +108,11 @@ class RefundTask extends Task
'task' => $task->toArray(), 'task' => $task->toArray(),
])); ]));
} }
if ($task->status == TaskModel::STATUS_FAILED) {
$refund->status = RefundModel::STATUS_FAILED;
$refund->update();
}
} }
} }
@ -120,22 +130,13 @@ class RefundTask extends Task
$alipay = new AlipayService(); $alipay = new AlipayService();
$response = $alipay->refundOrder([ $response = $alipay->refund($refund);
'out_trade_no' => $trade->sn,
'out_request_no' => $refund->sn,
'refund_amount' => $refund->amount,
]);
} elseif ($trade->channel == TradeModel::CHANNEL_WECHAT) { } elseif ($trade->channel == TradeModel::CHANNEL_WXPAY) {
$wechat = new WechatService(); $wxpay = new WxpayService();
$response = $wechat->refundOrder([ $response = $wxpay->refund($refund);
'out_trade_no' => $trade->sn,
'out_refund_no' => $refund->sn,
'total_fee' => 100 * $trade->amount,
'refund_fee' => 100 * $refund->amount,
]);
} }
if (!$response) { if (!$response) {
@ -252,6 +253,16 @@ class RefundTask extends Task
} }
/**
* @param RefundModel $refund
*/
protected function handleRefundNotice(RefundModel $refund)
{
$smser = new RefundSmser();
$smser->handle($refund);
}
/** /**
* @param int $limit * @param int $limit
* @return ResultsetInterface|Resultset|TaskModel[] * @return ResultsetInterface|Resultset|TaskModel[]

View File

@ -28,7 +28,7 @@ class RevokeVipTask extends Task
* 查找待解锁用户 * 查找待解锁用户
* *
* @param int $limit * @param int $limit
* @return UserModel[]|Resultset|ResultsetInterface * @return ResultsetInterface|Resultset|UserModel[]
*/ */
protected function findUsers($limit = 1000) protected function findUsers($limit = 1000)
{ {

View File

@ -22,13 +22,17 @@ class VodEventTask extends Task
$count = 0; $count = 0;
foreach ($events as $event) { foreach ($events as $event) {
$handles[] = $event['EventHandle']; $handles[] = $event['EventHandle'];
if ($event['EventType'] == 'NewFileUpload') { if ($event['EventType'] == 'NewFileUpload') {
$this->handleNewFileUploadEvent($event); $this->handleNewFileUploadEvent($event);
} elseif ($event['EventType'] == 'ProcedureStateChanged') { } elseif ($event['EventType'] == 'ProcedureStateChanged') {
$this->handleProcedureStateChangedEvent($event); $this->handleProcedureStateChangedEvent($event);
} }
$count++; $count++;
if ($count >= 12) { if ($count >= 12) {
break; break;
} }
@ -61,8 +65,10 @@ class VodEventTask extends Task
* @var array $attrs * @var array $attrs
*/ */
$attrs = $chapter->attrs; $attrs = $chapter->attrs;
$attrs['file_status'] = ChapterModel::FS_TRANSLATING; $attrs['file_status'] = ChapterModel::FS_TRANSLATING;
$attrs['duration'] = (int)$duration; $attrs['duration'] = (int)$duration;
$chapter->update(['attrs' => $attrs]); $chapter->update(['attrs' => $attrs]);
$this->updateVodAttrs($chapter->course_id); $this->updateVodAttrs($chapter->course_id);
@ -71,6 +77,7 @@ class VodEventTask extends Task
protected function handleProcedureStateChangedEvent($event) protected function handleProcedureStateChangedEvent($event)
{ {
$fileId = $event['ProcedureStateChangeEvent']['FileId']; $fileId = $event['ProcedureStateChangeEvent']['FileId'];
$processResult = $event['ProcedureStateChangeEvent']['MediaProcessResultSet']; $processResult = $event['ProcedureStateChangeEvent']['MediaProcessResultSet'];
$chapterRepo = new ChapterRepo(); $chapterRepo = new ChapterRepo();
@ -110,7 +117,9 @@ class VodEventTask extends Task
* @var array $attrs * @var array $attrs
*/ */
$attrs = $chapter->attrs; $attrs = $chapter->attrs;
$attrs['file_status'] = $fileStatus; $attrs['file_status'] = $fileStatus;
$chapter->update(['attrs' => $attrs]); $chapter->update(['attrs' => $attrs]);
} }

View File

@ -148,6 +148,7 @@ class ConfigController extends Controller
if ($this->request->isPost()) { if ($this->request->isPost()) {
$section = $this->request->getPost('section'); $section = $this->request->getPost('section');
$data = $this->request->getPost(); $data = $this->request->getPost();
$configService->updateSectionConfig($section, $data); $configService->updateSectionConfig($section, $data);
@ -157,10 +158,10 @@ class ConfigController extends Controller
} else { } else {
$alipay = $configService->getSectionConfig('payment.alipay'); $alipay = $configService->getSectionConfig('payment.alipay');
$wechat = $configService->getSectionConfig('payment.wechat'); $wxpay = $configService->getSectionConfig('payment.wxpay');
$this->view->setVar('alipay', $alipay); $this->view->setVar('alipay', $alipay);
$this->view->setVar('wechat', $wechat); $this->view->setVar('wxpay', $wxpay);
} }
} }

View File

@ -4,10 +4,11 @@ namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\AlipayTest as AlipayTestService; use App\Http\Admin\Services\AlipayTest as AlipayTestService;
use App\Http\Admin\Services\Config as ConfigService; use App\Http\Admin\Services\Config as ConfigService;
use App\Http\Admin\Services\WxpayTest as WxpayTestService;
use App\Services\Captcha as CaptchaService; use App\Services\Captcha as CaptchaService;
use App\Services\Live as LiveService; use App\Services\Live as LiveService;
use App\Services\Mailer as MailerService; use App\Services\Mailer\Test as TestMailerService;
use App\Services\Smser as SmserService; use App\Services\Smser\Test as TestSmserService;
use App\Services\Storage as StorageService; use App\Services\Storage as StorageService;
use App\Services\Vod as VodService; use App\Services\Vod as VodService;
use Phalcon\Mvc\View; use Phalcon\Mvc\View;
@ -21,7 +22,7 @@ class TestController extends Controller
/** /**
* @Post("/storage", name="admin.test.storage") * @Post("/storage", name="admin.test.storage")
*/ */
public function storageTestAction() public function storageAction()
{ {
$storageService = new StorageService(); $storageService = new StorageService();
@ -37,7 +38,7 @@ class TestController extends Controller
/** /**
* @Post("/vod", name="admin.test.vod") * @Post("/vod", name="admin.test.vod")
*/ */
public function vodTestAction() public function vodAction()
{ {
$vodService = new VodService(); $vodService = new VodService();
@ -53,12 +54,17 @@ class TestController extends Controller
/** /**
* @Get("/live/push", name="admin.test.live.push") * @Get("/live/push", name="admin.test.live.push")
*/ */
public function livePushTestAction() public function livePushAction()
{ {
$liveService = new LiveService(); $liveService = new LiveService();
$pushUrl = $liveService->getPushUrl('test'); $pushUrl = $liveService->getPushUrl('test');
$codeUrl = $this->url->get(
['for' => 'home.qr.img'],
['text' => urlencode($pushUrl)]
);
$obs = new \stdClass(); $obs = new \stdClass();
$position = strrpos($pushUrl, '/'); $position = strrpos($pushUrl, '/');
@ -66,14 +72,15 @@ class TestController extends Controller
$obs->stream_code = substr($pushUrl, $position + 1); $obs->stream_code = substr($pushUrl, $position + 1);
$this->view->pick('config/live_push_test'); $this->view->pick('config/live_push_test');
$this->view->setVar('push_url', $pushUrl);
$this->view->setVar('code_url', $codeUrl);
$this->view->setVar('obs', $obs); $this->view->setVar('obs', $obs);
} }
/** /**
* @Get("/live/pull", name="admin.test.live.pull") * @Get("/live/pull", name="admin.test.live.pull")
*/ */
public function livePullTestAction() public function livePullAction()
{ {
$liveService = new LiveService(); $liveService = new LiveService();
@ -81,7 +88,9 @@ class TestController extends Controller
$flvPullUrls = $liveService->getPullUrls('test', 'flv'); $flvPullUrls = $liveService->getPullUrls('test', 'flv');
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW); $this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->pick('public/live_player'); $this->view->pick('public/live_player');
$this->view->setVar('m3u8_pull_urls', $m3u8PullUrls); $this->view->setVar('m3u8_pull_urls', $m3u8PullUrls);
$this->view->setVar('flv_pull_urls', $flvPullUrls); $this->view->setVar('flv_pull_urls', $flvPullUrls);
} }
@ -89,13 +98,13 @@ class TestController extends Controller
/** /**
* @Post("/smser", name="admin.test.smser") * @Post("/smser", name="admin.test.smser")
*/ */
public function smserTestAction() public function smserAction()
{ {
$phone = $this->request->getPost('phone'); $phone = $this->request->getPost('phone');
$smserService = new SmserService(); $smserService = new TestSmserService();
$response = $smserService->sendTestMessage($phone); $response = $smserService->handle($phone);
if ($response) { if ($response) {
return $this->ajaxSuccess(['msg' => '发送短信成功,请到收件箱确认']); return $this->ajaxSuccess(['msg' => '发送短信成功,请到收件箱确认']);
@ -107,13 +116,13 @@ class TestController extends Controller
/** /**
* @Post("/mailer", name="admin.test.mailer") * @Post("/mailer", name="admin.test.mailer")
*/ */
public function mailerTestAction() public function mailerAction()
{ {
$email = $this->request->getPost('email'); $email = $this->request->getPost('email');
$mailerService = new MailerService(); $mailerService = new TestMailerService();
$result = $mailerService->sendTestMail($email); $result = $mailerService->handle($email);
if ($result) { if ($result) {
return $this->ajaxSuccess(['msg' => '发送邮件成功,请到收件箱确认']); return $this->ajaxSuccess(['msg' => '发送邮件成功,请到收件箱确认']);
@ -125,7 +134,7 @@ class TestController extends Controller
/** /**
* @Post("/captcha", name="admin.test.captcha") * @Post("/captcha", name="admin.test.captcha")
*/ */
public function captchaTestAction() public function captchaAction()
{ {
$post = $this->request->getPost(); $post = $this->request->getPost();
@ -149,37 +158,47 @@ class TestController extends Controller
/** /**
* @Get("/alipay", name="admin.test.alipay") * @Get("/alipay", name="admin.test.alipay")
*/ */
public function alipayTestAction() public function alipayAction()
{ {
$alipayTestService = new AlipayTestService(); $alipayTestService = new AlipayTestService();
$this->db->begin(); $this->db->begin();
$order = $alipayTestService->createTestOrder(); $order = $alipayTestService->createOrder();
$trade = $alipayTestService->createTestTrade($order); $trade = $alipayTestService->createTrade($order);
$qrcode = $alipayTestService->getTestQrCode($trade); $code = $alipayTestService->scan($trade);
if ($order->id > 0 && $trade->id > 0 && $qrcode) { if ($order && $trade && $code) {
$this->db->commit(); $this->db->commit();
} else { } else {
$this->db->rollback(); $this->db->rollback();
} }
$codeUrl = null;
if ($code) {
$codeUrl = $this->url->get(
['for' => 'home.qr.img'],
['text' => urlencode($code)]
);
}
$this->view->pick('config/payment_alipay_test'); $this->view->pick('config/payment_alipay_test');
$this->view->setVar('trade', $trade);
$this->view->setVar('qrcode', $qrcode); $this->view->setVar('trade_sn', $trade->sn);
$this->view->setVar('code_url', $codeUrl);
} }
/** /**
* @Post("/alipay/status", name="admin.test.alipay.status") * @Post("/alipay/status", name="admin.test.alipay.status")
*/ */
public function alipayTestStatusAction() public function alipayStatusAction()
{ {
$sn = $this->request->getPost('sn'); $tradeSn = $this->request->getPost('trade_sn');
$alipayTestService = new AlipayTestService(); $alipayTestService = new AlipayTestService();
$status = $alipayTestService->getTestStatus($sn); $status = $alipayTestService->status($tradeSn);
return $this->ajaxSuccess(['status' => $status]); return $this->ajaxSuccess(['status' => $status]);
} }
@ -187,13 +206,66 @@ class TestController extends Controller
/** /**
* @Post("/alipay/cancel", name="admin.test.alipay.cancel") * @Post("/alipay/cancel", name="admin.test.alipay.cancel")
*/ */
public function alipayTestCancelAction() public function alipayCancelAction()
{ {
$sn = $this->request->getPost('sn'); $tradeSn = $this->request->getPost('trade_sn');
$alipayTestService = new AlipayTestService(); $alipayTestService = new AlipayTestService();
$alipayTestService->cancelTestOrder($sn); $alipayTestService->cancel($tradeSn);
return $this->ajaxSuccess(['msg' => '取消订单成功']);
}
/**
* @Get("/wxpay", name="admin.test.wxpay")
*/
public function wxpayAction()
{
$wxpayTestService = new WxpayTestService();
$this->db->begin();
$order = $wxpayTestService->createOrder();
$trade = $wxpayTestService->createTrade($order);
$codeUrl = $wxpayTestService->scan($trade);
if ($order && $trade && $codeUrl) {
$this->db->commit();
} else {
$this->db->rollback();
}
$this->view->pick('config/payment_wxpay_test');
$this->view->setVar('trade_sn', $trade->sn);
$this->view->setVar('code_url', $codeUrl);
}
/**
* @Post("/wxpay/status", name="admin.test.wxpay.status")
*/
public function wxpayStatusAction()
{
$tradeSn = $this->request->getPost('trade_sn');
$wxpayTestService = new WxpayTestService();
$status = $wxpayTestService->status($tradeSn);
return $this->ajaxSuccess(['status' => $status]);
}
/**
* @Post("/wxpay/cancel", name="admin.test.wxpay.cancel")
*/
public function wxpayCancelAction()
{
$tradeSn = $this->request->getPost('trade_sn');
$wxpayTestService = new WxpayTestService();
$wxpayTestService->cancel($tradeSn);
return $this->ajaxSuccess(['msg' => '取消订单成功']); return $this->ajaxSuccess(['msg' => '取消订单成功']);
} }

View File

@ -6,44 +6,38 @@ use App\Models\Order as OrderModel;
use App\Models\Trade as TradeModel; use App\Models\Trade as TradeModel;
use App\Repos\Order as OrderRepo; use App\Repos\Order as OrderRepo;
use App\Repos\Trade as TradeRepo; use App\Repos\Trade as TradeRepo;
use App\Services\Alipay as AlipayService; use App\Services\Payment\Alipay as AlipayService;
class AlipayTest extends PaymentTest class AlipayTest extends PaymentTest
{ {
/** protected $channel = TradeModel::CHANNEL_ALIPAY;
* 获取测试二维码
*
* @param TradeModel $trade
* @return mixed
*/
public function getTestQrCode($trade)
{
$outOrder = [
'out_trade_no' => $trade->sn,
'total_amount' => $trade->amount,
'subject' => $trade->subject,
];
public function scan(TradeModel $trade)
{
$alipayService = new AlipayService(); $alipayService = new AlipayService();
$qrcode = $alipayService->getQrCode($outOrder); $qrcode = $alipayService->scan($trade);
$result = $qrcode ?: false; $result = $qrcode ?: false;
return $result; return $result;
} }
/** public function status($tradeNo)
* 取消测试订单 {
* $alipayService = new AlipayService();
* @param string $sn
*/ $result = $alipayService->status($tradeNo);
public function cancelTestOrder($sn)
return $result;
}
public function cancel($tradeNo)
{ {
$tradeRepo = new TradeRepo(); $tradeRepo = new TradeRepo();
$trade = $tradeRepo->findBySn($sn); $trade = $tradeRepo->findBySn($tradeNo);
$orderRepo = new OrderRepo(); $orderRepo = new OrderRepo();
@ -51,11 +45,13 @@ class AlipayTest extends PaymentTest
$alipayService = new AlipayService(); $alipayService = new AlipayService();
$response = $alipayService->cancelOrder($trade->sn); $response = $alipayService->cancel($trade->sn);
if ($response) { if ($response) {
$trade->status = TradeModel::STATUS_CLOSED; $trade->status = TradeModel::STATUS_CLOSED;
$trade->update(); $trade->update();
if ($order->status != OrderModel::STATUS_PENDING) { if ($order->status != OrderModel::STATUS_PENDING) {
$order->status = OrderModel::STATUS_PENDING; $order->status = OrderModel::STATUS_PENDING;
$order->update(); $order->update();

View File

@ -4,18 +4,25 @@ namespace App\Http\Admin\Services;
use App\Models\Order as OrderModel; use App\Models\Order as OrderModel;
use App\Models\Trade as TradeModel; use App\Models\Trade as TradeModel;
use App\Repos\Trade as TradeRepo;
abstract class PaymentTest extends Service abstract class PaymentTest extends Service
{ {
/** /**
* 创建测试订单 * @var string 支付平台
*/
protected $channel;
/**
* 创建订单
* *
* @return OrderModel * @return OrderModel
*/ */
public function createTestOrder() public function createOrder()
{ {
/**
* @var object $authUser
*/
$authUser = $this->getDI()->get('auth')->getAuthInfo(); $authUser = $this->getDI()->get('auth')->getAuthInfo();
$order = new OrderModel(); $order = new OrderModel();
@ -36,7 +43,7 @@ abstract class PaymentTest extends Service
* @param OrderModel $order * @param OrderModel $order
* @return TradeModel $trade * @return TradeModel $trade
*/ */
public function createTestTrade($order) public function createTrade(OrderModel $order)
{ {
$trade = new TradeModel(); $trade = new TradeModel();
@ -44,7 +51,7 @@ abstract class PaymentTest extends Service
$trade->order_id = $order->id; $trade->order_id = $order->id;
$trade->subject = $order->subject; $trade->subject = $order->subject;
$trade->amount = $order->amount; $trade->amount = $order->amount;
$trade->channel = TradeModel::CHANNEL_ALIPAY; $trade->channel = $this->channel;
$trade->create(); $trade->create();
@ -52,33 +59,27 @@ abstract class PaymentTest extends Service
} }
/** /**
* 获取订单状态 * 交易状态
* *
* @param string $sn * @param string $tradeNo
* @return string * @return string
*/ */
public function getTestStatus($sn) abstract public function status($tradeNo);
{
$tradeRepo = new TradeRepo();
$trade = $tradeRepo->findBySn($sn);
return $trade->status;
}
/** /**
* 获取测试二维码 * 扫码下单
* *
* @param TradeModel $trade * @param TradeModel $trade
* @return mixed * @return string|bool
*/ */
abstract public function getTestQrCode($trade); abstract public function scan(TradeModel $trade);
/** /**
* 取消测试订单 * 取消交易
* *
* @param string $sn * @param string $tradeNo
* @return bool
*/ */
abstract public function cancelTestOrder($sn); abstract public function cancel($tradeNo);
} }

View File

@ -94,7 +94,7 @@ class Refund extends Service
$data = []; $data = [];
$validator->checkIfAllowReview($refund); $validator->checkReviewAction($refund);
$data['status'] = $validator->checkReviewStatus($post['status']); $data['status'] = $validator->checkReviewStatus($post['status']);
$data['review_note'] = $validator->checkReviewNote($post['review_note']); $data['review_note'] = $validator->checkReviewNote($post['review_note']);

View File

@ -95,6 +95,7 @@ class Trade extends Service
$validator->checkIfAllowClose($trade); $validator->checkIfAllowClose($trade);
$trade->status = TradeModel::STATUS_CLOSED; $trade->status = TradeModel::STATUS_CLOSED;
$trade->update(); $trade->update();
return $trade; return $trade;
@ -114,7 +115,7 @@ class Trade extends Service
$refund->amount = $trade->amount; $refund->amount = $trade->amount;
$refund->user_id = $trade->user_id; $refund->user_id = $trade->user_id;
$refund->order_id = $trade->order_id; $refund->order_id = $trade->order_id;
$refund->trade_id = $trade->sn; $refund->trade_id = $trade->id;
$refund->apply_note = '后台人工申请退款'; $refund->apply_note = '后台人工申请退款';
$refund->create(); $refund->create();

View File

@ -1,66 +0,0 @@
<?php
namespace App\Http\Admin\Services;
use App\Models\Order as OrderModel;
use App\Models\Trade as TradeModel;
use App\Repos\Order as OrderRepo;
use App\Repos\Trade as TradeRepo;
use App\Services\Wechat as WechatService;
class WechatTest extends PaymentTest
{
/**
* 获取测试二维码
*
* @param TradeModel $trade
* @return mixed
*/
public function getTestQrcode($trade)
{
$outOrder = [
'out_trade_no' => $trade->sn,
'total_fee' => 100 * $trade->amount,
'body' => $trade->subject,
];
$wechatService = new WechatService();
$qrcode = $wechatService->getQrCode($outOrder);
$result = $qrcode ?: false;
return $result;
}
/**
* 取消测试订单
*
* @param string $sn
*/
public function cancelTestOrder($sn)
{
$tradeRepo = new TradeRepo();
$trade = $tradeRepo->findBySn($sn);
$orderRepo = new OrderRepo();
$order = $orderRepo->findById($trade->order_id);
$wechatService = new WechatService();
$response = $wechatService->closeOrder($trade->sn);
if ($response) {
$trade->status = TradeModel::STATUS_CLOSED;
$trade->update();
if ($order->status != OrderModel::STATUS_PENDING) {
$order->status = OrderModel::STATUS_PENDING;
$order->update();
}
}
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Http\Admin\Services;
use App\Models\Trade as TradeModel;
use App\Services\Payment\Wxpay as WxpayService;
class WxpayTest extends PaymentTest
{
protected $channel = TradeModel::CHANNEL_WXPAY;
public function scan(TradeModel $trade)
{
$wxpayService = new WxpayService();
$qrcode = $wxpayService->scan($trade);
$result = $qrcode ?: false;
return $result;
}
public function status($tradeNo)
{
$wxpayService = new WxpayService();
$result = $wxpayService->status($tradeNo);
return $result;
}
public function cancel($tradeNo)
{
$wxpayService = new WxpayService();
$response = $wxpayService->close($tradeNo);
return $response;
}
}

View File

@ -3,7 +3,7 @@
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-form-label"></label> <label class="layui-form-label"></label>
<div class="layui-input-inline" style="width:150px;margin-left:110px;"> <div class="layui-input-inline" style="width:150px;margin-left:110px;">
<img src="/qrcode/img?text={{ push_url|url_encode }}"> <img class="kg-qrcode" src="{{ code_url }}" alt="二维码图片">
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
<div class="layui-tab layui-tab-brief"> <div class="layui-tab layui-tab-brief">
<ul class="layui-tab-title kg-tab-title"> <ul class="layui-tab-title kg-tab-title">
<li class="layui-this">支付宝支付</li> <li class="layui-this">支付宝</li>
<li>微信支付</li> <li>微信支付</li>
</ul> </ul>
<div class="layui-tab-content"> <div class="layui-tab-content">
@ -8,7 +8,7 @@
{{ partial('config/payment_alipay') }} {{ partial('config/payment_alipay') }}
</div> </div>
<div class="layui-tab-item"> <div class="layui-tab-item">
{{ partial('config/payment_wechat') }} {{ partial('config/payment_wxpay') }}
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,33 +1,32 @@
<div class="kg-qrcode-block"> <div class="kg-qrcode-block">
{% if qrcode %} {% if code_url %}
<div id="qrcode" class="qrcode" qrcode-text="{{ qrcode }}"></div> <div id="qrcode">
<img class="kg-qrcode" src="{{ code_url }}" alt="二维码图片">
<input type="hidden" name="sn" value="{{ trade.sn }}">
<div id="success-tips" class="success-tips layui-hide">
<span><i class="layui-icon layui-icon-ok-circle"></i> 支付成功</span>
</div> </div>
<div id="error-tips" class="error-tips layui-hide"> <input type="hidden" name="trade_sn" value="{{ trade_sn }}">
<span><i class="layui-icon layui-icon-close-fill"></i> 支付失败</span>
<div id="success-tips" class="kg-success-tips layui-hide">
<span>支付成功</span>
</div>
<div id="error-tips" class="kg-error-tips layui-hide">
<span>支付失败</span>
</div> </div>
{% else %} {% else %}
<div class="error-tips"> <div class="kg-error-tips">
<span><i class="layui-icon layui-icon-close-fill"></i> 生成二维码失败</span> <span>生成二维码失败</span>
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% if qrcode %} {% if code_url %}
{{ javascript_include('lib/jquery.min.js') }}
{{ javascript_include('lib/jquery.qrcode.min.js') }}
<script> <script>
@ -35,21 +34,15 @@
var $ = layui.jquery; var $ = layui.jquery;
$('#qrcode').qrcode({
text: $('#qrcode').attr('qrcode-text'),
width: 150,
height: 150
});
var loopTime = 0; var loopTime = 0;
var sn = $('input[name=sn]').val(); var tradeSn = $('input[name=trade_sn]').val();
var interval = setInterval(function () { var interval = setInterval(function () {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: '/admin/test/alipay/status', url: '/admin/test/alipay/status',
data: {sn: sn}, data: {trade_sn: tradeSn},
success: function (res) { success: function (res) {
if (res.status == 'finished') { if (res.status === 'finished') {
$('#success-tips').removeClass('layui-hide'); $('#success-tips').removeClass('layui-hide');
$('#qrcode').addClass('layui-hide'); $('#qrcode').addClass('layui-hide');
clearInterval(interval); clearInterval(interval);
@ -66,7 +59,7 @@
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: '/admin/test/alipay/cancel', url: '/admin/test/alipay/cancel',
data: {sn: sn}, data: {trade_sn: tradeSn},
success: function (res) { success: function (res) {
}, },
error: function (xhr) { error: function (xhr) {

View File

@ -3,37 +3,36 @@
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-form-label">开启支付</label> <label class="layui-form-label">开启支付</label>
<div class="layui-input-block"> <div class="layui-input-block">
<input type="radio" name="enabled" value="1" title="是" {% if wechat.enabled == "1" %}checked{% endif %}> <input type="radio" name="enabled" value="1" title="是" {% if wxpay.enabled == "1" %}checked{% endif %}>
<input type="radio" name="enabled" value="0" title="否" {% if wechat.enabled == "0" %}checked{% endif %}> <input type="radio" name="enabled" value="0" title="否" {% if wxpay.enabled == "0" %}checked{% endif %}>
</div> </div>
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-form-label">App ID</label> <label class="layui-form-label">App ID</label>
<div class="layui-input-block"> <div class="layui-input-block">
<input class="layui-input" type="text" name="app_id" value="{{ wechat.app_id }}" lay-verify="required"> <input class="layui-input" type="text" name="app_id" value="{{ wxpay.app_id }}" lay-verify="required">
</div> </div>
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-form-label">Mch ID</label> <label class="layui-form-label">Mch ID</label>
<div class="layui-input-block"> <div class="layui-input-block">
<input class="layui-input" type="text" name="mch_id" value="{{ wechat.mch_id }}" lay-verify="required"> <input class="layui-input" type="text" name="mch_id" value="{{ wxpay.mch_id }}" lay-verify="required">
</div> </div>
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-form-label">Private Key</label> <label class="layui-form-label">Private Key</label>
<div class="layui-input-block"> <div class="layui-input-block">
<input class="layui-input" type="text" name="key" value="{{ wechat.key }}" lay-verify="required"> <input class="layui-input" type="text" name="key" value="{{ wxpay.key }}" lay-verify="required">
</div> </div>
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-form-label">Notify Url</label> <label class="layui-form-label">Notify Url</label>
<div class="layui-input-block"> <div class="layui-input-block">
<input class="layui-input" type="text" name="notify_url" value="{{ wechat.notify_url }}" <input class="layui-input" type="text" name="notify_url" value="{{ wxpay.notify_url }}" lay-verify="required">
lay-verify="required">
</div> </div>
</div> </div>
@ -42,7 +41,7 @@
<div class="layui-input-block"> <div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button> <button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button> <button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
<input type="hidden" name="section" value="payment.wechat"> <input type="hidden" name="section" value="payment.wxpay">
</div> </div>
</div> </div>
@ -71,7 +70,7 @@
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-form-label"></label> <label class="layui-form-label"></label>
<div class="layui-input-block"> <div class="layui-input-block">
<button type="button" class="layui-btn" id="show-wechat-test">提交</button> <button type="button" class="layui-btn" id="show-wxpay-test">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button> <button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
</div> </div>
</div> </div>
@ -85,8 +84,8 @@
var $ = layui.jquery; var $ = layui.jquery;
var layer = layui.layer; var layer = layui.layer;
$('#show-wechat-test').on('click', function () { $('#show-wxpay-test').on('click', function () {
var url = '/admin/test/wechat'; var url = '/admin/test/wxpay';
layer.open({ layer.open({
type: 2, type: 2,
title: '微信 - 支付测试', title: '微信 - 支付测试',

View File

@ -1,33 +1,32 @@
<div class="kg-qrcode-block"> <div class="kg-qrcode-block">
{% if qrcode %} {% if code_url %}
<div id="qrcode" class="qrcode" qrcode-text="{{ qrcode }}"></div> <div id="qrcode">
<img class="kg-qrcode" src="{{ code_url }}" alt="二维码图片">
<input type="hidden" name="sn" value="{{ trade.sn }}">
<div id="success-tips" class="success-tips layui-hide">
<span><i class="layui-icon layui-icon-ok-circle"></i> 支付成功</span>
</div> </div>
<div id="error-tips" class="error-tips layui-hide"> <input type="hidden" name="trade_sn" value="{{ trade_sn }}">
<span><i class="layui-icon layui-icon-close-fill"></i> 支付失败</span>
<div id="success-tips" class="kg-success-tips layui-hide">
<span>支付成功</span>
</div>
<div id="error-tips" class="kg-error-tips layui-hide">
<span>支付失败</span>
</div> </div>
{% else %} {% else %}
<div class="error-tips"> <div class="kg-error-tips">
<span><i class="layui-icon layui-icon-close-fill"></i> 生成二维码失败</span> <span>生成二维码失败</span>
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% if qrcode %} {% if code_url %}
{{ javascript_include('lib/jquery.min.js') }}
{{ javascript_include('lib/jquery.qrcode.min.js') }}
<script> <script>
@ -35,21 +34,15 @@
var $ = layui.jquery; var $ = layui.jquery;
$('#qrcode').qrcode({
text: $('#qrcode').attr('qrcode-text'),
width: 150,
height: 150
});
var loopTime = 0; var loopTime = 0;
var sn = $('input[name=sn]').val(); var tradeSn = $('input[name=trade_sn]').val();
var interval = setInterval(function () { var interval = setInterval(function () {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: '/admin/test/alipay/status', url: '/admin/test/wxpay/status',
data: {sn: sn}, data: {trade_sn: tradeSn},
success: function (res) { success: function (res) {
if (res.status == 2) { if (res.status === 'finished') {
$('#success-tips').removeClass('layui-hide'); $('#success-tips').removeClass('layui-hide');
$('#qrcode').addClass('layui-hide'); $('#qrcode').addClass('layui-hide');
clearInterval(interval); clearInterval(interval);
@ -65,8 +58,8 @@
if (loopTime >= 300) { if (loopTime >= 300) {
$.ajax({ $.ajax({
type: 'POST', type: 'POST',
url: '/admin/config/wechat/test/cancel', url: '/admin/test/wxpay/cancel',
data: {sn: sn}, data: {trade_sn: tradeSn},
success: function (res) { success: function (res) {
}, },
error: function (xhr) { error: function (xhr) {

View File

@ -32,15 +32,13 @@
<table class="kg-table layui-table layui-form"> <table class="kg-table layui-table layui-form">
<colgroup> <colgroup>
<col width="12%"> <col width="12%">
<col width="10%"> <col width="15%">
<col width="12%">
<col> <col>
<col width="10%"> <col width="10%">
</colgroup> </colgroup>
<thead> <thead>
<tr> <tr>
<th>名称</th> <th>名称</th>
<th>类型</th>
<th>模板编号</th> <th>模板编号</th>
<th>模板内容</th> <th>模板内容</th>
<th>操作</th> <th>操作</th>
@ -48,32 +46,28 @@
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td>注册帐号</td> <td>身份验证</td>
<td><span class="layui-badge">验证码</span></td> <td><input class="layui-input" type="text" name="template[id][verify]" value="{{ template.verify.id }}" lay-verify="required"></td>
<td><input class="layui-input" type="text" name="template[id][register]" value="{{ template.register.id }}" lay-verify="required"></td> <td><input id="tc-verify" class="layui-input" type="text" name="template[content][verify]" value="{{ template.verify.content }}" readonly="readonly" lay-verify="required"></td>
<td><input id="tc1" class="layui-input" type="text" name="template[content][register]" value="{{ template.register.content }}" lay-verify="required"></td> <td><span class="kg-copy layui-btn" data-clipboard-target="#tc-verify">复制</span></td>
<td><span class="kg-copy layui-btn" data-clipboard-target="#tc1">复制</span></td>
</tr> </tr>
<tr> <tr>
<td>重置密码</td> <td>订单通知</td>
<td><span class="layui-badge">验证码</span></td> <td><input class="layui-input" type="text" name="template[id][order]" value="{{ template.order.id }}" lay-verify="required"></td>
<td><input class="layui-input" type="text" name="template[id][reset_password]" value="{{ template.reset_password.id }}" lay-verify="required"></td> <td><input id="tc-order" class="layui-input" type="text" name="template[content][order]" value="{{ template.order.content }}" readonly="readonly" lay-verify="required"></td>
<td><input id="tc2" class="layui-input" type="text" name="template[content][reset_password]" value="{{ template.reset_password.content }}" lay-verify="required"></td> <td><span class="kg-copy layui-btn" data-clipboard-target="#tc-order">复制</span></td>
<td><span class="kg-copy layui-btn" data-clipboard-target="#tc2">复制</span></td>
</tr> </tr>
<tr> <tr>
<td>购买课程</td> <td>退款通知</td>
<td><span class="layui-badge layui-bg-blue">通知</span></td> <td><input class="layui-input" type="text" name="template[id][refund]" value="{{ template.refund.id }}" lay-verify="required"></td>
<td><input class="layui-input" type="text" name="template[id][buy_course]" value="{{ template.buy_course.id }}" lay-verify="required"></td> <td><input id="tc-refund" class="layui-input" type="text" name="template[content][refund]" value="{{ template.refund.content }}" readonly="readonly" lay-verify="required"></td>
<td><input id="tc3" class="layui-input" type="text" name="template[content][buy_course]" value="{{ template.buy_course.content }}" lay-verify="required"></td> <td><span class="kg-copy layui-btn" data-clipboard-target="#tc-refund">复制</span></td>
<td><span class="kg-copy layui-btn" data-clipboard-target="#tc3">复制</span></td>
</tr> </tr>
<tr> <tr>
<td>购买会员</td> <td>直播通知</td>
<td><span class="layui-badge layui-bg-blue">通知</span></td> <td><input class="layui-input" type="text" name="template[id][live]" value="{{ template.live.id }}" lay-verify="required"></td>
<td><input class="layui-input" type="text" name="template[id][buy_member]" value="{{ template.buy_member.id }}" lay-verify="required"></td> <td><input id="tc-live" class="layui-input" type="text" name="template[content][live]" value="{{ template.live.content }}" readonly="readonly" lay-verify="required"></td>
<td><input id="tc4" class="layui-input" type="text" name="template[content][buy_member]" value="{{ template.buy_member.content }}" lay-verify="required"></td> <td><span class="kg-copy layui-btn" data-clipboard-target="#tc-live">复制</span></td>
<td><span class="kg-copy layui-btn" data-clipboard-target="#tc4">复制</span></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@ -10,7 +10,7 @@
{%- macro category_info(items) %} {%- macro category_info(items) %}
{% for item in items %} {% for item in items %}
<span class="layui-badge layui-bg-green">{{ item.name }}</span> <a class="layui-badge layui-bg-green" href="{{ url({'for':'admin.course.list'},{'category_id':item.id}) }}">{{ item.name }}</a>
{% endfor %} {% endfor %}
{%- endmacro %} {%- endmacro %}

View File

@ -11,13 +11,6 @@
</div> </div>
</div> </div>
<div class="layui-form-item">
<label class="layui-form-label">用户编号</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="user_id" placeholder="用户编号精确匹配">
</div>
</div>
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-form-label">标题</label> <label class="layui-form-label">标题</label>
<div class="layui-input-block"> <div class="layui-input-block">
@ -37,7 +30,7 @@
<div class="layui-input-block"> <div class="layui-input-block">
<input type="radio" name="model" value="vod" title="点播"> <input type="radio" name="model" value="vod" title="点播">
<input type="radio" name="model" value="live" title="直播"> <input type="radio" name="model" value="live" title="直播">
<input type="radio" name="model" value="article" title="图文"> <input type="radio" name="model" value="read" title="图文">
</div> </div>
</div> </div>

View File

@ -13,7 +13,7 @@
{%- macro channel_type(value) %} {%- macro channel_type(value) %}
{% if value == 'alipay' %} {% if value == 'alipay' %}
<span class="layui-badge layui-bg-blue">支付宝</span> <span class="layui-badge layui-bg-blue">支付宝</span>
{% elseif value == 'wechat' %} {% elseif value == 'wxpay' %}
<span class="layui-badge layui-bg-green">微信</span> <span class="layui-badge layui-bg-green">微信</span>
{% endif %} {% endif %}
{%- endmacro %} {%- endmacro %}

View File

@ -22,7 +22,7 @@
<label class="layui-form-label">交易平台</label> <label class="layui-form-label">交易平台</label>
<div class="layui-input-block"> <div class="layui-input-block">
<input type="radio" name="channel" value="alipay" title="支付宝"> <input type="radio" name="channel" value="alipay" title="支付宝">
<input type="radio" name="channel" value="wechat" title="微信"> <input type="radio" name="channel" value="wxpay" title="微信">
</div> </div>
</div> </div>

View File

@ -1,59 +0,0 @@
<?php
namespace App\Http\Home\Controllers;
use App\Services\Alipay as AlipayService;
class NotifyController extends Controller
{
/**
* @Post("/alipay/notify", name="home.alipay.notify")
*/
public function alipayNotifyAction()
{
$alipayService = new AlipayService();
$response = $alipayService->handleNotify();
if (!$response) exit;
$response->send();
exit;
}
/**
* @Post("/wechat/notify", name="home.wechat.notify")
*/
public function wechatNotifyAction()
{
$alipayService = new AlipayService();
$response = $alipayService->handleNotify();
if (!$response) exit;
$response->send();
exit;
}
/**
* @Post("/vod/notify", name="home.vod.notify")
*/
public function vodNotifyAction()
{
}
/**
* @Post("/live/notify", name="home.live.notify")
*/
public function liveNotifyAction()
{
}
}

View File

@ -0,0 +1,74 @@
<?php
namespace App\Http\Home\Controllers;
use App\Services\Payment\Alipay as AlipayService;
use App\Services\Payment\Wxpay as WxpayService;
use App\Traits\Ajax as AjaxTrait;
class PaymentController extends \Phalcon\Mvc\Controller
{
use AjaxTrait;
/**
* @Post("/alipay/notify", name="home.alipay.notify")
*/
public function alipayNotifyAction()
{
$alipayService = new AlipayService();
$response = $alipayService->notify();
if (!$response) exit;
$response->send();
exit;
}
/**
* @Post("/wxpay/notify", name="home.wxpay.notify")
*/
public function wxpayNotifyAction()
{
$wxpayService = new WxpayService();
$response = $wxpayService->notify();
if (!$response) exit;
$response->send();
exit;
}
/**
* @Post("/alipay/status", name="home.alipay.status")
*/
public function alipayStatusAction()
{
$sn = $this->request->getPost('sn');
$alipayService = new AlipayService();
$status = $alipayService->status($sn);
return $this->ajaxSuccess(['status' => $status]);
}
/**
* @Post("/wxpay/status", name="home.wxpay.status")
*/
public function wxpayStatusAction()
{
$sn = $this->request->getPost('sn');
$wxpayService = new WxpayService();
$status = $wxpayService->status($sn);
return $this->ajaxSuccess(['status' => $status]);
}
}

View File

@ -66,9 +66,9 @@ class PublicController extends \Phalcon\Mvc\Controller
} }
/** /**
* @Get("/qrcode/img", name="home.qrcode.img") * @Get("/qr/img", name="home.qr.img")
*/ */
public function qrcodeImageAction() public function qrImageAction()
{ {
$text = $this->request->getQuery('text'); $text = $this->request->getQuery('text');
$level = $this->request->getQuery('level', 'int', 0); $level = $this->request->getQuery('level', 'int', 0);
@ -76,7 +76,9 @@ class PublicController extends \Phalcon\Mvc\Controller
$url = urldecode($text); $url = urldecode($text);
echo QRcode::png($url, false, $level, $size); QRcode::png($url, false, $level, $size);
$this->response->send();
exit; exit;
} }

View File

@ -52,7 +52,7 @@ class Payment extends Listener
$task->item_id = $order->id; $task->item_id = $order->id;
$task->item_info = $itemInfo; $task->item_info = $itemInfo;
$task->item_type = TaskModel::TYPE_PROCESS_ORDER; $task->item_type = TaskModel::TYPE_ORDER;
if ($task->create() === false) { if ($task->create() === false) {
throw new \RuntimeException('Create Order Process Task Failed'); throw new \RuntimeException('Create Order Process Task Failed');

View File

@ -12,6 +12,7 @@ class Order extends Model
*/ */
const ITEM_COURSE = 'course'; // 课程 const ITEM_COURSE = 'course'; // 课程
const ITEM_PACKAGE = 'package'; // 套餐 const ITEM_PACKAGE = 'package'; // 套餐
const ITEM_REWARD = 'reward'; // 赞赏
const ITEM_VIP = 'vip'; // 会员 const ITEM_VIP = 'vip'; // 会员
const ITEM_TEST = 'test'; // 测试 const ITEM_TEST = 'test'; // 测试
@ -183,6 +184,7 @@ class Order extends Model
$list = [ $list = [
self::ITEM_COURSE => '课程', self::ITEM_COURSE => '课程',
self::ITEM_PACKAGE => '套餐', self::ITEM_PACKAGE => '套餐',
self::ITEM_REWARD => '赞赏',
self::ITEM_VIP => '会员', self::ITEM_VIP => '会员',
self::ITEM_TEST => '测试', self::ITEM_TEST => '测试',
]; ];

View File

@ -9,8 +9,7 @@ class Task extends Model
* 任务类型 * 任务类型
*/ */
const TYPE_REFUND = 'refund'; // 退款 const TYPE_REFUND = 'refund'; // 退款
const TYPE_PROCESS_ORDER = 'process_order'; // 处理订单 const TYPE_ORDER = 'order'; // 下单
const TYPE_LIVE_NOTIFY = 'live_notify'; // 直播通知
/** /**
* 优先级 * 优先级

View File

@ -11,7 +11,7 @@ class Trade extends Model
* 平台类型 * 平台类型
*/ */
const CHANNEL_ALIPAY = 'alipay'; // 支付宝 const CHANNEL_ALIPAY = 'alipay'; // 支付宝
const CHANNEL_WECHAT = 'wechat'; // 微信 const CHANNEL_WXPAY = 'wxpay'; // 微信
/** /**
* 状态类型 * 状态类型
@ -145,7 +145,7 @@ class Trade extends Model
{ {
$list = [ $list = [
self::CHANNEL_ALIPAY => '支付宝', self::CHANNEL_ALIPAY => '支付宝',
self::CHANNEL_WECHAT => '微信', self::CHANNEL_WXPAY => '微信',
]; ];
return $list; return $list;

View File

@ -125,7 +125,7 @@ class Order extends Repository
* @param string $itemType * @param string $itemType
* @return OrderModel|Model|bool * @return OrderModel|Model|bool
*/ */
public function findLastUserOrder($userId, $itemId, $itemType) public function findLastUserItem($userId, $itemId, $itemType)
{ {
$result = OrderModel::findFirst([ $result = OrderModel::findFirst([
'conditions' => 'user_id = ?1 AND item_id = ?2 AND item_type = ?3', 'conditions' => 'user_id = ?1 AND item_id = ?2 AND item_type = ?3',

View File

@ -37,7 +37,7 @@ class Vip extends Repository
} }
/** /**
* @param $where * @param array $where
* @return ResultsetInterface|Resultset|VipModel[] * @return ResultsetInterface|Resultset|VipModel[]
*/ */
public function findAll($where = []) public function findAll($where = [])

View File

@ -2,6 +2,7 @@
namespace App\Services; namespace App\Services;
use Phalcon\Logger\Adapter\File as FileLogger;
use TencentCloud\Captcha\V20190722\CaptchaClient; use TencentCloud\Captcha\V20190722\CaptchaClient;
use TencentCloud\Captcha\V20190722\Models\DescribeCaptchaResultRequest; use TencentCloud\Captcha\V20190722\Models\DescribeCaptchaResultRequest;
use TencentCloud\Common\Credential; use TencentCloud\Common\Credential;
@ -14,14 +15,27 @@ class Captcha extends Service
const END_POINT = 'captcha.tencentcloudapi.com'; const END_POINT = 'captcha.tencentcloudapi.com';
/**
* @var array
*/
protected $config; protected $config;
/**
* @var FileLogger
*/
protected $logger; protected $logger;
/**
* @var CaptchaClient
*/
protected $client; protected $client;
public function __construct() public function __construct()
{ {
$this->config = $this->getSectionConfig('captcha'); $this->config = $this->getSectionConfig('captcha');
$this->logger = $this->getLogger('captcha'); $this->logger = $this->getLogger('captcha');
$this->client = $this->getCaptchaClient(); $this->client = $this->getCaptchaClient();
} }
@ -44,6 +58,9 @@ class Captcha extends Service
$request = new DescribeCaptchaResultRequest(); $request = new DescribeCaptchaResultRequest();
/**
* 注意CaptchaType CaptchaAppId 强类型要求
*/
$params = json_encode([ $params = json_encode([
'Ticket' => $ticket, 'Ticket' => $ticket,
'Randstr' => $rand, 'Randstr' => $rand,
@ -61,9 +78,9 @@ class Captcha extends Service
$this->logger->debug('Describe Captcha Result Response ' . $response->toJsonString()); $this->logger->debug('Describe Captcha Result Response ' . $response->toJsonString());
$result = json_decode($response->toJsonString(), true); $data = json_decode($response->toJsonString(), true);
return $result['CaptchaCode'] == 1 ? true : false; $result = $data['CaptchaCode'] == 1;
} catch (TencentCloudSDKException $e) { } catch (TencentCloudSDKException $e) {
@ -73,8 +90,10 @@ class Captcha extends Service
'requestId' => $e->getRequestId(), 'requestId' => $e->getRequestId(),
])); ]));
return false; $result = false;
} }
return $result;
} }
/** /**

View File

@ -27,7 +27,7 @@ class ChapterComment extends Service
$validator = new UserDailyLimitValidator(); $validator = new UserDailyLimitValidator();
$validator->checkCommentLimit($user->id); $validator->checkCommentLimit($user);
$validator = new CommentValidator(); $validator = new CommentValidator();
@ -62,6 +62,11 @@ class ChapterComment extends Service
$this->incrUserDailyCommentCount($user); $this->incrUserDailyCommentCount($user);
} }
protected function handleMentions($mentions)
{
}
protected function incrUserDailyCommentCount(UserModel $user) protected function incrUserDailyCommentCount(UserModel $user)
{ {
$this->eventsManager->fire('userDailyCounter:incrCommentCount', $this, $user); $this->eventsManager->fire('userDailyCounter:incrCommentCount', $this, $user);

View File

@ -9,7 +9,6 @@ use App\Models\CommentVote as CommentVoteModel;
use App\Models\User as UserModel; use App\Models\User as UserModel;
use App\Repos\Chapter as ChapterRepo; use App\Repos\Chapter as ChapterRepo;
use App\Repos\Comment as CommentRepo; use App\Repos\Comment as CommentRepo;
use Phalcon\Mvc\Model\Resultset;
class ChapterCommentList extends Service class ChapterCommentList extends Service
{ {
@ -104,9 +103,6 @@ class ChapterCommentList extends Service
$chapterRepo = new ChapterRepo(); $chapterRepo = new ChapterRepo();
/**
* @var Resultset $votes
*/
$votes = $chapterRepo->findUserCommentVotes($chapterId, $userId); $votes = $chapterRepo->findUserCommentVotes($chapterId, $userId);
if ($votes->count() == 0) { if ($votes->count() == 0) {

View File

@ -19,21 +19,26 @@ class Comment extends Service
$validator = new CommentValidator(); $validator = new CommentValidator();
$validator->checkOwnerPriv($user->id, $comment->user_id); $validator->checkOwner($user->id, $comment->user_id);
$comment->deleted = 1; $comment->deleted = 1;
$comment->update(); $comment->update();
$chapterRepo = new ChapterRepo(); $chapterRepo = new ChapterRepo();
$chapter = $chapterRepo->findById($comment->chapter_id); $chapter = $chapterRepo->findById($comment->chapter_id);
$chapter->comment_count -= 1; $chapter->comment_count -= 1;
$chapter->update(); $chapter->update();
$courseRepo = new CourseRepo(); $courseRepo = new CourseRepo();
$course = $courseRepo->findById($comment->course_id); $course = $courseRepo->findById($comment->course_id);
$course->comment_count -= 1; $course->comment_count -= 1;
$course->update(); $course->update();
} }

View File

@ -18,6 +18,8 @@ class CourseUser extends Service
$validator = new CourseUserValidator(); $validator = new CourseUserValidator();
$validator->checkIfAllowApply($course, $user);
$validator->checkIfJoined($course->id, $user->id); $validator->checkIfJoined($course->id, $user->id);
$courseUser = new CourseUserModel(); $courseUser = new CourseUserModel();

View File

@ -0,0 +1,30 @@
<?php
namespace App\Services\Frontend;
use App\Models\Order as OrderModel;
use App\Validators\Order as OrderValidator;
class OrderCancel extends Service
{
use OrderTrait;
public function cancelOrder($sn)
{
$order = $this->checkOrder($sn);
$user = $this->getLoginUser();
$validator = new OrderValidator();
$validator->checkOwner($user->id, $order->user_id);
$validator->checkIfAllowCancel($order);
$order->status = OrderModel::STATUS_CLOSED;
$order->update();
}
}

View File

@ -30,42 +30,46 @@ class OrderCreate extends Service
$validator = new OrderValidator(); $validator = new OrderValidator();
/** $validator->checkItemType($post['item_type']);
* @var CourseModel|PackageModel|VipModel $item
*/
$item = $validator->checkItem($post['item_id'], $post['item_type']);
$validator->checkIfBought($user->id, $post['item_id'], $post['item_type']);
$validator->checkDailyLimit($user->id);
$orderRepo = new OrderRepo(); $orderRepo = new OrderRepo();
$order = $orderRepo->findLastUserOrder($user->id, $post['item_id'], $post['item_type']); $order = $orderRepo->findLastUserItem($user->id, $post['item_id'], $post['item_type']);
/** /**
* 存在新鲜的未支付订单直接返回(减少订单记录) * 存在新鲜的未支付订单直接返回(减少订单记录)
*/ */
if ($order) { if ($order) {
$caseA = $order->status == OrderModel::STATUS_PENDING; $caseA = $order->status == OrderModel::STATUS_PENDING;
$caseB = time() - $order->created_at < 6 * 3600; $caseB = time() - $order->created_at < 6 * 3600;
if ($caseA && $caseB) { if ($caseA && $caseB) {
return $order; return $order;
} }
} }
$order = new OrderModel(); if ($post['item_type'] == OrderModel::ITEM_COURSE) {
switch ($post['item_type']) { $course = $validator->checkItemCourse($post['item_id']);
case OrderModel::ITEM_COURSE:
$order = $this->createCourseOrder($item, $user); $validator->checkIfBoughtCourse($user->id, $course->id);
break;
case OrderModel::ITEM_PACKAGE: $order = $this->createCourseOrder($course, $user);
$order = $this->createPackageOrder($item, $user);
break; } elseif ($post['item_type'] == OrderModel::ITEM_PACKAGE) {
case OrderModel::ITEM_VIP:
$order = $this->createVipOrder($item, $user); $package = $validator->checkItemPackage($post['item_id']);
break;
$validator->checkIfBoughtPackage($user->id, $package->id);
$order = $this->createPackageOrder($package, $user);
} elseif ($post['item_type'] == OrderModel::ITEM_VIP) {
$vip = $validator->checkItemVip($post['item_id']);
$order = $this->createVipOrder($vip, $user);
} }
$this->incrUserDailyOrderCount($user); $this->incrUserDailyOrderCount($user);
@ -78,7 +82,7 @@ class OrderCreate extends Service
* @param UserModel $user * @param UserModel $user
* @return OrderModel $order * @return OrderModel $order
*/ */
public function createCourseOrder($course, $user) public function createCourseOrder(CourseModel $course, UserModel $user)
{ {
$studyExpiryTime = strtotime("+{$course->study_expiry} months"); $studyExpiryTime = strtotime("+{$course->study_expiry} months");
$refundExpiryTime = strtotime("+{$course->refund_expiry} days"); $refundExpiryTime = strtotime("+{$course->refund_expiry} days");
@ -118,7 +122,7 @@ class OrderCreate extends Service
* @param UserModel $user * @param UserModel $user
* @return OrderModel $order * @return OrderModel $order
*/ */
public function createPackageOrder($package, $user) public function createPackageOrder(PackageModel $package, UserModel $user)
{ {
$packageRepo = new PackageRepo(); $packageRepo = new PackageRepo();
@ -175,7 +179,7 @@ class OrderCreate extends Service
* @param UserModel $user * @param UserModel $user
* @return OrderModel * @return OrderModel
*/ */
public function createVipOrder($vip, $user) public function createVipOrder(VipModel $vip, UserModel $user)
{ {
$baseTime = $user->vip_expiry_time > time() ? $user->vip_expiry_time : time(); $baseTime = $user->vip_expiry_time > time() ? $user->vip_expiry_time : time();
$expiryTime = strtotime("+{$vip->expiry} months", $baseTime); $expiryTime = strtotime("+{$vip->expiry} months", $baseTime);
@ -184,8 +188,8 @@ class OrderCreate extends Service
'vip' => [ 'vip' => [
'id' => $vip->id, 'id' => $vip->id,
'title' => $vip->title, 'title' => $vip->title,
'expiry' => $vip->expiry,
'price' => $vip->price, 'price' => $vip->price,
'expiry' => $vip->expiry,
'expiry_time' => $expiryTime, 'expiry_time' => $expiryTime,
] ]
]; ];
@ -204,6 +208,9 @@ class OrderCreate extends Service
return $order; return $order;
} }
/**
* @param UserModel $user
*/
protected function incrUserDailyOrderCount(UserModel $user) protected function incrUserDailyOrderCount(UserModel $user)
{ {
$this->eventsManager->fire('userDailyCounter:incrOrderCount', $this, $user); $this->eventsManager->fire('userDailyCounter:incrOrderCount', $this, $user);

View File

@ -3,8 +3,8 @@
namespace App\Services\Frontend; namespace App\Services\Frontend;
use App\Models\Trade as TradeModel; use App\Models\Trade as TradeModel;
use App\Services\Alipay as AlipayService; use App\Services\Payment\Alipay as AlipayService;
use App\Services\Wechat as WxPayService; use App\Services\Payment\Wxpay as WxPayService;
use App\Validators\Trade as TradeValidator; use App\Validators\Trade as TradeValidator;
class OrderTrade extends Service class OrderTrade extends Service
@ -68,21 +68,13 @@ class OrderTrade extends Service
$alipayService = new AlipayService(); $alipayService = new AlipayService();
$qrCode = $alipayService->getQrCode([ $qrCode = $alipayService->scan($trade);
'out_trade_no' => $trade->sn,
'total_amount' => $trade->amount,
'subject' => $trade->subject,
]);
} elseif ($trade->channel == TradeModel::CHANNEL_WECHAT) { } elseif ($trade->channel == TradeModel::CHANNEL_WXPAY) {
$wechatService = new WxPayService(); $wxpayService = new WxPayService();
$qrCode = $wechatService->getQrCode([ $qrCode = $wxpayService->scan($trade);
'out_trade_no' => $trade->sn,
'total_fee' => 100 * $trade->amount,
'body' => $trade->subject,
]);
} }
return $qrCode; return $qrCode;

View File

@ -3,52 +3,46 @@
namespace App\Services\Frontend; namespace App\Services\Frontend;
use App\Models\User as UserModel; use App\Models\User as UserModel;
use App\Repos\User as UserRepo;
use App\Validators\Validator as AppValidator; use App\Validators\Validator as AppValidator;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\User\Component; use Phalcon\Mvc\User\Component;
class Service extends Component class Service extends Component
{ {
/**
* @return UserModel|Model
*/
public function getCurrentUser() public function getCurrentUser()
{ {
$user = UserModel::findFirst(100015); $userRepo = new UserRepo();
$user = $userRepo->findById(100015);
return $user; return $user;
} }
/**
* @return UserModel|Model
*/
public function getCurrentUser2() public function getCurrentUser2()
{ {
$authUser = $this->getAuthUser(); $authUser = $this->getAuthUser();
if ($authUser) { if (!$authUser) {
$user = UserModel::findFirst($authUser->id); return $this->getGuestUser();
} else {
$user = $this->getGuestUser();
} }
$userRepo = new UserRepo();
$user = $userRepo->findById($authUser->id);
return $user; return $user;
} }
/**
* @return UserModel|Model
*/
public function getLoginUser() public function getLoginUser()
{ {
$user = UserModel::findFirst(100015); $userRepo = new UserRepo();
$user = $userRepo->findById(100015);
return $user; return $user;
} }
/**
* @return UserModel|Model
*/
public function getLoginUser2() public function getLoginUser2()
{ {
$authUser = $this->getAuthUser(); $authUser = $this->getAuthUser();
@ -57,7 +51,9 @@ class Service extends Component
$validator->checkAuthUser($authUser); $validator->checkAuthUser($authUser);
$user = UserModel::findFirst($authUser->id); $userRepo = new UserRepo();
$user = $userRepo->findById($authUser->id);
return $user; return $user;
} }

View File

@ -0,0 +1,55 @@
<?php
namespace App\Services\Frontend;
use App\Builders\CourseUserList as CourseUserListBuilder;
use App\Library\Paginator\Query as PagerQuery;
use App\Models\CourseUser as CourseUserModel;
use App\Repos\CourseUser as CourseUserRepo;
class TeacherCourseList extends Service
{
use UserTrait;
public function getCourses($id)
{
$user = $this->checkUser($id);
$pagerQuery = new PagerQuery();
$params = $pagerQuery->getParams();
$params['user_id'] = $user->id;
$params['role_type'] = CourseUserModel::ROLE_TEACHER;
$params['deleted'] = 0;
$sort = $pagerQuery->getSort();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
$courseUserRepo = new CourseUserRepo();
$pager = $courseUserRepo->paginate($params, $sort, $page, $limit);
return $this->handleCourses($pager);
}
protected function handleCourses($pager)
{
if ($pager->total_items == 0) {
return $pager;
}
$builder = new CourseUserListBuilder();
$relations = $pager->items->toArray();
$courses = $builder->getCourses($relations);
$pager->items = $courses;
return $pager;
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace App\Services\Frontend;
use App\Builders\CourseUserList as CourseUserListBuilder;
use App\Library\Paginator\Query as PagerQuery;
use App\Models\CourseUser as CourseUserModel;
use App\Repos\CourseUser as CourseUserRepo;
class UserCourseList extends Service
{
use UserTrait;
public function getCourses($id)
{
$user = $this->checkUser($id);
$pagerQuery = new PagerQuery();
$params = $pagerQuery->getParams();
$params['user_id'] = $user->id;
$params['role_type'] = CourseUserModel::ROLE_STUDENT;
$params['deleted'] = 0;
$sort = $pagerQuery->getSort();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
$courseUserRepo = new CourseUserRepo();
$pager = $courseUserRepo->paginate($params, $sort, $page, $limit);
return $this->handleCourses($pager);
}
protected function handleCourses($pager)
{
if ($pager->total_items == 0) {
return $pager;
}
$builder = new CourseUserListBuilder();
$relations = $pager->items->toArray();
$courses = $builder->getCourses($relations);
$items = [];
foreach ($relations as $relation) {
$course = $courses[$relation['course_id']] ?? [];
$items = [
'course' => $course,
'progress' => $relation['progress'],
'duration' => $relation['duration'],
];
}
$pager->items = $items;
return $pager;
}
}

View File

@ -41,7 +41,9 @@ class LearningSyncer extends Service
*/ */
public function addItem(LearningModel $learning, $timeout = 10) public function addItem(LearningModel $learning, $timeout = 10)
{ {
// 兼容秒和毫秒 /**
* 兼容秒和毫秒
*/
if ($timeout > 1000) { if ($timeout > 1000) {
$timeout = intval($timeout / 1000); $timeout = intval($timeout / 1000);
} }

View File

@ -5,6 +5,9 @@ namespace App\Services;
class Live extends Service class Live extends Service
{ {
/**
* @var array
*/
protected $config; protected $config;
public function __construct() public function __construct()
@ -63,17 +66,26 @@ class Live extends Service
$urls = []; $urls = [];
if ($transEnabled) { if ($transEnabled) {
foreach (['fd', 'sd', 'hd', 'od'] as $rateName) { foreach (['fd', 'sd', 'hd', 'od'] as $rateName) {
$realStreamName = ($rateName == 'od') ? $streamName : "{$streamName}_{$rateName}"; $realStreamName = ($rateName == 'od') ? $streamName : "{$streamName}_{$rateName}";
$authParams = $this->getAuthParams($realStreamName, $authKey, $expireTime); $authParams = $this->getAuthParams($realStreamName, $authKey, $expireTime);
$url = "{$protocol}://{$domain}/{$appName}/{$realStreamName}.{$extension}"; $url = "{$protocol}://{$domain}/{$appName}/{$realStreamName}.{$extension}";
$url .= $authEnabled ? "?{$authParams}" : ''; $url .= $authEnabled ? "?{$authParams}" : '';
$urls[$rateName] = $url; $urls[$rateName] = $url;
} }
} else { } else {
$authParams = $this->getAuthParams($streamName, $authKey, $expireTime); $authParams = $this->getAuthParams($streamName, $authKey, $expireTime);
$url = "{$protocol}://{$domain}/{$appName}/{$streamName}.{$extension}"; $url = "{$protocol}://{$domain}/{$appName}/{$streamName}.{$extension}";
$url .= $authEnabled ? "?{$authParams}" : ''; $url .= $authEnabled ? "?{$authParams}" : '';
$urls['od'] = $url; $urls['od'] = $url;
} }

View File

@ -2,38 +2,31 @@
namespace App\Services; namespace App\Services;
use Phalcon\Logger\Adapter\File as FileLogger;
use Phalcon\Mailer\Manager as MailerManager; use Phalcon\Mailer\Manager as MailerManager;
class Mailer extends Service abstract class Mailer extends Service
{ {
/**
* @var MailerManager
*/
protected $manager; protected $manager;
/**
* @var FileLogger
*/
protected $logger;
public function __construct() public function __construct()
{ {
$this->manager = $this->getManager(); $this->manager = $this->getManager();
$this->logger = $this->getLogger('mailer');
} }
/** /**
* 发送测试邮件 * 获取 Manager
*
* @param string $email
* @return mixed
*/
public function sendTestMail($email)
{
$message = $this->manager->createMessage();
$result = $message->to($email)
->subject('这是一封测试邮件')
->content('这是一封测试邮件')
->send();
return $result;
}
/**
* 获取Manager
*/ */
protected function getManager() protected function getManager()
{ {

View File

@ -0,0 +1,36 @@
<?php
namespace App\Services\Mailer;
use App\Services\Mailer;
class Test extends Mailer
{
public function handle($email)
{
try {
$message = $this->manager->createMessage();
$count = $message->to($email)
->subject('测试邮件')
->content('东风快递,使命必达')
->send();
$result = $count > 0;
} catch (\Exception $e) {
$this->logger->error('Send Test Mail Exception ' . kg_json_encode([
'code' => $e->getCode(),
'message' => $e->getMessage(),
]));
$result = false;
}
return $result;
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace App\Services\Mailer;
use App\Library\Util\Verification;
use App\Services\Mailer;
class Verify extends Mailer
{
public function handle($email)
{
try {
$message = $this->manager->createMessage();
$subject = '邮件验证码';
$minutes = 5;
$code = Verification::code($email, 60 * $minutes);
$content = $this->formatContent($code, $minutes);
$count = $message->to($email)
->subject($subject)
->content($content)
->send();
$result = $count > 0;
} catch (\Exception $e) {
$this->logger->error('Send Verify Mail Exception ' . kg_json_encode([
'code' => $e->getCode(),
'message' => $e->getMessage(),
]));
$result = false;
}
return $result;
}
protected function formatContent($code, $minutes)
{
$content = sprintf('验证码:%s%s 分钟内有效,如非本人操作请忽略。', $code, $minutes);
return $content;
}
}

67
app/Services/Payment.php Normal file
View File

@ -0,0 +1,67 @@
<?php
namespace App\Services;
use App\Models\Refund as RefundModel;
use App\Models\Trade as TradeModel;
use App\Repos\Trade as TradeRepo;
abstract class Payment extends Service
{
/**
* 交易状态
*
* @param string $tradeNo
* @return string
*/
public function status($tradeNo)
{
$tradeRepo = new TradeRepo();
$trade = $tradeRepo->findBySn($tradeNo);
return $trade->status;
}
/**
* 扫码下单
*
* @param TradeModel $trade
*/
abstract public function scan(TradeModel $trade);
/**
* 异步通知
*/
abstract public function notify();
/**
* 查找交易
*
* @param string $tradeNo
*/
abstract public function find($tradeNo);
/**
* 关闭交易
*
* @param string $tradeNo
*/
abstract public function close($tradeNo);
/**
* 取消交易
*
* @param string $tradeNo
*/
abstract public function cancel($tradeNo);
/**
* 申请退款
*
* @param RefundModel $refund
*/
abstract public function refund(RefundModel $refund);
}

View File

@ -1,14 +1,16 @@
<?php <?php
namespace App\Services; namespace App\Services\Payment;
use App\Models\Refund as RefundModel;
use App\Models\Trade as TradeModel; use App\Models\Trade as TradeModel;
use App\Repos\Trade as TradeRepo; use App\Repos\Trade as TradeRepo;
use App\Services\Payment;
use Yansongda\Pay\Log; use Yansongda\Pay\Log;
use Yansongda\Pay\Pay; use Yansongda\Pay\Pay;
use Yansongda\Supports\Collection; use Yansongda\Supports\Collection;
class Alipay extends Service class Alipay extends Payment
{ {
/** /**
@ -29,142 +31,23 @@ class Alipay extends Service
} }
/** /**
* 查询订单(扫码生成订单后可执行) * 扫码下单
* *
* @param string $outTradeNo * @param TradeModel $trade
* @return Collection|bool
*/
public function findOrder($outTradeNo)
{
try {
$order = ['out_trade_no' => $outTradeNo];
$result = $this->gateway->find($order);
return $result;
} catch (\Exception $e) {
Log::error('Alipay Find Order Exception', [
'code' => $e->getCode(),
'message' => $e->getMessage(),
]);
return false;
}
}
/**
* 撤销订单(未生成订单也可执行)
*
* @param string $outTradeNo
* @return Collection|bool
*/
public function cancelOrder($outTradeNo)
{
try {
$order = ['out_trade_no' => $outTradeNo];
$result = $this->gateway->cancel($order);
return $result;
} catch (\Exception $e) {
Log::error('Alipay Cancel Order Exception', [
'code' => $e->getCode(),
'message' => $e->getMessage(),
]);
return false;
}
}
/**
* 关闭订单(扫码生成订单后可执行)
*
* @param string $outTradeNo
* @return Collection|bool
*/
public function closeOrder($outTradeNo)
{
try {
$order = ['out_trade_no' => $outTradeNo];
$result = $this->gateway->close($order);
return $result;
} catch (\Exception $e) {
Log::error('Alipay Close Order Exception', [
'code' => $e->getCode(),
'message' => $e->getMessage(),
]);
return false;
}
}
/**
* 订单退款
*
* <code>
* $order = [
* 'out_trade_no' => '1514027114',
* 'refund_amount' => 0.01,
* ];
* </code>
*
* @param array $order
* @return Collection|bool
*/
public function refundOrder($order)
{
try {
$result = $this->gateway->refund($order);
return $result;
} catch (\Exception $e) {
Log::error('Alipay Refund Order Exception', [
'code' => $e->getCode(),
'message' => $e->getMessage(),
]);
return false;
}
}
/**
* 获取二维码内容
*
* <code>
* $order = [
* 'out_trade_no' =>'1514027114',
* 'total_amount' => 0.01,
* 'subject' => 'foo',
* ];
*</code>
*
* @param array $order
* @return bool|string * @return bool|string
*/ */
public function getQrCode($order) public function scan(TradeModel $trade)
{ {
try { try {
$response = $this->gateway->scan($order); $response = $this->gateway->scan([
'out_trade_no' => $trade->sn,
'total_amount' => $trade->amount,
'subject' => $trade->subject,
]);
$result = $response->qr_code ?? false; $result = $response->qr_code ?? false;
return $result;
} catch (\Exception $e) { } catch (\Exception $e) {
Log::error('Alipay Qrcode Exception', [ Log::error('Alipay Qrcode Exception', [
@ -172,14 +55,16 @@ class Alipay extends Service
'message' => $e->getMessage(), 'message' => $e->getMessage(),
]); ]);
return false; $result = false;
} }
return $result;
} }
/** /**
* 处理异步通知 * 异步通知
**/ */
public function handleNotify() public function notify()
{ {
try { try {
@ -229,7 +114,127 @@ class Alipay extends Service
} }
/** /**
* 获取 Alipay Gateway * 查询交易(扫码生成订单后可执行)
*
* @param string $outTradeNo
* @return Collection|bool
*/
public function find($outTradeNo)
{
try {
$result = $this->gateway->find([
'out_trade_no' => $outTradeNo,
]);
} catch (\Exception $e) {
Log::error('Alipay Find Order Exception', [
'code' => $e->getCode(),
'message' => $e->getMessage(),
]);
$result = false;
}
return $result;
}
/**
* 关闭交易(扫码生成订单后可执行)
*
* @param string $outTradeNo
* @return bool
*/
public function close($outTradeNo)
{
try {
$response = $this->gateway->close([
'out_trade_no' => $outTradeNo,
]);
$result = $response->code == '10000';
} catch (\Exception $e) {
Log::error('Alipay Close Order Exception', [
'code' => $e->getCode(),
'message' => $e->getMessage(),
]);
$result = false;
}
return $result;
}
/**
* 撤销交易(未生成订单也可执行)
*
* @param string $outTradeNo
* @return bool
*/
public function cancel($outTradeNo)
{
try {
$response = $this->gateway->cancel([
'out_trade_no' => $outTradeNo,
]);
$result = $response->code == '10000';
} catch (\Exception $e) {
Log::error('Alipay Cancel Order Exception', [
'code' => $e->getCode(),
'message' => $e->getMessage(),
]);
$result = false;
}
return $result;
}
/**
* 申请退款
*
* @param RefundModel $refund
* @return bool
*/
public function refund(RefundModel $refund)
{
try {
$tradeRepo = new TradeRepo();
$trade = $tradeRepo->findById($refund->trade_id);
$response = $this->gateway->refund([
'out_trade_no' => $trade->sn,
'out_request_no' => $refund->sn,
'refund_amount' => $refund->amount,
]);
$result = $response->code == '10000';
} catch (\Exception $e) {
Log::error('Alipay Refund Order Exception', [
'code' => $e->getCode(),
'message' => $e->getMessage(),
]);
$result = false;
}
return $result;
}
/**
* 获取 Gateway
* *
* @return \Yansongda\Pay\Gateways\Alipay * @return \Yansongda\Pay\Gateways\Alipay
*/ */

View File

@ -1,14 +1,17 @@
<?php <?php
namespace App\Services; namespace App\Services\Payment;
use App\Models\Refund as RefundModel;
use App\Models\Trade as TradeModel; use App\Models\Trade as TradeModel;
use App\Repos\Trade as TradeRepo; use App\Repos\Trade as TradeRepo;
use App\Services\Payment;
use Yansongda\Pay\Gateways\Wechat;
use Yansongda\Pay\Log; use Yansongda\Pay\Log;
use Yansongda\Pay\Pay; use Yansongda\Pay\Pay;
use Yansongda\Supports\Collection; use Yansongda\Supports\Collection;
class Wechat extends Service class Wxpay extends Payment
{ {
/** /**
@ -17,115 +20,50 @@ class Wechat extends Service
protected $config; protected $config;
/** /**
* @var \Yansongda\Pay\Gateways\Wechat * @var Wechat
*/ */
protected $gateway; protected $gateway;
public function __construct() public function __construct()
{ {
$this->config = $this->getSectionConfig('payment.wechat'); $this->config = $this->getSectionConfig('payment.wxpay');
$this->gateway = $this->getGateway(); $this->gateway = $this->getGateway();
} }
/** /**
* 关闭订单(扫码生成订单后可执行) * 扫码下单
* *
* @param string $outTradeNo * @param TradeModel $trade
* @return Collection|bool
*/
public function closeOrder($outTradeNo)
{
try {
$order = ['out_trade_no' => $outTradeNo];
$result = $this->gateway->close($order);
return $result;
} catch (\Exception $e) {
Log::error('Wechat Close Order Exception', [
'code' => $e->getCode(),
'message' => $e->getMessage(),
]);
return false;
}
}
/**
* 订单退款
*
* <code>
* $order = [
* 'out_trade_no' => '1514027114',
* 'out_refund_no' => '1734027115',
* 'total_fee' => 1,
* 'refund_fee' => 1,
* ];
* </code>
*
* @param array $order
* @return Collection|bool
*/
public function refundOrder($order)
{
try {
$result = $this->gateway->refund($order);
return $result;
} catch (\Exception $e) {
Log::error('Wechat Refund Order Exception', [
'code' => $e->getCode(),
'message' => $e->getMessage(),
]);
return false;
}
}
/**
* 获取二维码内容
*
* <code>
* $order = [
* 'out_trade_no' =>'1514027114',
* 'total_fee' => 1,
* 'body' => 'foo',
* ];
*</code>
*
* @param array $order
* @return bool|string * @return bool|string
*/ */
public function getQrCode($order) public function scan(TradeModel $trade)
{ {
try { try {
$response = $this->gateway->scan($order); $response = $this->gateway->scan([
'out_trade_no' => $trade->sn,
'total_fee' => 100 * $trade->amount,
'body' => $trade->subject,
]);
$result = $response->code_url ?? false; $result = $response->code_url ?? false;
return $result;
} catch (\Exception $e) { } catch (\Exception $e) {
Log::error('Wechat Scan Error', [ Log::error('Wxpay Scan Error', [
'code' => $e->getCode(), 'code' => $e->getCode(),
'message' => $e->getMessage(), 'message' => $e->getMessage(),
]); ]);
return false; $result = false;
} }
return $result;
} }
/** /**
* 处理异步通知 * 异步通知
*/ */
public function notify() public function notify()
{ {
@ -133,11 +71,11 @@ class Wechat extends Service
$data = $this->gateway->verify(); $data = $this->gateway->verify();
Log::debug('Wechat Verify Data', $data->all()); Log::debug('Wxpay Verify Data', $data->all());
} catch (\Exception $e) { } catch (\Exception $e) {
Log::error('Wechat Verify Error', [ Log::error('Wxpay Verify Error', [
'code' => $e->getCode(), 'code' => $e->getCode(),
'message' => $e->getMessage(), 'message' => $e->getMessage(),
]); ]);
@ -175,9 +113,114 @@ class Wechat extends Service
} }
/** /**
* 获取 Wechat Gateway * 查询交易(扫码生成订单后可执行)
* *
* @return \Yansongda\Pay\Gateways\Wechat * @param string $outTradeNo
* @return Collection|bool
*/
public function find($outTradeNo)
{
try {
$result = $this->gateway->find([
'out_trade_no' => $outTradeNo,
]);
} catch (\Exception $e) {
Log::error('Alipay Find Order Exception', [
'code' => $e->getCode(),
'message' => $e->getMessage(),
]);
$result = false;
}
return $result;
}
/**
* 关闭交易(扫码生成订单后可执行)
*
* @param string $outTradeNo
* @return bool
*/
public function close($outTradeNo)
{
try {
$response = $this->gateway->close([
'out_trade_no' => $outTradeNo,
]);
$result = $response->result_code == 'SUCCESS';
} catch (\Exception $e) {
Log::error('Wxpay Close Order Exception', [
'code' => $e->getCode(),
'message' => $e->getMessage(),
]);
$result = false;
}
return $result;
}
/**
* 取消交易
*
* @param string $outTradeNo
* @return bool
*/
public function cancel($outTradeNo)
{
$result = $this->close($outTradeNo);
return $result;
}
/**
* 申请退款
*
* @param RefundModel $refund
* @return Collection|bool
*/
public function refund(RefundModel $refund)
{
try {
$tradeRepo = new TradeRepo();
$trade = $tradeRepo->findById($refund->trade_id);
$response = $this->gateway->refund([
'out_trade_no' => $trade->sn,
'out_refund_no' => $refund->sn,
'total_fee' => 100 * $trade->amount,
'refund_fee' => 100 * $refund->amount,
]);
$result = $response->result_code == 'SUCCESS';
} catch (\Exception $e) {
Log::error('Wxpay Refund Order Exception', [
'code' => $e->getCode(),
'message' => $e->getMessage(),
]);
$result = false;
}
return $result;
}
/**
* 获取 Gateway
*
* @return Wechat
*/ */
public function getGateway() public function getGateway()
{ {
@ -191,7 +234,7 @@ class Wechat extends Service
'key' => $this->config['key'], 'key' => $this->config['key'],
'notify_url' => $this->config['notify_url'], 'notify_url' => $this->config['notify_url'],
'log' => [ 'log' => [
'file' => log_path('wechat.log'), 'file' => log_path('wxpay.log'),
'level' => $level, 'level' => $level,
'type' => 'daily', 'type' => 'daily',
'max_file' => 30, 'max_file' => 30,

View File

@ -2,74 +2,64 @@
namespace App\Services; namespace App\Services;
use Qcloud\Sms\SmsMultiSender; use Phalcon\Logger\Adapter\File as FileLogger;
use Qcloud\Sms\SmsSingleSender; use Qcloud\Sms\SmsSingleSender;
class Smser extends Service Abstract class Smser extends Service
{ {
/**
* @var array
*/
protected $config; protected $config;
/**
* @var FileLogger
*/
protected $logger; protected $logger;
public function __construct() public function __construct()
{ {
$this->config = $this->getSectionConfig('smser'); $this->config = $this->getSectionConfig('smser');
$this->logger = $this->getLogger('smser'); $this->logger = $this->getLogger('smser');
} }
public function register()
{
}
public function resetPassword()
{
}
public function buyCourse()
{
}
public function buyMember()
{
}
/** /**
* 发送测试短信 * 发送短信
* *
* @param string $phone * @param string $phoneNumber
* @param string $templateId
* @param array $params
* @return bool * @return bool
*/ */
public function sendTestMessage($phone) public function send($phoneNumber, $templateId, $params)
{ {
$sender = $this->createSingleSender(); $sender = $this->createSingleSender();
$templateId = $this->getTemplateId('register');
$signature = $this->getSignature();
$params = [888888, 5]; $signature = $this->getSignature();
try { try {
$response = $sender->sendWithParam('86', $phone, $templateId, $params, $signature); $response = $sender->sendWithParam('86', $phoneNumber, $templateId, $params, $signature);
$this->logger->debug('Send Test Message Response ' . $response); $this->logger->debug('Send Message Response ' . $response);
$content = json_decode($response, true); $content = json_decode($response, true);
return $content['result'] == 0 ? true : false; $result = $content['result'] == 0;
} catch (\Exception $e) { } catch (\Exception $e) {
$this->logger->error('Send Test Message Exception ' . kg_json_encode([ $this->logger->error('Send Message Exception ' . kg_json_encode([
'code' => $e->getCode(), 'code' => $e->getCode(),
'message' => $e->getMessage(), 'message' => $e->getMessage(),
])); ]));
return false; $result = false;
} }
return $result;
} }
protected function createSingleSender() protected function createSingleSender()
@ -79,20 +69,6 @@ class Smser extends Service
return $sender; return $sender;
} }
protected function createMultiSender()
{
$sender = new SmsMultiSender($this->config['app_id'], $this->config['app_key']);
return $sender;
}
protected function getRandNumber()
{
$result = rand(100, 999) . rand(100, 999);
return $result;
}
protected function getTemplateId($code) protected function getTemplateId($code)
{ {
$template = json_decode($this->config['template'], true); $template = json_decode($this->config['template'], true);

View File

@ -0,0 +1,50 @@
<?php
namespace App\Services\Smser;
use App\Repos\Account as AccountRepo;
use App\Repos\Chapter as ChapterRepo;
use App\Repos\Course as CourseRepo;
use App\Services\Smser;
class Live extends Smser
{
protected $templateCode = 'live';
/**
* @param int $chapterId
* @param int $userId
* @param int $startTime
* @return bool|null
*/
public function handle($chapterId, $userId, $startTime)
{
$accountRepo = new AccountRepo();
$account = $accountRepo->findById($userId);
if (!$account->phone) {
return null;
}
$chapterRepo = new ChapterRepo();
$chapter = $chapterRepo->findById($chapterId);
$courseRepo = new CourseRepo();
$course = $courseRepo->findById($chapter->course_id);
$params = [
$course->title,
$chapter->title,
$startTime,
];
$templateId = $this->getTemplateId($this->templateCode);
return $this->send($account->phone, $templateId, $params);
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Services\Smser;
use App\Models\Order as OrderModel;
use App\Repos\Account as AccountRepo;
use App\Services\Smser;
class Order extends Smser
{
protected $templateCode = 'order';
public function handle(OrderModel $order)
{
$accountRepo = new AccountRepo();
$account = $accountRepo->findById($order->user_id);
if (!$account->phone) {
return null;
}
$templateId = $this->getTemplateId($this->templateCode);
$params = [
$order->subject,
$order->sn,
$order->amount,
];
return $this->send($account->phone, $templateId, $params);
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Services\Smser;
use App\Models\Refund as RefundModel;
use App\Repos\Account as AccountRepo;
use App\Services\Smser;
class Refund extends Smser
{
protected $templateCode = 'refund';
public function handle(RefundModel $refund)
{
$accountRepo = new AccountRepo();
$account = $accountRepo->findById($refund->user_id);
if (!$account->phone) {
return null;
}
$templateId = $this->getTemplateId($this->templateCode);
$params = [
$refund->subject,
$refund->sn,
$refund->amount,
];
return $this->send($account->phone, $templateId, $params);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Services\Smser;
use App\Services\Smser;
class Test extends Smser
{
public function handle($phoneNumber)
{
$identity = new Verify();
$result = $identity->handle($phoneNumber);
return $result;
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Services\Smser;
use App\Library\Util\Verification;
use App\Services\Smser;
class Verify extends Smser
{
protected $templateCode = 'verify';
public function handle($phone)
{
$minutes = 5;
$code = Verification::code($phone, 60 * $minutes);
$templateId = $this->getTemplateId($this->templateCode);
$params = [$code, $minutes];
return $this->send($phone, $templateId, $params);
}
}

View File

@ -3,19 +3,33 @@
namespace App\Services; namespace App\Services;
use App\Models\ContentImage as ContentImageModel; use App\Models\ContentImage as ContentImageModel;
use Phalcon\Logger\Adapter\File as FileLogger;
use Qcloud\Cos\Client as CosClient; use Qcloud\Cos\Client as CosClient;
class Storage extends Service class Storage extends Service
{ {
/**
* @var array
*/
protected $config; protected $config;
/**
* @var FileLogger
*/
protected $logger; protected $logger;
/**
* @var CosClient
*/
protected $client; protected $client;
public function __construct() public function __construct()
{ {
$this->config = $this->getSectionConfig('storage'); $this->config = $this->getSectionConfig('storage');
$this->logger = $this->getLogger('storage'); $this->logger = $this->getLogger('storage');
$this->client = $this->getCosClient(); $this->client = $this->getCosClient();
} }
@ -49,7 +63,7 @@ class Storage extends Service
/** /**
* 上传内容图片 * 上传内容图片
* *
* @return mixed * @return string|bool
*/ */
public function uploadContentImage() public function uploadContentImage()
{ {
@ -74,7 +88,7 @@ class Storage extends Service
/** /**
* 上传头像图片 * 上传头像图片
* *
* @return mixed * @return string|bool
*/ */
public function uploadAvatarImage() public function uploadAvatarImage()
{ {
@ -87,7 +101,7 @@ class Storage extends Service
* 上传图片 * 上传图片
* *
* @param string $prefix * @param string $prefix
* @return mixed * @return string|bool
*/ */
public function uploadImage($prefix = '') public function uploadImage($prefix = '')
{ {
@ -117,7 +131,7 @@ class Storage extends Service
* *
* @param string $key * @param string $key
* @param string $body * @param string $body
* @return mixed string|bool * @return string|bool
*/ */
public function putString($key, $body) public function putString($key, $body)
{ {
@ -129,8 +143,6 @@ class Storage extends Service
$result = $response['Location'] ? $key : false; $result = $response['Location'] ? $key : false;
return $result;
} catch (\Exception $e) { } catch (\Exception $e) {
$this->logger->error('Put String Exception ' . kg_json_encode([ $this->logger->error('Put String Exception ' . kg_json_encode([
@ -138,8 +150,10 @@ class Storage extends Service
'message' => $e->getMessage(), 'message' => $e->getMessage(),
])); ]));
return false; $result = false;
} }
return $result;
} }
/** /**
@ -161,8 +175,6 @@ class Storage extends Service
$result = $response['Location'] ? $key : false; $result = $response['Location'] ? $key : false;
return $result;
} catch (\Exception $e) { } catch (\Exception $e) {
$this->logger->error('Put File Exception ' . kg_json_encode([ $this->logger->error('Put File Exception ' . kg_json_encode([
@ -170,8 +182,10 @@ class Storage extends Service
'message' => $e->getMessage(), 'message' => $e->getMessage(),
])); ]));
return false; $result = false;
} }
return $result;
} }
/** /**
@ -271,7 +285,7 @@ class Storage extends Service
$client = new CosClient([ $client = new CosClient([
'region' => $this->config['bucket_region'], 'region' => $this->config['bucket_region'],
'schema' => 'https', 'schema' => $this->config['bucket_protocol'],
'credentials' => [ 'credentials' => [
'secretId' => $secret['secret_id'], 'secretId' => $secret['secret_id'],
'secretKey' => $secret['secret_key'], 'secretKey' => $secret['secret_key'],

View File

@ -2,6 +2,7 @@
namespace App\Services; namespace App\Services;
use Phalcon\Logger\Adapter\File as FileLogger;
use TencentCloud\Common\Credential; use TencentCloud\Common\Credential;
use TencentCloud\Common\Exception\TencentCloudSDKException; use TencentCloud\Common\Exception\TencentCloudSDKException;
use TencentCloud\Common\Profile\ClientProfile; use TencentCloud\Common\Profile\ClientProfile;
@ -19,14 +20,27 @@ class Vod extends Service
const END_POINT = 'vod.tencentcloudapi.com'; const END_POINT = 'vod.tencentcloudapi.com';
/**
* @var array
*/
protected $config; protected $config;
/**
* @var VodClient
*/
protected $client; protected $client;
/**
* @var FileLogger
*/
protected $logger; protected $logger;
public function __construct() public function __construct()
{ {
$this->config = $this->getSectionConfig('vod'); $this->config = $this->getSectionConfig('vod');
$this->logger = $this->getLogger('vod'); $this->logger = $this->getLogger('vod');
$this->client = $this->getVodClient(); $this->client = $this->getVodClient();
} }
@ -49,9 +63,7 @@ class Vod extends Service
$this->logger->debug('Describe Audio Track Templates Response ' . $response->toJsonString()); $this->logger->debug('Describe Audio Track Templates Response ' . $response->toJsonString());
$result = $response->TotalCount > 0 ? true : false; $result = $response->TotalCount > 0;
return $result;
} catch (TencentCloudSDKException $e) { } catch (TencentCloudSDKException $e) {
@ -61,8 +73,10 @@ class Vod extends Service
'requestId' => $e->getRequestId(), 'requestId' => $e->getRequestId(),
])); ]));
return false; $result = false;
} }
return $result;
} }
/** /**
@ -205,9 +219,9 @@ class Vod extends Service
$this->logger->debug('Pull Events Response ' . $response->toJsonString()); $this->logger->debug('Pull Events Response ' . $response->toJsonString());
$result = json_decode($response->toJsonString(), true); $data = json_decode($response->toJsonString(), true);
return $result['EventSet'] ?? []; $result = $data['EventSet'] ?? [];
} catch (TencentCloudSDKException $e) { } catch (TencentCloudSDKException $e) {
@ -217,8 +231,10 @@ class Vod extends Service
'requestId' => $e->getRequestId(), 'requestId' => $e->getRequestId(),
])); ]));
return false; $result = false;
} }
return $result;
} }
/** /**
@ -245,8 +261,6 @@ class Vod extends Service
$result = json_decode($response->toJsonString(), true); $result = json_decode($response->toJsonString(), true);
return $result;
} catch (TencentCloudSDKException $e) { } catch (TencentCloudSDKException $e) {
$this->logger->error('Confirm Events Exception ' . kg_json_encode([ $this->logger->error('Confirm Events Exception ' . kg_json_encode([
@ -255,8 +269,10 @@ class Vod extends Service
'requestId' => $e->getRequestId(), 'requestId' => $e->getRequestId(),
])); ]));
return false; $result = false;
} }
return $result;
} }
/** /**
@ -289,8 +305,6 @@ class Vod extends Service
return false; return false;
} }
return $result;
} catch (TencentCloudSDKException $e) { } catch (TencentCloudSDKException $e) {
$this->logger->error('Describe Media Info Exception ' . kg_json_encode([ $this->logger->error('Describe Media Info Exception ' . kg_json_encode([
@ -299,8 +313,10 @@ class Vod extends Service
'requestId' => $e->getRequestId(), 'requestId' => $e->getRequestId(),
])); ]));
return false; $result = false;
} }
return $result;
} }
/** /**
@ -331,8 +347,6 @@ class Vod extends Service
return false; return false;
} }
return $result;
} catch (TencentCloudSDKException $e) { } catch (TencentCloudSDKException $e) {
$this->logger->error('Describe Task Detail Exception ' . kg_json_encode([ $this->logger->error('Describe Task Detail Exception ' . kg_json_encode([
@ -341,8 +355,10 @@ class Vod extends Service
'requestId' => $e->getRequestId(), 'requestId' => $e->getRequestId(),
])); ]));
return false; $result = false;
} }
return $result;
} }
/** /**
@ -364,13 +380,18 @@ class Vod extends Service
$transCodeTaskSet = []; $transCodeTaskSet = [];
foreach ($videoTransTemplates as $key => $template) { foreach ($videoTransTemplates as $key => $template) {
$caseA = $originVideoInfo['width'] >= $template['width']; $caseA = $originVideoInfo['width'] >= $template['width'];
$caseB = $originVideoInfo['bit_rate'] >= 1000 * $template['bit_rate']; $caseB = $originVideoInfo['bit_rate'] >= 1000 * $template['bit_rate'];
if ($caseA || $caseB) { if ($caseA || $caseB) {
$item = ['Definition' => $key]; $item = ['Definition' => $key];
if ($watermarkTemplate) { if ($watermarkTemplate) {
$item['WatermarkSet'][] = ['Definition' => $watermarkTemplate]; $item['WatermarkSet'][] = ['Definition' => $watermarkTemplate];
} }
$transCodeTaskSet[] = $item; $transCodeTaskSet[] = $item;
} }
} }
@ -379,11 +400,15 @@ class Vod extends Service
* 无匹配转码模板,取第一项转码 * 无匹配转码模板,取第一项转码
*/ */
if (empty($transCodeTaskSet)) { if (empty($transCodeTaskSet)) {
$keys = array_keys($videoTransTemplates); $keys = array_keys($videoTransTemplates);
$item = ['Definition' => $keys[0]]; $item = ['Definition' => $keys[0]];
if ($watermarkTemplate) { if ($watermarkTemplate) {
$item['WatermarkSet'][] = ['Definition' => $watermarkTemplate]; $item['WatermarkSet'][] = ['Definition' => $watermarkTemplate];
} }
$transCodeTaskSet[] = $item; $transCodeTaskSet[] = $item;
} }
@ -408,8 +433,6 @@ class Vod extends Service
$result = $response->TaskId ?: false; $result = $response->TaskId ?: false;
return $result;
} catch (TencentCloudSDKException $e) { } catch (TencentCloudSDKException $e) {
$this->logger->error('Process Media Exception ' . kg_json_encode([ $this->logger->error('Process Media Exception ' . kg_json_encode([
@ -418,8 +441,10 @@ class Vod extends Service
'requestId' => $e->getRequestId(), 'requestId' => $e->getRequestId(),
])); ]));
return false; $result = false;
} }
return $result;
} }
/** /**
@ -439,8 +464,11 @@ class Vod extends Service
$transCodeTaskSet = []; $transCodeTaskSet = [];
foreach ($audioTransTemplates as $key => $template) { foreach ($audioTransTemplates as $key => $template) {
if ($originAudioInfo['bit_rate'] >= 1000 * $template['bit_rate']) { if ($originAudioInfo['bit_rate'] >= 1000 * $template['bit_rate']) {
$item = ['Definition' => $key]; $item = ['Definition' => $key];
$transCodeTaskSet[] = $item; $transCodeTaskSet[] = $item;
} }
} }
@ -449,8 +477,11 @@ class Vod extends Service
* 无匹配转码模板,取第一项转码 * 无匹配转码模板,取第一项转码
*/ */
if (empty($transCodeTaskSet)) { if (empty($transCodeTaskSet)) {
$keys = array_keys($audioTransTemplates); $keys = array_keys($audioTransTemplates);
$item = ['Definition' => $keys[0]]; $item = ['Definition' => $keys[0]];
$transCodeTaskSet[] = $item; $transCodeTaskSet[] = $item;
} }
@ -475,8 +506,6 @@ class Vod extends Service
$result = $response->TaskId ?: false; $result = $response->TaskId ?: false;
return $result;
} catch (TencentCloudSDKException $e) { } catch (TencentCloudSDKException $e) {
$this->logger->error('Process Media Exception ' . kg_json_encode([ $this->logger->error('Process Media Exception ' . kg_json_encode([
@ -485,8 +514,10 @@ class Vod extends Service
'requestId' => $e->getRequestId(), 'requestId' => $e->getRequestId(),
])); ]));
return false; $result = false;
} }
return $result;
} }
/** /**

View File

@ -4,6 +4,8 @@ namespace App\Validators;
use App\Exceptions\BadRequest as BadRequestException; use App\Exceptions\BadRequest as BadRequestException;
use App\Library\Validator\Common as CommonValidator; use App\Library\Validator\Common as CommonValidator;
use App\Models\Course as CourseModel;
use App\Models\User as UserModel;
use App\Repos\Course as CourseRepo; use App\Repos\Course as CourseRepo;
use App\Repos\CourseUser as CourseUserRepo; use App\Repos\CourseUser as CourseUserRepo;
use App\Repos\User as UserRepo; use App\Repos\User as UserRepo;
@ -65,6 +67,16 @@ class CourseUser extends Validator
return strtotime($value); return strtotime($value);
} }
public function checkIfAllowApply(CourseModel $course, UserModel $user)
{
$caseA = $course->market_price > 0;
$caseB = $user->vip == 0 && $course->vip_price > 0;
if ($caseA || $caseB) {
throw new BadRequestException('course_user.apply_not_allowed');
}
}
public function checkIfJoined($courseId, $userId) public function checkIfJoined($courseId, $userId)
{ {
$repo = new CourseUserRepo(); $repo = new CourseUserRepo();
@ -72,7 +84,7 @@ class CourseUser extends Validator
$courseUser = $repo->findCourseStudent($courseId, $userId); $courseUser = $repo->findCourseStudent($courseId, $userId);
if ($courseUser) { if ($courseUser) {
throw new BadRequestException('course_user.user_has_joined'); throw new BadRequestException('course_user.has_joined_course');
} }
} }

View File

@ -38,33 +38,55 @@ class Order extends Validator
return $order; return $order;
} }
public function checkItem($itemId, $itemType) public function checkItemType($itemType)
{
$list = OrderModel::itemTypes();
if (!isset($list[$itemType])) {
throw new BadRequestException('order.invalid_item_type');
}
return $itemType;
}
public function checkItemCourse($itemId)
{ {
if ($itemType == OrderModel::ITEM_COURSE) {
$courseRepo = new CourseRepo(); $courseRepo = new CourseRepo();
$item = $courseRepo->findById($itemId); $item = $courseRepo->findById($itemId);
if (!$item) { if (!$item) {
throw new BadRequestException('order.item_not_found'); throw new BadRequestException('order.item_not_found');
} }
} elseif ($itemType == OrderModel::ITEM_PACKAGE) {
$packageRepo = new PackageRepo();
$item = $packageRepo->findById($itemId);
if (!$item) {
throw new BadRequestException('order.item_not_found');
}
} elseif ($itemType == OrderModel::ITEM_VIP) {
$vipRepo = new VipRepo();
$item = $vipRepo->findById($itemId);
if (!$item) {
throw new BadRequestException('order.item_not_found');
}
} else {
throw new BadRequestException('order.item_not_found');
}
return $item; return $item;
} }
public function checkItemPackage($itemId)
{
$packageRepo = new PackageRepo();
$item = $packageRepo->findById($itemId);
if (!$item) {
throw new BadRequestException('order.item_not_found');
}
return $item;
}
public function checkItemVip($itemId)
{
$vipRepo = new VipRepo();
$item = $vipRepo->findById($itemId);
if (!$item) {
throw new BadRequestException('order.item_not_found');
}
return $item;
}
public function checkAmount($amount) public function checkAmount($amount)
{ {
$value = $this->filter->sanitize($amount, ['trim', 'float']); $value = $this->filter->sanitize($amount, ['trim', 'float']);
@ -83,18 +105,6 @@ class Order extends Validator
} }
} }
public function checkIfBought($userId, $itemId, $itemType)
{
switch ($itemType) {
case OrderModel::ITEM_COURSE:
$this->checkIfBoughtCourse($userId, $itemId);
break;
case OrderModel::ITEM_PACKAGE:
$this->checkIfBoughtPackage($userId, $itemId);
break;
}
}
public function checkIfBoughtCourse($userId, $courseId) public function checkIfBoughtCourse($userId, $courseId)
{ {
$orderRepo = new OrderRepo(); $orderRepo = new OrderRepo();

View File

@ -27,7 +27,7 @@ class Validator extends Component
return $user; return $user;
} }
public function checkOwnerPriv($userId, $ownerId) public function checkOwner($userId, $ownerId)
{ {
if ($userId != $ownerId) { if ($userId != $ownerId) {
throw new ForbiddenException('sys.access_denied'); throw new ForbiddenException('sys.access_denied');

View File

@ -91,10 +91,6 @@ $error['course.invalid_publish_status'] = '无效的发布状态';
$error['course.pub_chapter_not_found'] = '尚未发现已发布的课时'; $error['course.pub_chapter_not_found'] = '尚未发现已发布的课时';
$error['course.pub_chapter_not_enough'] = '已发布的课时太少(未过三分之一)'; $error['course.pub_chapter_not_enough'] = '已发布的课时太少(未过三分之一)';
$error['course.has_applied'] = '已经参加过该课程了';
$error['course.has_not_applied'] = '尚未参加该课程';
$error['course.has_reviewed'] = '已经评价过该课程了';
/** /**
* 话题相关 * 话题相关
*/ */
@ -121,7 +117,8 @@ $error['package.invalid_publish_status'] = '无效的发布状态';
$error['course_user.not_found'] = '课程学员不存在'; $error['course_user.not_found'] = '课程学员不存在';
$error['course_user.course_not_found'] = '课程不存在'; $error['course_user.course_not_found'] = '课程不存在';
$error['course_user.user_not_found'] = '用户不存在'; $error['course_user.user_not_found'] = '用户不存在';
$error['course_user.user_has_joined'] = '课程学员已存在'; $error['course_user.apply_not_allowed'] = '当前不允许申请课程';
$error['course_user.has_joined_course'] = '已经加入当前课程';
$error['course_user.invalid_expiry_time'] = '无效的过期时间'; $error['course_user.invalid_expiry_time'] = '无效的过期时间';
/** /**
@ -240,7 +237,6 @@ $error['slide.invalid_publish_status'] = '无效的发布状态';
*/ */
$error['order.not_found'] = '订单不存在'; $error['order.not_found'] = '订单不存在';
$error['order.item_not_found'] = '商品不存在'; $error['order.item_not_found'] = '商品不存在';
$error['order.reach_daily_limit'] = '超出每日订单限额';
$error['order.close_not_allowed'] = '当前不允许关闭订单'; $error['order.close_not_allowed'] = '当前不允许关闭订单';
$error['order.has_bought_course'] = '已经够买过该课程'; $error['order.has_bought_course'] = '已经够买过该课程';
$error['order.has_bought_package'] = '已经够买过该套餐'; $error['order.has_bought_package'] = '已经够买过该套餐';

View File

@ -161,14 +161,19 @@ img.kg-cover {
text-align: center; text-align: center;
} }
.kg-qrcode-block .layui-icon-close-fill { .kg-error-tips {
color: red; color: red;
} }
.kg-qrcode-block .layui-icon-ok-circle { .kg-success-tips {
color: green; color: green;
} }
img.kg-qrcode {
width: 140px;
height: 140px;
}
.kg-order-date { .kg-order-date {
margin-left: 30px; margin-left: 30px;
} }

View File

@ -17,11 +17,14 @@ $scheduler->php($script, $bin, ['--task' => 'process_order', '--action' => 'main
->at('*/5 * * * *'); ->at('*/5 * * * *');
$scheduler->php($script, $bin, ['--task' => 'vod_event', '--action' => 'main']) $scheduler->php($script, $bin, ['--task' => 'vod_event', '--action' => 'main'])
->at('*/7 * * * *'); ->at('*/5 * * * *');
$scheduler->php($script, $bin, ['--task' => 'close_trade', '--action' => 'main']) $scheduler->php($script, $bin, ['--task' => 'close_trade', '--action' => 'main'])
->at('*/10 * * * *'); ->at('*/10 * * * *');
$scheduler->php($script, $bin, ['--task' => 'live_notice_consumer', '--action' => 'main'])
->at('*/15 * * * *');
$scheduler->php($script, $bin, ['--task' => 'close_order', '--action' => 'main']) $scheduler->php($script, $bin, ['--task' => 'close_order', '--action' => 'main'])
->hourly(3); ->hourly(3);
@ -43,4 +46,7 @@ $scheduler->php($script, $bin, ['--task' => 'revoke_vip', '--action' => 'main'])
$scheduler->php($script, $bin, ['--task' => 'count_course', '--action' => 'main']) $scheduler->php($script, $bin, ['--task' => 'count_course', '--action' => 'main'])
->daily(3, 17); ->daily(3, 17);
$scheduler->php($script, $bin, ['--task' => 'live_notice_provider', '--action' => 'main'])
->daily(3, 23);
$scheduler->run(); $scheduler->run();