1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-06-20 19:07:03 +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->cleanStorageLog();
$this->cleanAlipayLog();
$this->cleanWechatLog();
$this->cleanWxpayLog();
$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;
use App\Models\Trade as TradeModel;
use App\Services\Alipay as AlipayService;
use App\Services\Wechat as WechatService;
use App\Services\Payment\Alipay as AlipayService;
use App\Services\Payment\Wxpay as WxpayService;
use Phalcon\Cli\Task;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
@ -23,8 +23,8 @@ class CloseTradeTask extends Task
foreach ($trades as $trade) {
if ($trade->channel == TradeModel::CHANNEL_ALIPAY) {
$this->closeAlipayTrade($trade);
} elseif ($trade->channel == TradeModel::CHANNEL_WECHAT) {
$this->closeWechatTrade($trade);
} elseif ($trade->channel == TradeModel::CHANNEL_WXPAY) {
$this->closeWxpayTrade($trade);
}
}
}
@ -36,48 +36,38 @@ class CloseTradeTask extends Task
*/
protected function closeAlipayTrade($trade)
{
$service = new AlipayService();
$alipay = new AlipayService();
$alyOrder = $service->findOrder($trade->sn);
if ($alyOrder) {
if ($alyOrder->trade_status == 'WAIT_BUYER_PAY') {
$service->closeOrder($trade->sn);
}
}
$success = $alipay->close($trade->sn);
if ($success) {
$trade->status = TradeModel::STATUS_CLOSED;
$trade->update();
}
}
/**
* 关闭微信交易
*
* @param TradeModel $trade
*/
protected function closeWechatTrade($trade)
protected function closeWxpayTrade($trade)
{
$service = new WechatService();
$wxpay = new WxpayService();
$wxOrder = $service->findOrder($trade->sn);
if ($wxOrder) {
if ($wxOrder->trade_state == 'NOTPAY') {
$service->closeOrder($trade->sn);
}
}
$success = $wxpay->close($trade->sn);
if ($success) {
$trade->status = TradeModel::STATUS_CLOSED;
$trade->update();
}
}
/**
* 查找待关闭交易
*
* @param int $limit
* @return Resultset|ResultsetInterface
* @return ResultsetInterface|Resultset|TradeModel[]
*/
protected function findTrades($limit = 5)
{

View File

@ -41,11 +41,13 @@ class LearningTask extends Task
if (!$requestIds) return;
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\Order as OrderModel;
use App\Models\Refund as RefundModel;
use App\Models\Task as TaskModel;
use App\Models\Trade as TradeModel;
use App\Repos\Course as CourseRepo;
use App\Repos\CourseUser as CourseUserRepo;
use App\Repos\Order as OrderRepo;
use App\Repos\User as UserRepo;
use App\Services\Smser\Order as OrderSmser;
use Phalcon\Cli\Task;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class ProcessOrderTask extends Task
class OrderTask extends Task
{
const TRY_COUNT = 3;
@ -30,8 +34,6 @@ class ProcessOrderTask extends Task
foreach ($tasks as $task) {
try {
/**
* @var array $itemInfo
*/
@ -39,6 +41,10 @@ class ProcessOrderTask extends Task
$order = $orderRepo->findById($itemInfo['order']['id']);
if (!$order) continue;
try {
switch ($order->item_type) {
case OrderModel::ITEM_COURSE:
$this->handleCourseOrder($order);
@ -51,6 +57,12 @@ class ProcessOrderTask extends Task
break;
}
$task->status = TaskModel::STATUS_FINISHED;
$task->update();
$this->handleOrderNotice($order);
} catch (\Exception $e) {
$task->try_count += 1;
@ -62,6 +74,13 @@ class ProcessOrderTask extends Task
$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 $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
* @return ResultsetInterface|Resultset|TaskModel[]
*/
protected function findTasks($limit = 100)
{
$itemType = TaskModel::TYPE_PROCESS_ORDER;
$itemType = TaskModel::TYPE_ORDER;
$status = TaskModel::STATUS_PENDING;
$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\Trade as TradeRepo;
use App\Repos\User as UserRepo;
use App\Services\Alipay as AlipayService;
use App\Services\Wechat as WechatService;
use App\Services\Payment\Alipay as AlipayService;
use App\Services\Payment\Wxpay as WxpayService;
use App\Services\Smser\Refund as RefundSmser;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
@ -49,6 +50,10 @@ class RefundTask extends Task
$trade = $tradeRepo->findById($itemInfo['refund']['trade_id']);
$order = $orderRepo->findById($itemInfo['refund']['order_id']);
if (!$refund || !$trade || !$order) {
continue;
}
try {
$this->db->begin();
@ -83,6 +88,8 @@ class RefundTask extends Task
$this->db->commit();
$this->handleRefundNotice($refund);
} catch (\Exception $e) {
$this->db->rollback();
@ -92,8 +99,6 @@ class RefundTask extends Task
if ($task->try_count > self::TRY_COUNT) {
$task->status = TaskModel::STATUS_FAILED;
$refund->status = RefundModel::STATUS_FAILED;
$refund->update();
}
$task->update();
@ -103,6 +108,11 @@ class RefundTask extends Task
'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();
$response = $alipay->refundOrder([
'out_trade_no' => $trade->sn,
'out_request_no' => $refund->sn,
'refund_amount' => $refund->amount,
]);
$response = $alipay->refund($refund);
} elseif ($trade->channel == TradeModel::CHANNEL_WECHAT) {
} elseif ($trade->channel == TradeModel::CHANNEL_WXPAY) {
$wechat = new WechatService();
$wxpay = new WxpayService();
$response = $wechat->refundOrder([
'out_trade_no' => $trade->sn,
'out_refund_no' => $refund->sn,
'total_fee' => 100 * $trade->amount,
'refund_fee' => 100 * $refund->amount,
]);
$response = $wxpay->refund($refund);
}
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
* @return ResultsetInterface|Resultset|TaskModel[]

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,7 +10,7 @@
{%- macro category_info(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 %}
{%- endmacro %}

View File

@ -11,13 +11,6 @@
</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">
<label class="layui-form-label">标题</label>
<div class="layui-input-block">
@ -37,7 +30,7 @@
<div class="layui-input-block">
<input type="radio" name="model" value="vod" 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>

View File

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

View File

@ -22,7 +22,7 @@
<label class="layui-form-label">交易平台</label>
<div class="layui-input-block">
<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>

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');
$level = $this->request->getQuery('level', 'int', 0);
@ -76,7 +76,9 @@ class PublicController extends \Phalcon\Mvc\Controller
$url = urldecode($text);
echo QRcode::png($url, false, $level, $size);
QRcode::png($url, false, $level, $size);
$this->response->send();
exit;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -125,7 +125,7 @@ class Order extends Repository
* @param string $itemType
* @return OrderModel|Model|bool
*/
public function findLastUserOrder($userId, $itemId, $itemType)
public function findLastUserItem($userId, $itemId, $itemType)
{
$result = OrderModel::findFirst([
'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[]
*/
public function findAll($where = [])

View File

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

View File

@ -27,7 +27,7 @@ class ChapterComment extends Service
$validator = new UserDailyLimitValidator();
$validator->checkCommentLimit($user->id);
$validator->checkCommentLimit($user);
$validator = new CommentValidator();
@ -62,6 +62,11 @@ class ChapterComment extends Service
$this->incrUserDailyCommentCount($user);
}
protected function handleMentions($mentions)
{
}
protected function incrUserDailyCommentCount(UserModel $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\Repos\Chapter as ChapterRepo;
use App\Repos\Comment as CommentRepo;
use Phalcon\Mvc\Model\Resultset;
class ChapterCommentList extends Service
{
@ -104,9 +103,6 @@ class ChapterCommentList extends Service
$chapterRepo = new ChapterRepo();
/**
* @var Resultset $votes
*/
$votes = $chapterRepo->findUserCommentVotes($chapterId, $userId);
if ($votes->count() == 0) {

View File

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

View File

@ -18,6 +18,8 @@ class CourseUser extends Service
$validator = new CourseUserValidator();
$validator->checkIfAllowApply($course, $user);
$validator->checkIfJoined($course->id, $user->id);
$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();
/**
* @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);
$validator->checkItemType($post['item_type']);
$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) {
$caseA = $order->status == OrderModel::STATUS_PENDING;
$caseB = time() - $order->created_at < 6 * 3600;
if ($caseA && $caseB) {
return $order;
}
}
$order = new OrderModel();
if ($post['item_type'] == OrderModel::ITEM_COURSE) {
switch ($post['item_type']) {
case OrderModel::ITEM_COURSE:
$order = $this->createCourseOrder($item, $user);
break;
case OrderModel::ITEM_PACKAGE:
$order = $this->createPackageOrder($item, $user);
break;
case OrderModel::ITEM_VIP:
$order = $this->createVipOrder($item, $user);
break;
$course = $validator->checkItemCourse($post['item_id']);
$validator->checkIfBoughtCourse($user->id, $course->id);
$order = $this->createCourseOrder($course, $user);
} elseif ($post['item_type'] == OrderModel::ITEM_PACKAGE) {
$package = $validator->checkItemPackage($post['item_id']);
$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);
@ -78,7 +82,7 @@ class OrderCreate extends Service
* @param UserModel $user
* @return OrderModel $order
*/
public function createCourseOrder($course, $user)
public function createCourseOrder(CourseModel $course, UserModel $user)
{
$studyExpiryTime = strtotime("+{$course->study_expiry} months");
$refundExpiryTime = strtotime("+{$course->refund_expiry} days");
@ -118,7 +122,7 @@ class OrderCreate extends Service
* @param UserModel $user
* @return OrderModel $order
*/
public function createPackageOrder($package, $user)
public function createPackageOrder(PackageModel $package, UserModel $user)
{
$packageRepo = new PackageRepo();
@ -175,7 +179,7 @@ class OrderCreate extends Service
* @param UserModel $user
* @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();
$expiryTime = strtotime("+{$vip->expiry} months", $baseTime);
@ -184,8 +188,8 @@ class OrderCreate extends Service
'vip' => [
'id' => $vip->id,
'title' => $vip->title,
'expiry' => $vip->expiry,
'price' => $vip->price,
'expiry' => $vip->expiry,
'expiry_time' => $expiryTime,
]
];
@ -204,6 +208,9 @@ class OrderCreate extends Service
return $order;
}
/**
* @param UserModel $user
*/
protected function incrUserDailyOrderCount(UserModel $user)
{
$this->eventsManager->fire('userDailyCounter:incrOrderCount', $this, $user);

View File

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

View File

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

View File

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

View File

@ -2,38 +2,31 @@
namespace App\Services;
use Phalcon\Logger\Adapter\File as FileLogger;
use Phalcon\Mailer\Manager as MailerManager;
class Mailer extends Service
abstract class Mailer extends Service
{
/**
* @var MailerManager
*/
protected $manager;
/**
* @var FileLogger
*/
protected $logger;
public function __construct()
{
$this->manager = $this->getManager();
$this->logger = $this->getLogger('mailer');
}
/**
* 发送测试邮件
*
* @param string $email
* @return mixed
*/
public function sendTestMail($email)
{
$message = $this->manager->createMessage();
$result = $message->to($email)
->subject('这是一封测试邮件')
->content('这是一封测试邮件')
->send();
return $result;
}
/**
* 获取Manager
* 获取 Manager
*/
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
namespace App\Services;
namespace App\Services\Payment;
use App\Models\Refund as RefundModel;
use App\Models\Trade as TradeModel;
use App\Repos\Trade as TradeRepo;
use App\Services\Payment;
use Yansongda\Pay\Log;
use Yansongda\Pay\Pay;
use Yansongda\Supports\Collection;
class Alipay extends Service
class Alipay extends Payment
{
/**
@ -29,142 +31,23 @@ class Alipay extends Service
}
/**
* 查询订单(扫码生成订单后可执行)
* 扫码下单
*
* @param string $outTradeNo
* @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
* @param TradeModel $trade
* @return bool|string
*/
public function getQrCode($order)
public function scan(TradeModel $trade)
{
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;
return $result;
} catch (\Exception $e) {
Log::error('Alipay Qrcode Exception', [
@ -172,14 +55,16 @@ class Alipay extends Service
'message' => $e->getMessage(),
]);
return false;
$result = false;
}
return $result;
}
/**
* 处理异步通知
**/
public function handleNotify()
* 异步通知
*/
public function notify()
{
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
*/

View File

@ -1,14 +1,17 @@
<?php
namespace App\Services;
namespace App\Services\Payment;
use App\Models\Refund as RefundModel;
use App\Models\Trade as TradeModel;
use App\Repos\Trade as TradeRepo;
use App\Services\Payment;
use Yansongda\Pay\Gateways\Wechat;
use Yansongda\Pay\Log;
use Yansongda\Pay\Pay;
use Yansongda\Supports\Collection;
class Wechat extends Service
class Wxpay extends Payment
{
/**
@ -17,115 +20,50 @@ class Wechat extends Service
protected $config;
/**
* @var \Yansongda\Pay\Gateways\Wechat
* @var Wechat
*/
protected $gateway;
public function __construct()
{
$this->config = $this->getSectionConfig('payment.wechat');
$this->config = $this->getSectionConfig('payment.wxpay');
$this->gateway = $this->getGateway();
}
/**
* 关闭订单(扫码生成订单后可执行)
* 扫码下单
*
* @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('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
* @param TradeModel $trade
* @return bool|string
*/
public function getQrCode($order)
public function scan(TradeModel $trade)
{
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;
return $result;
} catch (\Exception $e) {
Log::error('Wechat Scan Error', [
Log::error('Wxpay Scan Error', [
'code' => $e->getCode(),
'message' => $e->getMessage(),
]);
return false;
$result = false;
}
return $result;
}
/**
* 处理异步通知
* 异步通知
*/
public function notify()
{
@ -133,11 +71,11 @@ class Wechat extends Service
$data = $this->gateway->verify();
Log::debug('Wechat Verify Data', $data->all());
Log::debug('Wxpay Verify Data', $data->all());
} catch (\Exception $e) {
Log::error('Wechat Verify Error', [
Log::error('Wxpay Verify Error', [
'code' => $e->getCode(),
'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()
{
@ -191,7 +234,7 @@ class Wechat extends Service
'key' => $this->config['key'],
'notify_url' => $this->config['notify_url'],
'log' => [
'file' => log_path('wechat.log'),
'file' => log_path('wxpay.log'),
'level' => $level,
'type' => 'daily',
'max_file' => 30,

View File

@ -2,74 +2,64 @@
namespace App\Services;
use Qcloud\Sms\SmsMultiSender;
use Phalcon\Logger\Adapter\File as FileLogger;
use Qcloud\Sms\SmsSingleSender;
class Smser extends Service
Abstract class Smser extends Service
{
/**
* @var array
*/
protected $config;
/**
* @var FileLogger
*/
protected $logger;
public function __construct()
{
$this->config = $this->getSectionConfig('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
*/
public function sendTestMessage($phone)
public function send($phoneNumber, $templateId, $params)
{
$sender = $this->createSingleSender();
$templateId = $this->getTemplateId('register');
$signature = $this->getSignature();
$params = [888888, 5];
$signature = $this->getSignature();
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);
return $content['result'] == 0 ? true : false;
$result = $content['result'] == 0;
} 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(),
'message' => $e->getMessage(),
]));
return false;
$result = false;
}
return $result;
}
protected function createSingleSender()
@ -79,20 +69,6 @@ class Smser extends Service
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)
{
$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;
use App\Models\ContentImage as ContentImageModel;
use Phalcon\Logger\Adapter\File as FileLogger;
use Qcloud\Cos\Client as CosClient;
class Storage extends Service
{
/**
* @var array
*/
protected $config;
/**
* @var FileLogger
*/
protected $logger;
/**
* @var CosClient
*/
protected $client;
public function __construct()
{
$this->config = $this->getSectionConfig('storage');
$this->logger = $this->getLogger('storage');
$this->client = $this->getCosClient();
}
@ -49,7 +63,7 @@ class Storage extends Service
/**
* 上传内容图片
*
* @return mixed
* @return string|bool
*/
public function uploadContentImage()
{
@ -74,7 +88,7 @@ class Storage extends Service
/**
* 上传头像图片
*
* @return mixed
* @return string|bool
*/
public function uploadAvatarImage()
{
@ -87,7 +101,7 @@ class Storage extends Service
* 上传图片
*
* @param string $prefix
* @return mixed
* @return string|bool
*/
public function uploadImage($prefix = '')
{
@ -117,7 +131,7 @@ class Storage extends Service
*
* @param string $key
* @param string $body
* @return mixed string|bool
* @return string|bool
*/
public function putString($key, $body)
{
@ -129,8 +143,6 @@ class Storage extends Service
$result = $response['Location'] ? $key : false;
return $result;
} catch (\Exception $e) {
$this->logger->error('Put String Exception ' . kg_json_encode([
@ -138,8 +150,10 @@ class Storage extends Service
'message' => $e->getMessage(),
]));
return false;
$result = false;
}
return $result;
}
/**
@ -161,8 +175,6 @@ class Storage extends Service
$result = $response['Location'] ? $key : false;
return $result;
} catch (\Exception $e) {
$this->logger->error('Put File Exception ' . kg_json_encode([
@ -170,8 +182,10 @@ class Storage extends Service
'message' => $e->getMessage(),
]));
return false;
$result = false;
}
return $result;
}
/**
@ -271,7 +285,7 @@ class Storage extends Service
$client = new CosClient([
'region' => $this->config['bucket_region'],
'schema' => 'https',
'schema' => $this->config['bucket_protocol'],
'credentials' => [
'secretId' => $secret['secret_id'],
'secretKey' => $secret['secret_key'],

View File

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

View File

@ -27,7 +27,7 @@ class Validator extends Component
return $user;
}
public function checkOwnerPriv($userId, $ownerId)
public function checkOwner($userId, $ownerId)
{
if ($userId != $ownerId) {
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_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.course_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'] = '无效的过期时间';
/**
@ -240,7 +237,6 @@ $error['slide.invalid_publish_status'] = '无效的发布状态';
*/
$error['order.not_found'] = '订单不存在';
$error['order.item_not_found'] = '商品不存在';
$error['order.reach_daily_limit'] = '超出每日订单限额';
$error['order.close_not_allowed'] = '当前不允许关闭订单';
$error['order.has_bought_course'] = '已经够买过该课程';
$error['order.has_bought_package'] = '已经够买过该套餐';

View File

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

View File

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