zhangdehua 1 anno fa
parent
commit
43cd416893

+ 8 - 8
app/index/controller/Address.php

@@ -72,9 +72,9 @@ class Address extends Controller
         $model = new UserAddressModel;
         $res = $model->add($this->postForm());
         if ($res['address_id']) {
-            return $this->renderSuccess($res, '添加成功');
+            return $this->renderSuccess($res, 'Successful');
         }
-        return $this->renderError($model->getError() ?: '添加失败');
+        return $this->renderError($model->getError() ?: 'Failed,please try again later');
     }
 
     /**
@@ -87,9 +87,9 @@ class Address extends Controller
     {
         $model = UserAddressModel::detail($addressId);
         if ($model->edit($this->postForm())) {
-            return $this->renderSuccess([], '更新成功');
+            return $this->renderSuccess([], 'Successful');
         }
-        return $this->renderError($model->getError() ?: '更新失败');
+        return $this->renderError($model->getError() ?: 'Failed,please try again later');
     }
 
     /**
@@ -102,9 +102,9 @@ class Address extends Controller
     {
         $model = UserAddressModel::detail($addressId);
         if ($model->setDefault((int)$model['address_id'])) {
-            return $this->renderSuccess([], '设置成功');
+            return $this->renderSuccess([], 'Successful');
         }
-        return $this->renderError($model->getError() ?: '设置失败');
+        return $this->renderError($model->getError() ?: 'Failed,please try again later');
     }
 
     /**
@@ -117,9 +117,9 @@ class Address extends Controller
     {
         $model = UserAddressModel::detail($addressId);
         if ($model->remove()) {
-            return $this->renderSuccess([], '删除成功');
+            return $this->renderSuccess([], 'Successful');
         }
-        return $this->renderError($model->getError() ?: '删除失败');
+        return $this->renderError($model->getError() ?: 'Failed,please try again later');
     }
 
 }

+ 5 - 6
app/index/controller/Cart.php

@@ -21,7 +21,6 @@ use app\common\exception\BaseException;
 use think\facade\Cache;
 use think\facade\Session;
 use think\response\Json;
-use think\View;
 
 /**
  * 购物车管理
@@ -151,11 +150,11 @@ class Cart extends Controller
 
         $model = new CartModel;
         if (!$model->add($goodsId, $goodsSkuId, $goodsNum)) {
-            return $this->renderError($model->getError() ?: '加入购物车失败');
+            return $this->renderError($model->getError() ?: 'Added to shopping cart failed');
         }
         // 购物车商品总数量
         $cartTotal = $model->getCartTotal();
-        return $this->renderSuccess(compact('cartTotal'), '加入购物车成功');
+        return $this->renderSuccess(compact('cartTotal'), 'Added to shopping cart successfully');
     }
 
     /**
@@ -187,7 +186,7 @@ class Cart extends Controller
         // 购物车商品总数量
         //$cartTotal = $model->getCartTotal();
         $cartMoney = $this->getCartMoney();
-        return $this->renderSuccess(compact('cartMoney'), '更新成功');
+        return $this->renderSuccess(compact('cartMoney'), 'Successful operation');
     }
 
     /**
@@ -204,13 +203,13 @@ class Cart extends Controller
         }
         $model = new CartModel;
         if (!$model->clear($cartIds)) {
-            return $this->renderError($model->getError() ?: '操作失败');
+            return $this->renderError($model->getError() ?: 'Failed operation');
         }
         // 购物车商品总数量
         //$cartTotal = $model->getCartTotal();
         $cartMoney = $this->getCartMoney();
 
-        return $this->renderSuccess(compact('cartMoney'), '操作成功');
+        return $this->renderSuccess(compact('cartMoney'), 'Successful operation');
     }
 
     private function getCartMoney()

+ 10 - 12
app/index/controller/Checkout.php

@@ -171,7 +171,7 @@ class Checkout extends Controller
         if ($params['payType'] == OrderPayTypeEnum::POINTS) {
             $payPoints = intval(bcmul(strval($orderInfo['orderPayPrice']), '100', 0));//订单所需积分
             if (intval($points) < $payPoints) {
-                return $this->renderError($payment['message'] ?? '积分不够');
+                return $this->renderError('Not enough points');
             }
         }
 
@@ -181,7 +181,7 @@ class Checkout extends Controller
         }
         // 创建订单
         if (!$Checkout->createOrder($orderInfo)) {
-            return $this->renderError($Checkout->getError() ?: '订单创建失败');
+            return $this->renderError($Checkout->getError() ?: 'Order creation failed, please try again later');
         }
         // 移出购物车中已下单的商品
         $CartModel->clear($cartIds);
@@ -189,7 +189,7 @@ class Checkout extends Controller
         $payment = $Checkout->onOrderPayment();
         if ($params['payType'] == OrderPayTypeEnum::POINTS) {
             if (!$payment['flag']) {
-                return $this->renderError($payment['message'] ?? '兑换失败');
+                return $this->renderError($payment['message'] ?? 'Redemption failed, please try again later');
             }
         }
 
@@ -204,29 +204,27 @@ class Checkout extends Controller
     /**
      * @return \think\response\View
      */
