diff --git a/app/Http/Api/Controllers/ConnectController.php b/app/Http/Api/Controllers/ConnectController.php
new file mode 100644
index 00000000..cf34611c
--- /dev/null
+++ b/app/Http/Api/Controllers/ConnectController.php
@@ -0,0 +1,52 @@
+getAuthorizeUrl(ConnectModel::PROVIDER_WECHAT);
+
+ return $this->response->redirect($url, true);
+ }
+
+ /**
+ * @Get("/wechat/callback", name="api.oauth.wechat_callback")
+ */
+ public function wechatCallbackAction()
+ {
+ $service = new ConnectService();
+
+ $data = $service->handleCallback(ConnectModel::PROVIDER_WECHAT);
+
+ $location = kg_h5_index_url() . '?' . http_build_query($data);
+
+ return $this->response->redirect($location, true);
+ }
+
+ /**
+ * @Get("/wechat/info", name="api.oauth.wechat_info")
+ */
+ public function wechatInfoAction()
+ {
+ $service = new ConnectService();
+
+ $wechat = $service->getWechatInfo();
+
+ return $this->jsonSuccess(['wechat' => $wechat]);
+ }
+
+}
diff --git a/app/Http/Api/Controllers/TradeController.php b/app/Http/Api/Controllers/TradeController.php
index 4e772559..bb03665d 100644
--- a/app/Http/Api/Controllers/TradeController.php
+++ b/app/Http/Api/Controllers/TradeController.php
@@ -54,6 +54,18 @@ class TradeController extends Controller
return $this->jsonSuccess($content);
}
+ /**
+ * @Post("/mini/create", name="api.trade.mini_create")
+ */
+ public function createMiniTradeAction()
+ {
+ $service = new TradeService();
+
+ $content = $service->createMiniTrade();
+
+ return $this->jsonSuccess($content);
+ }
+
/**
* @Post("/app/create", name="api.trade.app_create")
*/
@@ -61,7 +73,7 @@ class TradeController extends Controller
{
$service = new TradeService();
- $content = $service->createMpTrade();
+ $content = $service->createAppTrade();
return $this->jsonSuccess($content);
}
diff --git a/app/Http/Api/Services/Connect.php b/app/Http/Api/Services/Connect.php
new file mode 100644
index 00000000..f64b01e5
--- /dev/null
+++ b/app/Http/Api/Services/Connect.php
@@ -0,0 +1,190 @@
+ kg_full_url(['for' => 'api.oauth.wechat']),
+ 'auto_login' => 1,
+ ];
+ }
+
+ public function handleCallback($provider)
+ {
+ $code = $this->request->getQuery('code');
+ $state = $this->request->getQuery('state');
+
+ $openUser = $this->getOpenUserInfo($code, $state, $provider);
+ $relation = $this->getConnectRelation($openUser['id'], $provider);
+
+ $token = null;
+
+ if ($relation) {
+
+ $userRepo = new UserRepo();
+
+ $user = $userRepo->findById($relation->user_id);
+
+ $auth = new ApiAuthService();
+
+ $token = $auth->saveAuthInfo($user);
+
+ $this->handleLoginNotice($user);
+ }
+
+ return [
+ 'openid' => $openUser['id'],
+ 'token' => $token,
+ ];
+ }
+
+ public function getAuthorizeUrl($provider)
+ {
+ $auth = $this->getConnectAuth($provider);
+
+ return $auth->getAuthorizeUrl();
+ }
+
+ public function getOpenUserInfo($code, $state, $provider)
+ {
+ $auth = $this->getConnectAuth($provider);
+
+ $auth->checkState($state);
+
+ $token = $auth->getAccessToken($code);
+
+ $openId = $auth->getOpenId($token);
+
+ return $auth->getUserInfo($token, $openId);
+ }
+
+ public function getConnectRelation($openId, $provider)
+ {
+ $connectRepo = new ConnectRepo();
+
+ return $connectRepo->findByOpenId($openId, $provider);
+ }
+
+ public function getConnectAuth($provider)
+ {
+ $auth = null;
+
+ switch ($provider) {
+ case ConnectModel::PROVIDER_QQ:
+ $auth = $this->getQQAuth();
+ break;
+ case ConnectModel::PROVIDER_WEIBO:
+ $auth = $this->getWeiBoAuth();
+ break;
+ case ConnectModel::PROVIDER_WECHAT:
+ $auth = $this->getWeChatAuth();
+ break;
+ }
+
+ if (!$auth) {
+ throw new Exception('Invalid OAuth Provider');
+ }
+
+ return $auth;
+ }
+
+ protected function getQQAuth()
+ {
+ $settings = $this->getSettings('oauth.qq');
+
+ $settings['redirect_uri'] = kg_full_url(['for' => 'api.oauth.qq_callback']);
+
+ return new QQAuth(
+ $settings['client_id'],
+ $settings['client_secret'],
+ $settings['redirect_uri']
+ );
+ }
+
+ protected function getWeChatAuth()
+ {
+ /**
+ * 使用的是微信公众号网页授权登录功能
+ */
+ $settings = $this->getSettings('wechat.oa');
+
+ $settings['redirect_uri'] = kg_full_url(['for' => 'api.oauth.wechat_callback']);
+
+ return new WeChatAuth(
+ $settings['app_id'],
+ $settings['app_secret'],
+ $settings['redirect_uri']
+ );
+ }
+
+ protected function getWeiBoAuth()
+ {
+ $settings = $this->getSettings('oauth.weibo');
+
+ $settings['redirect_uri'] = kg_full_url(['for' => 'api.oauth.weibo_callback']);
+
+ return new WeiBoAuth(
+ $settings['client_id'],
+ $settings['client_secret'],
+ $settings['redirect_uri']
+ );
+ }
+
+ protected function handleConnectRelation(UserModel $user, array $openUser)
+ {
+ $connectRepo = new ConnectRepo();
+
+ $connect = $connectRepo->findByOpenId($openUser['id'], $openUser['provider']);
+
+ if ($connect) {
+
+ $connect->open_name = $openUser['name'];
+ $connect->open_avatar = $openUser['avatar'];
+
+ if ($connect->user_id != $user->id) {
+ $connect->user_id = $user->id;
+ }
+
+ if ($connect->deleted == 1) {
+ $connect->deleted = 0;
+ }
+
+ $connect->update();
+
+ } else {
+
+ $connect = new ConnectModel();
+
+ $connect->user_id = $user->id;
+ $connect->open_id = $openUser['id'];
+ $connect->open_name = $openUser['name'];
+ $connect->open_avatar = $openUser['avatar'];
+ $connect->provider = $openUser['provider'];
+
+ $connect->create();
+ }
+ }
+
+ protected function handleLoginNotice(UserModel $user)
+ {
+ $service = new AccountLoginNoticeService();
+
+ $service->createTask($user);
+ }
+
+}
diff --git a/app/Http/Api/Services/Trade.php b/app/Http/Api/Services/Trade.php
index 933aae6e..36946153 100644
--- a/app/Http/Api/Services/Trade.php
+++ b/app/Http/Api/Services/Trade.php
@@ -75,6 +75,45 @@ class Trade extends Service
{
$post = $this->request->getPost();
+ $order = $this->checkOrderBySn($post['order_sn']);
+
+ $user = $this->getLoginUser();
+
+ $channel = TradeModel::CHANNEL_WXPAY;
+
+ $trade = new TradeModel();
+
+ $trade->subject = $order->subject;
+ $trade->amount = $order->amount;
+ $trade->channel = $channel;
+ $trade->order_id = $order->id;
+ $trade->owner_id = $user->id;
+
+ $trade->create();
+
+ $wxpay = new Wxpay();
+
+ $response = $wxpay->mp($trade, $post['open_id']);
+
+ $payment = [
+ 'appId' => $response->appId,
+ 'timeStamp' => $response->timeStamp,
+ 'nonceStr' => $response->nonceStr,
+ 'package' => $response->package,
+ 'signType' => $response->signType,
+ 'paySign' => $response->paySign,
+ ];
+
+ return [
+ 'trade' => $this->handleTradeInfo($trade->sn),
+ 'payment' => $payment,
+ ];
+ }
+
+ public function createMiniTrade()
+ {
+ $post = $this->request->getPost();
+
$validator = new ClientValidator();
$platform = $this->getPlatform();
@@ -120,7 +159,7 @@ class Trade extends Service
public function createAppTrade()
{
-
+ return [];
}
protected function getPlatform()
diff --git a/app/Http/Home/Views/course/show_catalog.volt b/app/Http/Home/Views/course/show_catalog.volt
index d6502be6..e7ed78b3 100644
--- a/app/Http/Home/Views/course/show_catalog.volt
+++ b/app/Http/Home/Views/course/show_catalog.volt
@@ -66,7 +66,7 @@
{%- endmacro %}
{%- macro live_status_info(lesson) %}
- {% if lesson.attrs.start_time < time() and lesson.attrs.end_time > time() %}
+ {% if lesson.attrs.stream.status == 'active' %}
{{ date('m月d日 H:i',lesson.attrs.start_time) }} 直播中
{% elseif lesson.attrs.start_time > time() %}
{{ date('m月d日 H:i',lesson.attrs.start_time) }} 倒计时
diff --git a/app/Library/AppInfo.php b/app/Library/AppInfo.php
index 2e94c37e..55c22de9 100644
--- a/app/Library/AppInfo.php
+++ b/app/Library/AppInfo.php
@@ -16,7 +16,7 @@ class AppInfo
protected $link = 'https://koogua.com';
- protected $version = '1.4.4';
+ protected $version = '1.4.5';
public function __get($name)
{
diff --git a/app/Library/Helper.php b/app/Library/Helper.php
index fcc32506..3288ff70 100644
--- a/app/Library/Helper.php
+++ b/app/Library/Helper.php
@@ -707,4 +707,14 @@ function kg_full_url($uri, $args = null)
$baseUrl = kg_site_url();
return $baseUrl . $url->get($uri, $args);
-}
\ No newline at end of file
+}
+
+/**
+ * 获取H5首页地址
+ *
+ * @return string
+ */
+function kg_h5_index_url()
+{
+ return kg_site_url() . '/h5/#/pages/index/index';
+}
diff --git a/app/Models/Connect.php b/app/Models/Connect.php
index b4700dd8..9f7d17e7 100644
--- a/app/Models/Connect.php
+++ b/app/Models/Connect.php
@@ -7,12 +7,15 @@
namespace App\Models;
+use Phalcon\Mvc\Model\Behavior\SoftDelete;
+
class Connect extends Model
{
const PROVIDER_QQ = 1; // QQ
- const PROVIDER_WEIXIN = 2; // 微信
- const PROVIDER_WEIBO = 3; // 微博
+ const PROVIDER_WEIXIN = 2; // 微信扫码
+ const PROVIDER_WEIBO = 3; // 新浪微博
+ const PROVIDER_WECHAT = 4; // 公众号网页
/**
* 主键编号
@@ -28,6 +31,13 @@ class Connect extends Model
*/
public $user_id = 0;
+ /**
+ * 联合ID
+ *
+ * @var string
+ */
+ public $union_id = '';
+
/**
* 开放ID
*
@@ -56,6 +66,13 @@ class Connect extends Model
*/
public $provider = 0;
+ /**
+ * 删除标识
+ *
+ * @var int
+ */
+ public $deleted = 0;
+
/**
* 创建时间
*
@@ -75,6 +92,18 @@ class Connect extends Model
return 'kg_connect';
}
+ public function initialize()
+ {
+ parent::initialize();
+
+ $this->addBehavior(
+ new SoftDelete([
+ 'field' => 'deleted',
+ 'value' => 1,
+ ])
+ );
+ }
+
public function beforeCreate()
{
$this->create_time = time();
diff --git a/app/Services/OAuth/QQ.php b/app/Services/OAuth/QQ.php
index 444f5d66..dd6dac68 100644
--- a/app/Services/OAuth/QQ.php
+++ b/app/Services/OAuth/QQ.php
@@ -113,6 +113,7 @@ class QQ extends OAuth
$userInfo['name'] = $data['nickname'];
$userInfo['avatar'] = $data['figureurl'];
$userInfo['provider'] = ConnectModel::PROVIDER_QQ;
+ $userInfo['unionid'] = '';
return $userInfo;
}
diff --git a/app/Services/OAuth/WeChat.php b/app/Services/OAuth/WeChat.php
new file mode 100644
index 00000000..29e541a3
--- /dev/null
+++ b/app/Services/OAuth/WeChat.php
@@ -0,0 +1,94 @@
+ $this->clientId,
+ 'redirect_uri' => $this->redirectUri,
+ 'response_type' => 'code',
+ 'scope' => 'snsapi_userinfo',
+ 'state' => $this->getState(),
+ ];
+
+ return self::AUTHORIZE_URL . '?' . http_build_query($params) . '#wechat_redirect';
+ }
+
+ public function getAccessToken($code)
+ {
+ $params = [
+ 'code' => $code,
+ 'appid' => $this->clientId,
+ 'secret' => $this->clientSecret,
+ 'grant_type' => 'authorization_code',
+ ];
+
+ $response = $this->httpPost(self::ACCESS_TOKEN_URL, $params);
+
+ $this->accessToken = $this->parseAccessToken($response);
+
+ return $this->accessToken;
+ }
+
+ public function getOpenId($accessToken = null)
+ {
+ return $this->openId;
+ }
+
+ public function getUserInfo($accessToken, $openId)
+ {
+ $params = [
+ 'access_token' => $accessToken,
+ 'openid' => $openId,
+ ];
+
+ $response = $this->httpGet(self::USER_INFO_URL, $params);
+
+ return $this->parseUserInfo($response);
+ }
+
+ private function parseAccessToken($response)
+ {
+ $data = json_decode($response, true);
+
+ if (isset($data['errcode']) && $data['errcode'] != 0) {
+ throw new \Exception("Fetch Access Token Failed:{$response}");
+ }
+
+ $this->openId = $data['openid'];
+
+ return $data['access_token'];
+ }
+
+ private function parseUserInfo($response)
+ {
+ $data = json_decode($response, true);
+
+ if (isset($data['errcode']) && $data['errcode'] != 0) {
+ throw new \Exception("Fetch User Info Failed:{$response}");
+ }
+
+ $userInfo['id'] = $data['openid'];
+ $userInfo['name'] = $data['nickname'];
+ $userInfo['avatar'] = $data['headimgurl'];
+ $userInfo['unionid'] = $data['unionid'] ?? '';
+ $userInfo['provider'] = ConnectModel::PROVIDER_WECHAT;
+
+ return $userInfo;
+ }
+
+}
diff --git a/app/Services/OAuth/WeiBo.php b/app/Services/OAuth/WeiBo.php
index 698ec0a6..ba98eb74 100644
--- a/app/Services/OAuth/WeiBo.php
+++ b/app/Services/OAuth/WeiBo.php
@@ -88,6 +88,7 @@ class WeiBo extends OAuth
$userInfo['name'] = $data['name'];
$userInfo['avatar'] = $data['profile_image_url'];
$userInfo['provider'] = ConnectModel::PROVIDER_WEIBO;
+ $userInfo['unionid'] = '';
return $userInfo;
}
diff --git a/app/Services/OAuth/WeiXin.php b/app/Services/OAuth/WeiXin.php
index c5d22282..52703c16 100644
--- a/app/Services/OAuth/WeiXin.php
+++ b/app/Services/OAuth/WeiXin.php
@@ -87,6 +87,7 @@ class WeiXin extends OAuth
$userInfo['id'] = $data['openid'];
$userInfo['name'] = $data['nickname'];
$userInfo['avatar'] = $data['headimgurl'];
+ $userInfo['unionid'] = $data['unionid'] ?? '';
$userInfo['provider'] = ConnectModel::PROVIDER_WEIXIN;
return $userInfo;
diff --git a/app/Services/Pay/Wxpay.php b/app/Services/Pay/Wxpay.php
index 86c8493e..c72a4182 100644
--- a/app/Services/Pay/Wxpay.php
+++ b/app/Services/Pay/Wxpay.php
@@ -121,6 +121,37 @@ class Wxpay extends PayService
return $result;
}
+ /**
+ * 公众号支付
+ *
+ * @param TradeModel $trade
+ * @param string $openId
+ * @return Collection|bool
+ */
+ public function mp(TradeModel $trade, $openId)
+ {
+ try {
+
+ $result = $this->gateway->mp([
+ 'out_trade_no' => $trade->sn,
+ 'total_fee' => 100 * $trade->amount,
+ 'body' => $trade->subject,
+ 'openid' => $openId,
+ ]);
+
+ } catch (\Exception $e) {
+
+ Log::error('Wxpay MP Exception', [
+ 'code' => $e->getCode(),
+ 'message' => $e->getMessage(),
+ ]);
+
+ $result = false;
+ }
+
+ return $result;
+ }
+
/**
* 小程序支付
*
diff --git a/db/migrations/20210917093354.php b/db/migrations/20210917093354.php
new file mode 100644
index 00000000..f4ddbfe9
--- /dev/null
+++ b/db/migrations/20210917093354.php
@@ -0,0 +1,56 @@
+alterConnectTable();
+ $this->alterWechatSubscribeTable();
+ }
+
+ protected function alterConnectTable()
+ {
+ $this->table('kg_connect')
+ ->addColumn('deleted', 'integer', [
+ 'null' => false,
+ 'default' => '0',
+ 'limit' => MysqlAdapter::INT_REGULAR,
+ 'signed' => false,
+ 'comment' => '删除标识',
+ 'after' => 'provider',
+ ])
+ ->save();
+ }
+
+ protected function alterWechatSubscribeTable()
+ {
+ $this->table('kg_wechat_subscribe')
+ ->addColumn('union_id', 'string', [
+ 'null' => false,
+ 'default' => '',
+ 'limit' => 64,
+ 'collation' => 'utf8mb4_general_ci',
+ 'encoding' => 'utf8mb4',
+ 'comment' => '联合ID',
+ 'after' => 'open_id',
+ ])
+ ->addColumn('deleted', 'integer', [
+ 'null' => false,
+ 'default' => '0',
+ 'limit' => MysqlAdapter::INT_REGULAR,
+ 'signed' => false,
+ 'comment' => '删除标识',
+ 'after' => 'open_id',
+ ])
+ ->save();
+ }
+
+}