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(); + } + +}