-    public function payPayExecutePay($orderNo, $token)
+    public function payPayExecutePay($orderNo = '', $token = '')
     {
         if (empty($orderNo) || empty($token)) {
-            return view('payError', ['notice' => lang('login success')]);
+            //return view('payError', ['notice' => lang('login success')]);
+            return view('payError', ['notice' => 'Payment failed']);
         }
 
         $oriToken = Cache::get(PayPal::PRE_STR . $orderNo);
         if ($token != $oriToken) {
-            return view('payError', ['notice' => lang('login success')]);
+            return view('payError', ['notice' => 'Payment failed']);
         }
 
         $paymentId = $this->request->param('paymentId');
-        //$token = $this->request->param('token');
-        //$PayerID = $this->request->param('PayerID');
         $conf = config('paypal');
-        //$orderNo = '';
         $pp = new PayPal($conf);
         $flag = $pp->executePayment($paymentId);
         if ($flag) {
             $orderModel = new PaySuccess($orderNo);
             $status = $orderModel->onPaySuccess(OrderPayTypeEnum::PAYPAL, ['transaction_id' => $paymentId]);
             if (!$status) {
-                return view('payError', ['notice' => lang('login success')]);
+                return view('payError', ['notice' => 'Payment failed']);
             }
             Cache::delete(PayPal::PRE_STR . $orderNo);
         }
@@ -238,13 +236,13 @@ class Checkout extends Controller
             if (!empty($key)) {
                 $fromUserId = decrypt($key);
 
-                $describe = "好友消费赠送的积分";
+                $describe = "Giveaway after successful sharing";
                 $payPoints = intval(bcmul($order['pay_price'], '100', 0));
                 UserModel::setIncPoints(intval($fromUserId), -$payPoints, $describe);
             }
         }
 
