diff --git a/app/admin/view/default/appmini/config.html b/app/admin/view/default/appmini/config.html index e3a2414a5..93cfaac25 100755 --- a/app/admin/view/default/appmini/config.html +++ b/app/admin/view/default/appmini/config.html @@ -212,6 +212,32 @@ {{/case}} + {{case kuaishou}} + +
+
+

基础配置

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ {{/case}} {{default /}}
{{$nav_type}}无配置信息
{{/switch}} diff --git a/app/service/AppMiniUserService.php b/app/service/AppMiniUserService.php index 9c31a7ee1..e0522ea6a 100644 --- a/app/service/AppMiniUserService.php +++ b/app/service/AppMiniUserService.php @@ -464,6 +464,95 @@ class AppMiniUserService return $ret; } + /** + * 快手小程序用户授权 + * @author Devil + * @blog http://gong.gg/ + * @version 1.0.0 + * @date 2019-10-27 + * @desc description + * @param [array] $params [输入参数] + */ + public static function KuaishouUserAuth($params = []) + { + $config = [ + 'appid' => self::AppMiniConfig('common_app_mini_kuaishou_appid'), + 'secret' => self::AppMiniConfig('common_app_mini_kuaishou_appsecret'), + ]; + $ret = (new \base\Kuaishou($config))->GetAuthSessionKey($params); + if($ret['code'] == 0) + { + // 先从数据库获取用户信息 + $user = UserService::AppUserInfoHandle(null, 'toutiao_openid', $ret['data']['openid']); + if(empty($user)) + { + $ret = DataReturn('授权登录成功', 0, ['is_user_exist'=>0, 'openid'=>$ret['data']['openid']]); + } else { + // 用户状态 + $ret = UserService::UserStatusCheck('id', $user['id']); + if($ret['code'] == 0) + { + // 标记用户存在 + $user['is_user_exist'] = 1; + $ret = DataReturn('授权登录成功', 0, $user); + } + } + } + return $ret; + } + + /** + * 快手小程序获取用户信息 + * @author Devil + * @blog http://gong.gg/ + * @version 1.0.0 + * @date 2019-10-27 + * @desc description + * @param [array] $params [输入参数] + */ + public static function KuaishouUserInfo($params = []) + { + // 参数校验 + $p = [ + [ + 'checked_type' => 'empty', + 'key_name' => 'openid', + 'error_msg' => 'openid为空', + ], + [ + 'checked_type' => 'empty', + 'key_name' => 'auth_data', + 'error_msg' => '授权数据为空', + ] + ]; + $ret = ParamsChecked($params, $p); + if($ret === true) + { + // 先从数据库获取用户信息 + $user = UserService::AppUserInfoHandle(null, 'kuaishou_openid', $params['openid']); + if(empty($user)) + { + $auth_data = is_array($params['auth_data']) ? $params['auth_data'] : json_decode(htmlspecialchars_decode($params['auth_data']), true); + $auth_data['nickname'] = isset($auth_data['nickName']) ? $auth_data['nickName'] : ''; + $auth_data['avatar'] = isset($auth_data['avatarUrl']) ? $auth_data['avatarUrl'] : ''; + $auth_data['gender'] = 0; + $auth_data['openid'] = $params['openid']; + $auth_data['referrer']= isset($params['referrer']) ? $params['referrer'] : 0; + $ret = UserService::AuthUserProgram($auth_data, 'kuaishou_openid'); + } else { + // 用户状态 + $ret = UserService::UserStatusCheck('id', $user['id']); + if($ret['code'] == 0) + { + $ret = DataReturn('授权成功', 0, $user); + } + } + } else { + $ret = DataReturn($ret, -1); + } + return $ret; + } + /** * QQ小程序获取用户授权 * @author Devil diff --git a/app/service/ConstService.php b/app/service/ConstService.php index bc64ce123..e03d3f609 100644 --- a/app/service/ConstService.php +++ b/app/service/ConstService.php @@ -171,30 +171,32 @@ class ConstService // 所属平台 'common_platform_type' => [ - 'pc' => ['value' => 'pc', 'name' => 'PC网站'], - 'h5' => ['value' => 'h5', 'name' => 'H5手机网站'], - 'ios' => ['value' => 'ios', 'name' => '苹果APP'], - 'android' => ['value' => 'android', 'name' => '安卓APP'], - 'weixin' => ['value' => 'weixin', 'name' => '微信小程序'], - 'alipay' => ['value' => 'alipay', 'name' => '支付宝小程序'], - 'baidu' => ['value' => 'baidu', 'name' => '百度小程序'], - 'toutiao' => ['value' => 'toutiao', 'name' => '头条小程序'], - 'qq' => ['value' => 'qq', 'name' => 'QQ小程序'], + 'pc' => ['value' => 'pc', 'name' => 'PC网站'], + 'h5' => ['value' => 'h5', 'name' => 'H5手机网站'], + 'ios' => ['value' => 'ios', 'name' => '苹果APP'], + 'android' => ['value' => 'android', 'name' => '安卓APP'], + 'weixin' => ['value' => 'weixin', 'name' => '微信小程序'], + 'alipay' => ['value' => 'alipay', 'name' => '支付宝小程序'], + 'baidu' => ['value' => 'baidu', 'name' => '百度小程序'], + 'toutiao' => ['value' => 'toutiao', 'name' => '头条小程序'], + 'qq' => ['value' => 'qq', 'name' => 'QQ小程序'], + 'kuaishou' => ['value' => 'kuaishou', 'name' => '快手小程序'], ], // app平台 'common_app_type' => [ - 'ios' => ['value' => 'ios', 'name' => '苹果APP'], - 'android' => ['value' => 'android', 'name' => '安卓APP'], + 'ios' => ['value' => 'ios', 'name' => '苹果APP'], + 'android' => ['value' => 'android', 'name' => '安卓APP'], ], // 小程序平台 'common_appmini_type' => [ - 'weixin' => ['value' => 'weixin', 'name' => '微信小程序'], - 'alipay' => ['value' => 'alipay', 'name' => '支付宝小程序'], - 'baidu' => ['value' => 'baidu', 'name' => '百度小程序'], - 'toutiao' => ['value' => 'toutiao', 'name' => '头条小程序'], - 'qq' => ['value' => 'qq', 'name' => 'QQ小程序'], + 'weixin' => ['value' => 'weixin', 'name' => '微信小程序'], + 'alipay' => ['value' => 'alipay', 'name' => '支付宝小程序'], + 'baidu' => ['value' => 'baidu', 'name' => '百度小程序'], + 'toutiao' => ['value' => 'toutiao', 'name' => '头条小程序'], + 'qq' => ['value' => 'qq', 'name' => 'QQ小程序'], + 'kuaishou' => ['value' => 'kuaishou', 'name' => '快手小程序'], ], // 扣除库存规则 diff --git a/extend/base/Baidu.php b/extend/base/Baidu.php index fe9495337..438fd5a3b 100755 --- a/extend/base/Baidu.php +++ b/extend/base/Baidu.php @@ -228,7 +228,7 @@ class Baidu * @version 1.0.0 * @datetime 2018-01-02T19:53:42+0800 */ - private function GetMiniAccessToken() + public function GetMiniAccessToken() { // 缓存key $key = $this->_appid.'_access_token'; diff --git a/extend/base/Kuaishou.php b/extend/base/Kuaishou.php new file mode 100644 index 000000000..e0c7749a0 --- /dev/null +++ b/extend/base/Kuaishou.php @@ -0,0 +1,172 @@ +config = $config; + } + + /** + * 用户授权 + * @author Devil + * @blog http://gong.gg/ + * @version 1.0.0 + * @date 2019-10-27 + * @desc description + * @param [array] $params [输入参数] + */ + public function GetAuthSessionKey($params = []) + { + if(empty($params['authcode'])) + { + return DataReturn('授权码有误', -1); + } + if(empty($this->config['appid']) || empty($this->config['secret'])) + { + return DataReturn('配置有误', -1); + } + + // 请求获取session_key + $request_params = [ + 'js_code' => $params['authcode'], + 'app_id' => $this->config['appid'], + 'app_secret'=> $this->config['secret'], + ]; + $url = 'https://open.kuaishou.com/oauth2/mp/code2session'; + $ret = CurlPost($url, $request_params); + if($ret['code'] != 0) + { + return $ret; + } + $result = json_decode($ret['data'], true); + if(!empty($result['open_id'])) + { + // 缓存SessionKey + $key = 'kuaishou_user_login_'.$result['open_id']; + + // 缓存存储 + MyCache($key, $result); + return DataReturn('授权成功', 0, ['openid'=>$result['open_id']]); + } + $msg = empty($result['error_msg']) ? '授权接口异常错误' : $result['error_msg']; + return DataReturn($msg, -1); + } + + /** + * 获取access_token + * @author Devil + * @blog http://gong.gg/ + * @version 1.0.0 + * @datetime 2018-01-02T19:53:42+0800 + */ + public function GetMiniAccessToken() + { + // 缓存key + $key = $this->config['appid'].'_access_token'; + $result = MyCache($key); + if(!empty($result)) + { + if($result['expires_in'] > time()) + { + return DataReturn('success', 0, $result['access_token']); + } + } + + // 网络请求 + $request_params = [ + 'app_id' => $this->config['appid'], + 'app_secret'=> $this->config['secret'], + 'grant_type'=> 'client_credentials', + ]; + $url = 'https://open.kuaishou.com/oauth2/access_token'; + $ret = CurlPost($url, $request_params); + if($ret['code'] != 0) + { + return $ret; + } + $result = json_decode($ret['data'], true); + if(!empty($result['access_token'])) + { + // 缓存存储 + $result['expires_in'] += time(); + MyCache($key, $result); + return DataReturn('授权成功', 0, $result['access_token']); + } + $msg = empty($result['error_msg']) ? '授权接口异常错误' : $result['error_msg']; + return DataReturn($msg, -1); + } + + /** + * curl模拟post + * @author Devil + * @blog http://gong.gg/ + * @version 0.0.1 + * @datetime 2016-12-03T21:58:54+0800 + * @param [string] $url [请求地址] + * @param [array] $post [发送的post数据] + * @param [boolean] $is_json [是否使用 json 数据发送] + * @return [mixed] [请求返回的数据] + */ + private function HttpRequestPost($url, $post, $is_json = false) + { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_HEADER, false); + curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + curl_setopt($ch, CURLOPT_URL, $url); + + // 是否 json + if($is_json) + { + $data_string = json_encode($post); + curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + "Content-Type: application/json; charset=utf-8", + "Content-Length: " . strlen($data_string) + ) + ); + } else { + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post)); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + "Content-Type: application/x-www-form-urlencoded", + "cache-control: no-cache" + ) + ); + } + + $result = curl_exec($ch); + curl_close($ch); + return $result; + } +} +?> \ No newline at end of file diff --git a/extend/base/QQ.php b/extend/base/QQ.php index 9027a7228..4b9f06ffe 100644 --- a/extend/base/QQ.php +++ b/extend/base/QQ.php @@ -16,7 +16,6 @@ namespace base; * @blog http://gong.gg/ * @version 1.0.0 * @date 2019-10-31 - * @desc 支持所有文件存储到硬盘 */ class QQ { diff --git a/extend/base/Toutiao.php b/extend/base/Toutiao.php index 5923f0f26..b0cc60a75 100644 --- a/extend/base/Toutiao.php +++ b/extend/base/Toutiao.php @@ -13,7 +13,9 @@ namespace base; /** * 头条驱动 * @author Devil - * @version V_1.0.0 + * @blog http://gong.gg/ + * @version 1.0.0 + * @date 2019-10-31 */ class Toutiao { @@ -68,31 +70,6 @@ class Toutiao return DataReturn($msg, -1); } - /** - * 支付签名生成 - * @author Devil - * @blog http://gong.gg/ - * @version 1.0.0 - * @date 2019-10-29 - * @desc description - * @param [array] $data [需要生成签名的数据] - * @param [string] $secret [密钥] - */ - public function PaySignCreated($data, $secret) - { - ksort($data); - $sign = ''; - foreach($data AS $key=>$val) - { - if($key != 'sign' && $key != 'risk_info' && $val != '') - { - $sign .= "$key=$val&"; - } - } - $sign = substr($sign, 0, -1); - return md5($sign.$secret); - } - /** * [MiniQrCodeCreate 二维码创建] * @author Devil @@ -163,7 +140,7 @@ class Toutiao * @version 1.0.0 * @datetime 2018-01-02T19:53:42+0800 */ - private function GetMiniAccessToken() + public function GetMiniAccessToken() { // 缓存key $key = $this->config['appid'].'_access_token'; diff --git a/extend/payment/Kuaishou.php b/extend/payment/Kuaishou.php new file mode 100644 index 000000000..8edb7a1a9 --- /dev/null +++ b/extend/payment/Kuaishou.php @@ -0,0 +1,415 @@ +config = $params; + } + + /** + * 配置信息 + * @author Devil + * @blog http://gong.gg/ + * @version 1.0.0 + * @date 2018-09-19 + * @desc description + */ + public function Config() + { + // 基础信息 + $base = [ + 'name' => '快手', // 插件名称 + 'version' => '1.0.0', // 插件版本 + 'apply_version' => '不限', // 适用系统版本描述 + 'apply_terminal'=> ['kuaishou'], // 适用终端 默认全部 ['pc', 'h5', 'ios', 'android', 'alipay', 'weixin', 'baidu'] + 'desc' => '适用快手小程序,担保交易支付方式。 立即申请', // 插件描述(支持html) + 'author' => 'Devil', // 开发者 + 'author_url' => 'http://shopxo.net/', // 开发者主页 + ]; + + // 配置信息 + $element = [ + [ + 'element' => 'input', + 'type' => 'text', + 'default' => '', + 'name' => 'app_id', + 'placeholder' => '小程序AppID', + 'title' => '小程序AppID', + 'is_required' => 0, + 'message' => '请填写小程序AppID', + ], + [ + 'element' => 'input', + 'type' => 'text', + 'default' => '', + 'name' => 'app_secret', + 'placeholder' => '小程序AppSecret', + 'title' => '小程序AppSecret', + 'is_required' => 0, + 'message' => '请填写小程序AppSecret', + ], + [ + 'element' => 'input', + 'type' => 'text', + 'default' => '', + 'name' => 'type', + 'placeholder' => '商品类型、担保支付商品类目编号', + 'title' => '商品类型', + 'is_required' => 0, + 'message' => '请填写商品类型', + ], + [ + 'element' => 'message', + 'message' => '将当前网站域名配置到快手小程序后台->权限管理->支付设置->支付回调域名中、网站必须是https请求访问', + ], + ]; + + return [ + 'base' => $base, + 'element' => $element, + ]; + } + + /** + * 支付入口 + * @author Devil + * @blog http://gong.gg/ + * @version 1.0.0 + * @date 2018-09-19 + * @desc description + * @param [array] $params [输入参数] + */ + public function Pay($params = []) + { + // 参数 + if(empty($params)) + { + return DataReturn('参数不能为空', -1); + } + + // 配置信息 + if(empty($this->config)) + { + return DataReturn('支付缺少配置', -1); + } + + // 获取access_token + $config = [ + 'appid' => $this->config['app_id'], + 'secret' => $this->config['app_secret'], + ]; + $access_token = (new \base\Kuaishou($config))->GetMiniAccessToken($params); + if($access_token['code'] != 0) + { + return $access_token; + } + + // 处理支付 + $parameter = [ + 'out_order_no' => $params['order_no'], + 'open_id' => $params['user']['kuaishou_openid'], + 'total_amount' => (int) (($params['total_price']*1000)/10), + 'subject' => $params['name'], + 'detail' => $params['site_name'].'-'.$params['name'], + 'type' => $this->config['type'], + 'expire_time' => $this->OrderAutoCloseTime(), + 'notify_url' => $params['notify_url'], + ]; + + // 签名 + $parameter['sign'] = $this->GetParamSign(array_merge($parameter, ['app_id'=>$this->config['app_id']])); + + // 请求接口 + $url = 'https://open.kuaishou.com/openapi/mp/developer/epay/create_order?app_id='.$this->config['app_id'].'&access_token='.$access_token['data']; + $ret = $this->HttpRequest($url, $parameter); + if($ret['code'] == 0 && !empty($ret['data']['order_info'])) + { + $ret['data'] = $ret['data']['order_info']; + } + return $ret; + } + + /** + * 订单自动关闭的时间 + * @author Devil + * @blog http://gong.gg/ + * @version 1.0.0 + * @date 2021-03-24 + * @desc description + */ + public function OrderAutoCloseTime() + { + return intval(MyC('common_order_close_limit_time', 30, true))*60; + } + + /** + * 签名生成 + * @author Devil + * @blog http://gong.gg/ + * @version 1.0.0 + * @date 2018-09-19 + * @desc description + * @param [array] $params [输入参数] + */ + private function GetParamSign($params = []) + { + ksort($params); + $prestr = ''; + foreach($params as $key=>$val) + { + if(!in_array($key, ['sign', 'access_token'])) + { + $prestr .= "$key=$val&"; + } + } + $prestr = substr($prestr, 0, -1); + return md5($prestr.$this->config['app_secret']); + } + + /** + * 支付回调处理 + * @author Devil + * @blog http://gong.gg/ + * @version 1.0.0 + * @date 2018-09-19 + * @desc description + * @param [array] $params [输入参数] + */ + public function Respond($params = []) + { + // 参数 + if(empty($this->config)) + { + return DataReturn('配置有误', -1); + } + // 签名 + if(empty($_SERVER['HTTP_KWAISIGN'])) + { + return DataReturn('授权签名为空', -1); + } + $body = file_get_contents('php://input'); + if(md5($body.$this->config['app_secret']) != $_SERVER['HTTP_KWAISIGN']) + { + return DataReturn('签名验证失败', -1); + } + if(empty($params['data'])) + { + return DataReturn('支付数据为空', -1); + } + return DataReturn('支付成功', 0, $this->ReturnData($params['data'])); + } + + /** + * 返回数据统一格式 + * @author Devil + * @blog http://gong.gg/ + * @version 1.0.0 + * @datetime 2018-10-06T16:54:24+0800 + * @param [array] $data [返回数据] + */ + private function ReturnData($data) + { + // 返回数据固定基础参数 + $data['trade_no'] = isset($data['ks_order_no']) ? $data['ks_order_no'] : ''; // 支付平台 - 订单号 + $data['buyer_user'] = isset($data['channel']) ? $data['channel'] : ''; // 支付平台 - 用户 + $data['out_trade_no'] = $data['out_order_no']; // 本系统发起支付的 - 订单号 + $data['subject'] = $data['trade_no']; // 本系统发起支付的 - 商品名称 + $data['pay_price'] = $data['order_amount']/100; // 本系统发起支付的 - 总价 + + return $data; + } + + /** + * 退款处理 + * @author Devil + * @blog http://gong.gg/ + * @version 1.0.0 + * @date 2019-05-28 + * @desc description + * @param [array] $params [输入参数] + */ + public function Refund($params = []) + { + // 参数 + $p = [ + [ + 'checked_type' => 'empty', + 'key_name' => 'order_no', + 'error_msg' => '订单号不能为空', + ], + [ + 'checked_type' => 'empty', + 'key_name' => 'trade_no', + 'error_msg' => '交易平台订单号不能为空', + ], + [ + 'checked_type' => 'empty', + 'key_name' => 'pay_price', + 'error_msg' => '支付金额不能为空', + ], + [ + 'checked_type' => 'empty', + 'key_name' => 'refund_price', + 'error_msg' => '退款金额不能为空', + ], + [ + 'checked_type' => 'empty', + 'key_name' => 'pay_time', + 'error_msg' => '支付时间不能为空', + ], + ]; + $ret = ParamsChecked($params, $p); + if($ret !== true) + { + return DataReturn($ret, -1); + } + + // 获取access_token + $config = [ + 'appid' => $this->config['app_id'], + 'secret' => $this->config['app_secret'], + ]; + $access_token = (new \base\Kuaishou($config))->GetMiniAccessToken($params); + if($access_token['code'] != 0) + { + return $access_token; + } + + // 退款原因 + $refund_reason = empty($params['refund_reason']) ? $params['order_no'].'订单退款'.$params['refund_price'].'元' : $params['refund_reason']; + + // 处理支付 + $parameter = [ + 'app_id' => $this->config['app_id'], + 'out_order_no' => $params['order_no'], + 'out_refund_no' => md5(time().GetNumberCode(6)), + 'refund_amount' => (int) (($params['refund_price']*1000)/10), + 'reason' => $refund_reason, + 'notify_url' => __MY_URL__, + ]; + + // 签名 + $parameter['sign'] = $this->GetParamSign(array_merge($parameter, ['app_id'=>$this->config['app_id']])); + + // 请求接口 + $url = 'https://open.kuaishou.com/openapi/mp/developer/epay/apply_refund?app_id='.$this->config['app_id'].'&access_token='.$access_token['data']; + $ret = $this->HttpRequest($url, $parameter); + if($ret['code'] == 0 && !empty($ret['data'])) + { + // 统一返回格式 + $data = [ + 'trade_no' => $ret['data']['refund_no'], + 'refund_price' => $params['refund_price'], + 'return_params' => $ret['data'], + ]; + return DataReturn('退款成功', 0, $data); + } else { + // 是否退款中、或退款完成 + if(stripos($ret['msg'], '10000687') !== false || stripos($ret['msg'], '10000607') !== false) + { + // 统一返回格式 + $data = [ + 'trade_no' => '', + 'refund_price' => $params['refund_price'], + 'return_params' => $ret['msg'], + ]; + return DataReturn('退款成功', 0, $data); + } + } + return $ret; + } + + /** + * 网络请求 + * @author Devil + * @blog http://gong.gg/ + * @version 1.0.0 + * @datetime 2017-09-25T09:10:46+0800 + * @param [string] $url [请求url] + * @param [array] $data [发送数据] + * @param [int] $second [超时] + * @return [mixed] [请求返回数据] + */ + private function HttpRequest($url, $data, $second = 30) + { + $ch = curl_init(); + $header = ['Content-Type: application/json;charset=utf-8']; + curl_setopt_array($ch, array( + CURLOPT_URL => $url, + CURLOPT_HTTPHEADER => $header, + CURLOPT_POST => true, + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POSTFIELDS => json_encode($data), + CURLOPT_TIMEOUT => $second, + )); + $result = curl_exec($ch); + + //返回结果 + if($result) + { + curl_close($ch); + $res = json_decode($result, true); + if(empty($res) || !isset($res['result'])) + { + return DataReturn('请求失败['.$result.']', -1); + } + if($res['result'] != 1) + { + return DataReturn($res['error_msg'].'('.$res['result'].')', -1); + } + return DataReturn('success', 0, $res); + } else { + $error = curl_errno($ch); + curl_close($ch); + return DataReturn('curl出错,错误码['.$error.']', -1); + } + } + + /** + * 自定义成功返回内容 + * @author Devil + * @blog http://gong.gg/ + * @version 1.0.0 + * @date 2020-07-01 + * @desc description + */ + public function SuccessReturn() + { + return '{"result": 1,"message_id": "'.MyInput('message_id').'"}'; + } +} +?> \ No newline at end of file