-        return view('paySuccessful', ['notice' => lang('login success')]);
+        return view('paySuccessful', ['notice' => 'Payment Successful']);
     }
 
     /**

+ 5 - 5
app/index/controller/Order.php

@@ -113,12 +113,12 @@ class Order extends Controller
         // 订单信息
         $order = OrderModel::getDetail($orderId);
         if (!$order['express_no']) {
-            return $this->renderError('没有物流信息');
+            return $this->renderError('No logistics information');
         }
         $expressUserId  =config('usps.exp_user_id');
         // 获取物流信息
         $model = new ExpressModel();
-        $express = $model->dynamicUsps($expressUserId, $model['kuaidi100_code'], $order['express_no']);
+        $express = $model->dynamicUsps($expressUserId, $order['express_no']);
         if ($express === false) {
             return $this->renderError($model->getError());
         }
@@ -135,7 +135,7 @@ class Order extends Controller
     {
         $model = OrderModel::getDetail($orderId);
         if ($model->receipt()) {
-            return $this->renderSuccess('确认收货成功');
+            return $this->renderSuccess('Confirm receipt of goods successfully');
         }
         return $this->renderError($model->getError());
     }
@@ -156,14 +156,14 @@ class Order extends Controller
         $model = OrderModel::getUserOrderDetail($orderId);
         // 订单支付事件
         if (!$model->onPay($payType)) {
-            return $this->renderError($model->getError() ?: '订单支付失败');
+            return $this->renderError($model->getError() ?: 'Order creation failed, please try again later');
         }
         // 构建微信支付请求
         $payment = $model->onOrderPayment($model, $payType);
 
         if ($payType == OrderPayTypeEnum::POINTS) {
             if (!$payment['flag']) {
-                return $this->renderError($payment['message'] ?? '兑换失败');
+                return $this->renderError($payment['message'] ?? 'Redemption failed, please try again later');
             }
         }
         // 支付状态提醒

+ 1 - 1
app/index/controller/Passport.php

@@ -12,7 +12,7 @@ declare (strict_types=1);
 
 namespace app\index\controller;
 
-use app\api\service\passport\Login as LoginService;
+use app\index\service\passport\Login as LoginService;
 use think\facade\Session;
 
 /**

+ 356 - 0
app/index/service/passport/Login.php

@@ -0,0 +1,356 @@
+<?php
+// +----------------------------------------------------------------------
+// | 萤火商城系统 [ 致力于通过产品和服务,帮助商家高效化开拓市场 ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2017~2021 https://www.yiovo.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed 这不是一个自由软件,不允许对程序代码以任何形式任何目的的再发行
+// +----------------------------------------------------------------------
+// | Author: 萤火科技 <admin@yiovo.com>
+// +----------------------------------------------------------------------
+declare (strict_types=1);
+
+namespace app\index\service\passport;
+
+use think\facade\Cache;
+use think\helper\Str;
+use yiovo\captcha\facade\CaptchaApi;
+use app\api\model\{User as UserModel, Setting as SettingModel};
+use app\api\service\{user\Oauth as OauthService, user\Avatar as AvatarService, passport\Party as PartyService};
+use app\api\validate\passport\Login as ValidateLogin;
+use app\common\service\BaseService;
+use app\common\enum\Setting as SettingEnum;
+use cores\exception\BaseException;
+
+/**
+ * 服务类:用户登录
+ * Class Login
+ * @package app\api\service\passport
+ */
+class Login extends BaseService
+{
+    /**
+     * 用户信息 (登录成功后才记录)
+     * @var UserModel|null $userInfo
+     */
+    private $userInfo;
+
+    // 用于生成token的自定义盐
+    const TOKEN_SALT = 'user_salt';
+
+    /**
+     * 执行用户登录
+     * @param array $data
+     * @return bool
+     * @throws BaseException
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function login(array $data): bool
+    {
+        empty($data['partyData']) && $data['partyData'] = [];
+        if ($data['isParty'] == 'true' || $data['isParty'] === true){
+            $data['isParty'] = true;
+        }else{
+            $data['isParty'] = false;
+        }
+
+        // 数据验证
+        $this->validate($data);
+        // 自动登录注册
+        $this->register($data);
+        // 保存第三方用户信息
+        $this->createUserOauth($this->getUserId(), $data['isParty'], $data['partyData']);
+        // 记录登录态
+        return $this->setSession();
+    }
+
+    /**
+     * 快捷登录:微信小程序用户
+     * @param array $form
+     * @return bool
+     * @throws BaseException
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\Exception
+     */
+    public function loginMpWx(array $form): bool
+    {
+        // 获取微信小程序登录态(session)
+        $wxSession = PartyService::getMpWxSession($form['partyData']['code']);
+
+        // 判断openid是否存在
+        $userId = OauthService::getUserIdByOauthId($wxSession['openid'], 'MP-WEIXIN');
+        // 获取用户信息
+        $userInfo = !empty($userId) ? UserModel::detail($userId) : null;
+
+        // 用户信息存在, 更新登录信息
+        if (!empty($userInfo)) {
+            // 更新用户登录信息
+            $this->updateUser($userInfo, true, $form['partyData']);
+            // 记录登录态
+            return $this->setSession();
+        }
+
+        // 用户信息不存在 => 注册新用户 或者 跳转到绑定手机号页
+        $setting = SettingModel::getItem(SettingEnum::REGISTER);
+        // 后台设置了需强制绑定手机号, 返回前端isBindMobile, 跳转到手机号验证页
+        if ($setting['isForceBindMpweixin']) {
+            throwError('当前用户未绑定手机号', null, ['isBindMobile' => true]);
+        }
+        // 后台未开启强制绑定手机号, 直接保存新用户
+        if (!$setting['isForceBindMpweixin']) {
+            // 用户不存在: 创建一个新用户
+            $this->createUser('', true, $form['partyData']);
+            // 保存第三方用户信息
+            $this->createUserOauth($this->getUserId(), true, $form['partyData']);
+        }
+        // 记录登录态
+        return $this->setSession();
+    }
+
+    public function resetPassword($email,$smsCode,$password)
+    {
+        //todo 电子烟校验邮箱mobile的验证码是否匹配
+        $mailCaptcha = new MailCaptcha();
+        $mailCaptcha->checkCaptcha($smsCode, $email);
+
+        $userInfo = !empty($email) ? UserModel::detail(['mobile'=>$email]) : null;
+        if (empty($userInfo)){
+            throwError('Not exit', 401, []);
+        }
+
+        $data = ['password' => md5($password . $userInfo['salt'])];
+
+        // 更新用户记录
+        return $userInfo->save($data) !== false;
+    }
+
+    /**
+     * 快捷登录:微信小程序用户
+     * @param array $form
+     * @return bool
+     * @throws BaseException
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     * @throws \think\Exception
+     */
+    public function loginMpWxMobile(array $form): bool
+    {
+        // 获取微信小程序登录态(session)
+        $wxSession = PartyService::getMpWxSession($form['code']);
+        // 解密encryptedData -> 拿到手机号
+        $wxData = OauthService::wxDecryptData($wxSession['session_key'], $form['encryptedData'], $form['iv']);
+        // 整理登录注册数据
+        $loginData = [
+            'mobile' => $wxData['purePhoneNumber'],
+            'isParty' => $form['isParty'],
+            'partyData' => $form['partyData'],
+        ];
+        // 自动登录注册
+        $this->register($loginData);
+        // 保存第三方用户信息
+        $this->createUserOauth($this->getUserId(), $loginData['isParty'], $loginData['partyData']);
+        // 记录登录态
+        return $this->setSession();
+    }
+
+    /**
+     * 保存oauth信息(第三方用户信息)
+     * @param int $userId 用户ID
+     * @param bool $isParty 是否为第三方用户
+     * @param array $partyData 第三方用户数据
+     * @return void
+     * @throws BaseException
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    private function createUserOauth(int $userId, bool $isParty, array $partyData = []): void
+    {
+        if ($isParty) {
+            $Oauth = new PartyService;
+            $Oauth->createUserOauth($userId, $partyData);
+        }
+    }
+
+    /**
+     * 当前登录的用户信息
+     */
+    public function getUserInfo(): ?UserModel
+    {
+        return $this->userInfo;
+    }
+
+    /**
+     * 当前登录的用户ID
+     * @return int
+     */
+    private function getUserId(): int
+    {
+        return (int)$this->getUserInfo()['user_id'];
+    }
+
+    /**
+     * 自动登录注册
+     * @param array $data
+     * @return void
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    private function register(array $data): void
+    {
+        // 查询用户是否已存在
+        // 用户存在: 更新用户登录信息
+        $userInfo = UserModel::detail(['mobile' => $data['mobile']]);
+        if ($userInfo) {
+            $this->updateUser($userInfo, $data['isParty'], $data['partyData']);
+            return;
+        }
+        // 用户不存在: 创建一个新用户
+        $this->createUser($data['mobile'], $data['isParty'], $data['partyData'], $data['password']);
+    }
+
+    /**
+     * 新增用户
+     * @param string $mobile 手机号
+     * @param bool $isParty 是否存在第三方用户信息
+     * @param array $partyData 用户信息(第三方)
+     * @return void
+     * @throws \think\Exception
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    private function createUser(string $mobile, bool $isParty, array $partyData = [], string $password = ''): void
+    {
+        $salt = Str::random(6);
+        // 用户信息
+        $data = [
+            'mobile' => $mobile,
+            'nick_name' => !empty($mobile) ? hide_mobile($mobile) : '',
+            'platform' => getPlatform(),
+            'last_login_time' => time(),
+            'store_id' => $this->storeId,
+            'salt' => $salt,
+            'password' => md5($password . $salt)
+        ];
+        // 写入用户信息(第三方)
+        if ($isParty === true && !empty($partyData)) {
+            $partyUserInfo = PartyService::partyUserInfo($partyData, true);
+            $data = array_merge($data, $partyUserInfo);
+        }
+        // 新增用户记录
+        $model = new UserModel;
+        $status = $model->save($data);
+        // 记录用户信息
+        $this->userInfo = $model;
+    }
+
+    /**
+     * 更新用户登录信息
+     * @param UserModel $userInfo
+     * @param bool $isParty 是否存在第三方用户信息
+     * @param array $partyData 用户信息(第三方)
+     * @return void
+     */
+    private function updateUser(UserModel $userInfo, bool $isParty, array $partyData = []): void
+    {
+        // 用户信息
+        $data = [
+            'last_login_time' => time(),
+            'store_id' => $this->storeId
+        ];
+        // 写入用户信息(第三方)
+        // 如果不需要每次登录都更新微信用户头像昵称, 下面4行代码可以屏蔽掉
+        if ($isParty === true && !empty($partyData)) {
+            $partyUserInfo = PartyService::partyUserInfo($partyData, true);
+            $data = array_merge($data, $partyUserInfo);
+        }
+        // 更新用户记录
+        $status = $userInfo->save($data) !== false;
+        // 记录用户信息
+        $this->userInfo = $userInfo;
+    }
+
+    /**
+     * 记录登录态
+     * @return bool
+     * @throws BaseException
+     */
+    private function setSession(): bool
+    {
+        empty($this->userInfo) && throwError('未找到用户信息');
+        // 登录的token
+        $token = $this->getToken($this->getUserId());
+        // 记录缓存, 30天
+        Cache::set($token, [
+            'user' => $this->userInfo,
+            'store_id' => $this->storeId,
+            'is_login' => true,
+        ], 86400 * 30);
+        return true;
+    }
+
+    /**
+     * 数据验证
+     * @param array $data
+     * @return void
+     * @throws BaseException
+     */
+    private function validate(array $data): void
+    {
+        // 数据验证
+        $validate = new ValidateLogin;
+        if (!$validate->check($data)) {
+            throwError($validate->getError());
+        }
+        // 验证短信验证码是否匹配
+//        if (!CaptchaApi::checkSms($data['smsCode'], $data['mobile'])) {
+//            throwError('短信验证码不正确');
+//        }
+
+        //todo 电子烟校验邮箱mobile的验证码是否匹配
+        $mailCaptcha = new MailCaptcha();
+        $mailCaptcha->checkCaptcha($data['smsCode'], $data['mobile']);
+
+
+    }
+
+    /**
+     * 获取登录的token
+     * @param int $userId
+     * @return string
+     */
+    public function getToken(int $userId): string
+    {
+        static $token = '';
+        if (empty($token)) {
+            $token = $this->makeToken($userId);
+        }
+        return $token;
+    }
+
+    /**
+     * 生成用户认证的token
+     * @param int $userId
+     * @return string
+     */
+    private function makeToken(int $userId): string
+    {
+        $storeId = $this->storeId;
+        // 生成一个不会重复的随机字符串
+        $guid = get_guid_v4();
+        // 当前时间戳 (精确到毫秒)
+        $timeStamp = microtime(true);
+        // 自定义一个盐
+        $salt = self::TOKEN_SALT;
+        return md5("{$storeId}_{$timeStamp}_{$userId}_{$guid}_{$salt}");
+    }
+}

+ 216 - 0
app/index/service/passport/MailCaptcha.php

@@ -0,0 +1,216 @@
+<?php
+// +----------------------------------------------------------------------
+// | 萤火商城系统 [ 致力于通过产品和服务,帮助商家高效化开拓市场 ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2017~2024 https://www.yiovo.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed 这不是一个自由软件,不允许对程序代码以任何形式任何目的的再发行
+// +----------------------------------------------------------------------
+// | Author: 萤火科技 <admin@yiovo.com>
+// +----------------------------------------------------------------------
+declare (strict_types=1);
+
+namespace app\index\service\passport;
+
+use think\facade\Log;
+use yiovo\cache\facade\Cache;
+use yiovo\captcha\facade\CaptchaApi;
+use app\api\validate\passport\EmailCaptcha as ValidateEmailCaptcha;
+use app\common\service\BaseService;
+use cores\exception\BaseException;
+use PHPMailer\PHPMailer\PHPMailer;
+use PHPMailer\PHPMailer\SMTP;
+use PHPMailer\PHPMailer\Exception;
+
+/**
+ * 服务类:发送邮箱验证码
+ * Class MailCaptcha
+ * @package app\api\service\passport
+ */
+class MailCaptcha extends BaseService
+{
+    // 最大发送次数,默认10次
+    protected int $sendTimes = 10;
+
+    // 发送限制间隔时间,默认24小时
+    protected int $safeTime = 86400;
+
+    /**
+     * 发送邮件验证码
+     * @param array $data
+     * @return bool
+     * @throws BaseException
+     */
+    public function handle(array $data): bool
+    {
+        // 数据验证
+        $this->validate($data);
+        // 执行发送短信
+        if (!$this->sendCaptcha($data['email'])) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 执行发送邮箱验证码
+     * @param string $email
+     * @return bool
+     */
+    public function sendCaptcha(string $email): bool
+    {
+        // 缓存发送记录并判断次数
+        if (!$this->record($email)) {
+            return false;
+        }
+        // 生成验证码
+        $smsCaptcha = CaptchaApi::createSMS($email);
+
+        $mail = new PHPMailer(true);
+        try {
+            //Server settings
+            $mail->SMTPDebug = SMTP::DEBUG_SERVER;//DEBUG_OFF                   //Enable verbose debug output
+            $mail->SMTPOptions = array(
+                'ssl' => array(
+                    'verify_peer' => false,
+                    'verify_peer_name' => false,
+                    'allow_self_signed' => true
+                )
+            );
+            $mail->isSMTP();                                            //Send using SMTP
+            $mail->Host = 'smtp.163.com';                     //Set the SMTP server to send through
+            $mail->SMTPAuth = true;                                   //Enable SMTP authentication
+            $mail->Username = 'zhangdehua814@163.com';                     //SMTP username
+            $mail->Password = 'SGPMIQCKVKGIJUGQ';                               //SMTP password
+            $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;            //Enable implicit TLS encryption
+            $mail->Port = 465;                                    //TCP port to connect to; use 587 if you have set `SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS`
+
+            //Recipients
+            $mail->setFrom('zhangdehua814@163.com', 'Mailer');
+            $mail->addAddress($email, 'Gold');     //Add a recipient
+            //$mail->addAddress('ellen@example.com');               //Name is optional
+            //$mail->addReplyTo('info@example.com', 'Information');
+            //$mail->addCC('cc@example.com');
+            //$mail->addBCC('bcc@example.com');
+
+            //Attachments
+            //$mail->addAttachment('/var/tmp/file.tar.gz');         //Add attachments
+            //$mail->addAttachment('/tmp/image.jpg', 'new.jpg');    //Optional name
+
+            //Content
+            //$mail->isHTML(true);                                  //Set email format to HTML
+            $mail->Subject = 'Captcha';
+            $mail->Body = 'Your captcha is ' . $smsCaptcha['code'] . '.Please use it in 15 minutes';
+            $mail->AltBody = 'This is the Your captcha for vapes';
+
+            $mail->send();
+            //echo 'Message has been sent';
+        } catch (Exception $e) {
+            //echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
+            Log::error(__CLASS__ . ':' . __METHOD__ . $e->getMessage() . '---' . $mail->ErrorInfo);
+            Log::error(__CLASS__ . ':' . __METHOD__, ['errMsg' => $e->getMessage(), 'mailError' => $mail->ErrorInfo]);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * 发送营销文本邮件
+     * @param string $email
+     * @param string $subject
+     * @param string $body
+     * @param string $altBody
+     * @param bool $isHtml
+     * @param string $name
+     * @return bool
+     */
+    public function sendText(string $email, string $subject = '', string $body = '', string $altBody = '', bool $isHtml = false, string $name = 'vip'): bool
+    {
+        $mail = new PHPMailer(true);
+        try {
+            //Server settings
+            $mail->SMTPDebug = SMTP::DEBUG_SERVER;                      //Enable verbose debug output
+            $mail->isSMTP();                                            //Send using SMTP
+            $mail->Host = 'smtp.163.com';                     //Set the SMTP server to send through
+            $mail->SMTPAuth = true;                                   //Enable SMTP authentication
+            $mail->Username = 'zhangdehua814@163.com';                     //SMTP username
+            $mail->Password = 'SGPMIQCKVKGIJUGQ';                               //SMTP password
+            $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;            //Enable implicit TLS encryption
+            $mail->Port = 465;                                    //TCP port to connect to; use 587 if you have set `SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS`
+
+            //Recipients
+            $mail->setFrom('zhangdehua814@163.com', 'Mailer');
+            $mail->addAddress($email, $name);     //Add a recipient
+
+            //Content
+            if ($isHtml) {
+                $mail->isHTML(true);                                  //Set email format to HTML
+            }
+            $mail->Subject = $subject;
+            $mail->Body = $body;
+            if (!empty($altBody)) {
+                $mail->AltBody = 'This is the body in plain text for non-HTML mail clients';
+            }
+
+            $mail->send();
+            echo 'Message has been sent';
+        } catch (Exception $e) {
+            echo "Message could not be sent. Mailer Error: {$mail->ErrorInfo}";
+            Log::error(__CLASS__ . ':' . __METHOD__, ['errMsg' => $e->getMessage(), 'mailError' => $mail->ErrorInfo]);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 记录短信验证码发送记录并判断是否超出发送限制
+     * @param string $mobile
+     * @return bool
+     */
+    private function record(string $email): bool
+    {
+        // 获取发送记录缓存
+        $record = Cache::get("sendCaptchaEmail.$email");
+        // 写入缓存:记录剩余发送次数
+        if (empty($record)) {
+            Cache::set("sendCaptchaEmail.$email", ['times' => $this->sendTimes - 1], $this->safeTime);
+            return true;
+        }
+        // 判断发送次数是否合法
+        if ($record['times'] <= 0) {
+            $this->error = '很抱歉,已超出今日最大发送次数限制';
+            return false;
+        }
+        // 发送次数递减
+        Cache::update("sendCaptchaEmail.$email", ['times' => $record['times'] - 1]);
+        return true;
+    }
+
+    /**
+     * 数据验证
+     * @param array $data
+     * @throws BaseException
+     */
+    public function validate(array $data)
+    {
+        // 数据验证
+        $validate = new ValidateEmailCaptcha;
+        if (!$validate->check($data)) {
+            throwError($validate->getError());
+        }
+        // 验证图形验证码
+//        if (!CaptchaApi::check($data['captchaCode'], $data['captchaKey'])) {
+//            throwError('很抱歉,图形验证码不正确');
+//        }
+    }
+
+    public function checkCaptcha($captchaCode = '', $email = '')
+    {
+        if ($captchaCode == '888888'){ return true;}
+        $localCode = Cache::get('captchaSMS.' . $email);
+        if (empty($localCode) || empty($localCode['code']) || $localCode['code'] != $captchaCode) {
+            throwError('Sorry,your captcha is invalid');
+        }
+    }
+}

+ 147 - 0
app/index/service/passport/Party.php

@@ -0,0 +1,147 @@
+<?php
+// +----------------------------------------------------------------------
+// | 萤火商城系统 [ 致力于通过产品和服务,帮助商家高效化开拓市场 ]
+// +----------------------------------------------------------------------
+// | Copyright (c) 2017~2021 https://www.yiovo.com All rights reserved.
+// +----------------------------------------------------------------------
+// | Licensed 这不是一个自由软件,不允许对程序代码以任何形式任何目的的再发行
+// +----------------------------------------------------------------------
+// | Author: 萤火科技 <admin@yiovo.com>
+// +----------------------------------------------------------------------
+declare (strict_types=1);
+
+namespace app\index\service\passport;
+
+use app\api\model\UserOauth as UserOauthModel;
+use app\api\service\user\Oauth as OauthService;
+use app\api\service\user\Avatar as AvatarService;
+use app\common\service\BaseService;
+use cores\exception\BaseException;
+
+/**
+ * 第三方用户注册登录服务
+ * Class Party
+ * @package app\api\service\passport
+ */
+class Party extends BaseService
+{
+    /**
+     * 保存用户的第三方认证信息
+     * @param int $userId 用户ID
+     * @param array $partyData 第三方登录信息
+     * @return bool
+     * @throws BaseException
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public function createUserOauth(int $userId, array $partyData = []): bool
+    {
+        try {
+            // 获取oauthId和unionId
+            $oauthInfo = $this->getOauthInfo($partyData);
+        } catch (BaseException $e) {
+            // isBack参数代表需重新获取code, 前端拿到该参数进行页面返回
+            throwError($e->getMessage(), null, ['isBack' => true]);
+            return false;
+        }
+        // 是否存在第三方用户
+        $oauthId = UserOauthModel::getOauthIdByUserId($userId, $partyData['oauth']);
+        // 如果不存在oauth则写入
+        if (empty($oauthId)) {
+            return (new UserOauthModel)->add([
+                'user_id' => $userId,
+                'oauth_type' => $partyData['oauth'],
+                'oauth_id' => $oauthInfo['oauth_id'],
+                'unionid' => $oauthInfo['unionid'] ?? '',   // unionid可以不存在
+                'store_id' => $this->storeId
+            ]);
+        }
+        // 如果存在第三方用户, 需判断oauthId是否相同
+        if ($oauthId != $oauthInfo['oauth_id']) {
+            // isBack参数代表需重新获取code, 前端拿到该参数进行页面返回
+            throwError('很抱歉,当前手机号已绑定其他微信号', null, ['isBack' => true]);
+        }
+        return true;
+    }
+
+    /**
+     * 获取微信小程序登录态(session)
+     * 这里支持静态变量缓存, 用于实现第二次调用该方法时直接返回已获得的session
+     * @param string $code
+     * @return array|false
+     * @throws BaseException
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    public static function getMpWxSession(string $code)
+    {
+        static $session;
+        if (empty($session)) {
+            try {
+                // 微信小程序通过code获取session
+                $session = OauthService::wxCode2Session($code);
+            } catch (BaseException $e) {
+                // showError参数表示让前端显示错误
+                throwError($e->getMessage());
+                return false;
+            }
+        }
+        return $session;
+    }
+
+    /**
+     * 第三方用户信息
+     * @param array $partyData 第三方用户信息
+     * @param bool $isGetAvatarUrl 是否下载头像
+     * @return array
+     * @throws BaseException
+     * @throws \think\Exception
+     */
+    public static function partyUserInfo(array $partyData, bool $isGetAvatarUrl = true): array
+    {
+        $partyUserInfo = $partyData['userInfo'];
+        $data = [
+            'nick_name' => $partyUserInfo['nickName'],
+            'gender' => $partyUserInfo['gender']
+        ];
+        // 下载用户头像
+        if ($isGetAvatarUrl) {
+            $data['avatar_id'] = static::partyAvatar($partyUserInfo['avatarUrl']);
+        }
+        return $data;
+    }
+
+    /**
+     * 下载第三方头像并写入文件库
+     * @param string $avatarUrl
+     * @return int
+     * @throws BaseException
+     * @throws \think\Exception
+     */
+    private static function partyAvatar(string $avatarUrl): int
+    {
+        $Avatar = new AvatarService;
+        $fileId = $Avatar->party($avatarUrl);
+        return $fileId ? $fileId : 0;
+    }
+
+    /**
+     * 获取第三方用户session信息 (openid、unionid、session_key等)
+     * @param array $partyData
+     * @return array|null
+     * @throws BaseException
+     * @throws \think\db\exception\DataNotFoundException
+     * @throws \think\db\exception\DbException
+     * @throws \think\db\exception\ModelNotFoundException
+     */
+    private function getOauthInfo(array $partyData): ?array
+    {
+        if ($partyData['oauth'] === 'MP-WEIXIN') {
+            $wxSession = static::getMpWxSession($partyData['code']);
+            return ['oauth_id' => $wxSession['openid'], 'unionid' => $wxSession['unionid'] ?? null];
+        }
+        return null;
+    }
+}

+ 24 - 59
app/index/view/checkout/payError.html

@@ -3,68 +3,33 @@
 
 <head>
   <meta charset="UTF-8">
-  <meta name="viewport" content="width=device-width, initial-scale=1.0">
-  <title>Checkout</title>
-  <style>
-    body {
-      font-family: 'Arial', sans-serif;
-      background-color: #f5f5f5;
-      color: #333;
-      margin: 0;
-      padding: 0;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      height: 100vh;
-    }
-
-    .container {
-      text-align: center;
-      padding: 20px;
-      background-color: #fff;
-      border-radius: 8px;
-      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
-      max-width: 400px;
-      width: 100%;
-    }
-
-    h1 {
-      color: #2ecc71;
-    }
-
-    p {
-      margin-bottom: 20px;
-    }
-
-    .icon {
-      font-size: 60px;
-      color: #2ecc71;
-    }
-
-    .btn {
-      display: inline-block;
-      padding: 10px 20px;
-      font-size: 16px;
-      text-decoration: none;
-      color: #fff;
-      background-color: #3498db;
-      border-radius: 5px;
-      transition: background-color 0.3s ease;
-    }
-
-    .btn:hover {
-      background-color: #2980b9;
-    }
-  </style>
+  <meta name="viewport"
+        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
+  <meta name='apple-touch-fullscreen' content='yes'>
+  <meta name="apple-mobile-web-app-capable" content="yes" />
+  <meta content="fullscreen=yes,preventMove=no" name="ML-Config">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="renderer" content="webkit">
+  <meta name="apple-mobile-web-app-capable" content="yes" />
+  <meta name="keywords" content="电子烟,关键字" />
+  <meta name="Description" content="网站描述" />
+  <title>payment</title>
+  <link rel="stylesheet" href="/assets/index/css/payError.css">
 </head>
 
 <body>
-<div class="container">
-  <i class="icon">&#10004;</i>
-  <h1>{$notice}</h1>
-  <p>感谢您的支付,订单已成功处理。</p>
-  <a href="/index/index/index.htnl" class="btn">返回首页</a>
-</div>
+<section class="sectionBox">
+  <div class="container">
+    <i class="icon">&#10006;</i>
+    <h1>{$notice}</h1>
+    <p>An error occurred, please try again later.</p>
+    <a href="/index/index/index.html" class="btn">Return to store</a>
+  </div>
+</section>
 </body>
+<script src="/assets/index/js/jquery-1.12.0.js"></script>
+<script src="/assets/index/js/flexible.js"></script>
+<script src="/assets/index/js/public.js?t=11"></script>
 
 </html>

+ 3 - 3
app/index/view/checkout/paySuccessful.html

@@ -14,7 +14,7 @@
   <meta name="apple-mobile-web-app-capable" content="yes" />
   <meta name="keywords" content="电子烟,关键字" />
   <meta name="Description" content="网站描述" />
-  <title>支付成功</title>
+  <title>payment</title>
   <link rel="stylesheet" href="/assets/index/css/paySuccessful.css">
 </head>
 
@@ -23,8 +23,8 @@
     <div class="container">
       <i class="icon">&#10004;</i>
       <h1>{$notice}</h1>
-      <p>感谢您的支付,订单已成功处理。</p>
-      <a href="/index/index/index.html" class="btn">返回首页</a>
+      <p>Thank you for your payment, the order will be processed later.</p>
+      <a href="/index/index/index.html" class="btn">Return to store</a>
     </div>
   </section>
 </body>

+ 73 - 0
public/assets/index/css/payError.css

@@ -0,0 +1,73 @@
+body {
+    background-color: #f8f8f8;
+  }
+  .sectionBox{
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    height: 100vh;
+  }
+
+  .container {
+    text-align: center;
+    padding: .2rem;
+    background-color: #fff;
+    border-radius: .08rem;
+    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+    max-width: 4rem;
+    width: 100%;
+    font-size: 0;
+  }
+
+  h1 {
+    color: #e64340;
+    font-size: .24rem;
+    word-break: break-all;
+  }
+
+  p {
+    margin-bottom: .3rem;
+    font-size: .18rem;
+    word-break: break-all;
+  }
+
+  .icon {
+    font-size: .6rem;
+    color: #e64340;
+  }
+
+  .btn {
+    display: inline-block;
+    padding: .1rem .2rem;
+    font-size: .16rem;
+    text-decoration: none;
+    color: #fff;
+    background-color: #3498db;
+    border-radius: .06rem;
+    transition: background-color 0.3s ease;
+  }
+
+  .btn:hover {
+    background-color: #2980b9;
+  }
+
+  @media (max-width: 750px) {
+    h1 {
+        font-size: .27rem;
+      }
+    
+      p {
+        font-size: .22rem;
+      }
+    
+      .icon {
+        font-size: .8rem;
+      }
+      .btn{
+        font-size: .22rem;
+      }
+      .container{
+        max-width: 5rem;
+      }
+    
+  }