453 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			PHP
		
	
	
	
			
		
		
	
	
			453 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			PHP
		
	
	
	
| <?php
 | ||
| 
 | ||
| namespace app\service;
 | ||
| 
 | ||
| use app\config\BusinessCacheKey;
 | ||
| use app\exception\BusinessException;
 | ||
| use app\exception\LogicException;
 | ||
| use app\model\Order;
 | ||
| use app\model\Product;
 | ||
| use app\model\Sign;
 | ||
| use app\model\User;
 | ||
| use app\service\util\CmbLifeUtils;
 | ||
| use app\service\util\SmsUtil;
 | ||
| use app\sms\AliSms;
 | ||
| use app\util\CmbHttpUtils;
 | ||
| use app\util\StringUtil;
 | ||
| use think\facade\Db;
 | ||
| use think\facade\Log;
 | ||
| 
 | ||
| class AgreementService extends BaseService
 | ||
| {
 | ||
|     /**
 | ||
|      * 生成签约协议
 | ||
|      * @param int $userId
 | ||
|      * @return mixed
 | ||
|      * @throws \think\db\exception\DataNotFoundException
 | ||
|      * @throws \think\db\exception\DbException
 | ||
|      * @throws \think\db\exception\ModelNotFoundException
 | ||
|      */
 | ||
|     public static function agreeApproval(int $userId, string $callback, string $account): string
 | ||
|     {
 | ||
|         $cmbConfig = config('cmb');
 | ||
|         $user = User::getUserById($userId);
 | ||
|         if ($user->isEmpty()) {
 | ||
|             throw new LogicException('用户不存在!');
 | ||
|         }
 | ||
|         // 查询当前用户是否可再签约
 | ||
|         $isCanAgree = self::checkCanAgree($userId, $account);
 | ||
|         if (!$isCanAgree['success']) {
 | ||
|             throw new \LogicException($isCanAgree['msg']);
 | ||
|         }
 | ||
|         $params = [
 | ||
|             'mAgreementId' => StringUtil::makeAgreementId(),
 | ||
|             'merchantUserId' => StringUtil::maskPhone($account),
 | ||
|             'requestSerial' => uniqid(), // 请求流水号(商户侧唯一)
 | ||
|             'notifyUrl' => $cmbConfig['agree_notify_url'],
 | ||
|             'callback' => $callback,
 | ||
|         ];
 | ||
|         $sign = Sign::getWaitSignByUserId($userId);
 | ||
|         if (!$sign->isEmpty()) {
 | ||
|             $params['mAgreementId'] = $sign->m_agreement_id;
 | ||
|             $params['requestSerial'] = $sign->request_serial;
 | ||
|         }
 | ||
|         $funcName = 'authorize';
 | ||
|         // 生成新签约记录
 | ||
|         Db::startTrans();
 | ||
|         try {
 | ||
|             if (!$sign->isEmpty()) { // 更新
 | ||
|                 $sign->mobile = $account;
 | ||
|                 $sign->create_time = time();
 | ||
|                 $sign->save();
 | ||
|                 $order = Order::getByAgreementId($sign->m_agreement_id);
 | ||
|                 $order->account = $account;
 | ||
|                 $order->create_time = time();
 | ||
|                 $order->save();
 | ||
|             } else {
 | ||
|                 Sign::create([
 | ||
|                     'user_id' => $userId,
 | ||
|                     'open_id' => $user->open_id,
 | ||
|                     'mobile' => $account,
 | ||
|                     'request_serial' => $params['requestSerial'],
 | ||
|                     'm_agreement_id' => $params['mAgreementId']
 | ||
|                 ]);
 | ||
|                 // 创建订单
 | ||
|                 $data = [
 | ||
|                     'user_id' => $user->id,
 | ||
|                     'product_id' => config('cmb.product_id'),
 | ||
|                     'account' => $account,
 | ||
|                     'agreement_id' => $params['mAgreementId']
 | ||
|                 ];
 | ||
|                 CmbService::createOrder($data);
 | ||
|             }
 | ||
|             Db::commit();
 | ||
|         } catch (\Throwable $e) {
 | ||
|             Db::rollback();
 | ||
|             throw new \LogicException($e->getMessage());
 | ||
|         }
 | ||
|         return CmbLifeUtils::genProtocol($funcName, $params);
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * 签约通知
 | ||
|      * @param $data
 | ||
|      * @return array
 | ||
|      */
 | ||
|     public static function agreeNotify($data)
 | ||
|     {
 | ||
|         Log::info('签约回调:' . json_encode($data));
 | ||
|         $verifyRes = CmbLifeUtils::verifyForResponse($data);
 | ||
|         if (!$verifyRes) {
 | ||
|             return ['respCode' => 1001, 'msg' => '验签不通过'];
 | ||
|         }
 | ||
|         $requestSerial = $data['requestSerial'];
 | ||
|         $sign = Sign::getByRequestSerial($requestSerial);
 | ||
|         if ($sign->isEmpty()) {
 | ||
|             return ['respCode' => 1000, 'msg' => '处理成功'];
 | ||
|         }
 | ||
|         $sign->sign_status = Sign::SIGN_STATUS_SUCCESS;
 | ||
|         $sign->agreement_id = $data['agreementId'];
 | ||
|         $sign->save();
 | ||
|         // 签约成功开始扣款
 | ||
|         $order = Order::getWaitSignByAgreementId($data['mAgreementId']);
 | ||
|         if ($order->isEmpty() || $order->order_status == Order::STATUS_SIGNED) {
 | ||
|             return ['respCode' => 1000, 'msg' => '处理成功'];
 | ||
|         }
 | ||
|         $order->order_status = Order::STATUS_SIGNED;
 | ||
|         $order->save();
 | ||
|         // 签约第一次扣款
 | ||
|         self::firstPay($sign, $order);
 | ||
| //        if ($agreePayRes['respCode'] != '1000') {
 | ||
| //            return ['respCode' => 1001, 'msg' => '扣款失败' . $agreePayRes['respMsg']];
 | ||
| //        }
 | ||
|         return ['respCode' => 1000, 'msg' => '处理成功'];
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * 签约状态查询处理
 | ||
|      * @return mixed
 | ||
|      */
 | ||
|     public static function queryAgreementStatus(Sign $sign)
 | ||
|     {
 | ||
|         $funcName = 'queryAgreementStatus';
 | ||
|         $requestParams = [
 | ||
|             'mAgreementId' => $sign->m_agreement_id // 商户端协议号
 | ||
|         ];
 | ||
|         $res = CmbHttpUtils::doPost($funcName, $requestParams);
 | ||
|         if ($res['respCode'] !== '1000') {
 | ||
|             return true;
 | ||
|         }
 | ||
|         $sign->sign_status = $res['status'];
 | ||
|         $sign->agreement_id = $res['agreementId'];
 | ||
|         $sign->save();
 | ||
|         $agreementId = $sign->m_agreement_id;
 | ||
|         // 如果待签约未扣款订单开始执行扣款操作
 | ||
|         $order = Order::getWaitSignByAgreementId($agreementId);
 | ||
|         if ($res['status'] != 1 || $order->isEmpty()) {
 | ||
|             return true;
 | ||
|         }
 | ||
|         $order->order_status = Order::STATUS_SIGNED;
 | ||
|         $order->save();
 | ||
|         // 签约完成开始处理第一次签约扣款
 | ||
|         self::firstPay($sign, $order);
 | ||
|         return true;
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * 第一次扣款
 | ||
|      * @param Sign $collection
 | ||
|      * @param Order $orderCollection
 | ||
|      * @return mixed|string
 | ||
|      */
 | ||
|     public static function firstPay(Sign $collection, Order $orderCollection)
 | ||
|     {
 | ||
|         $productId = $orderCollection->product_id;
 | ||
|         $product = Product::getBySupplierProductId($productId);
 | ||
|         // 扣款参数
 | ||
|         $requestParams = [
 | ||
|             'agreementId' => $collection->agreement_id,
 | ||
|             'billNo' => $orderCollection->order_number,
 | ||
|             'amount' => $orderCollection->price * 100,
 | ||
|             'bonus' => $orderCollection->bonus,
 | ||
|             'notifyUrl' => config('cmb.pay_notify_url'),
 | ||
|             'productName' => $product['name']
 | ||
|         ];
 | ||
|         $funcName = 'agreementPay';
 | ||
|         return CmbHttpUtils::doPost($funcName, $requestParams);
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * 处理协议扣款
 | ||
|      * @param Sign $collection
 | ||
|      * @param Order $orderCollection
 | ||
|      * @return mixed
 | ||
|      * @throws \think\db\exception\DbException
 | ||
|      */
 | ||
|     public static function agreementPay(Sign $collection, Order $orderCollection)
 | ||
|     {
 | ||
|         $productId = $orderCollection->product_id;
 | ||
|         $product = Product::getBySupplierProductId($productId);
 | ||
|         $order = [
 | ||
|             'user_id' => $orderCollection->user_id,
 | ||
|             'order_number' => StringUtil::makeOrderNumber(),
 | ||
|             'account' => $orderCollection->account,
 | ||
|             'type' => $orderCollection->type,
 | ||
|             'product_id' => $orderCollection->product_id,
 | ||
|             'price' => $product['price'],
 | ||
|             'agreement_id' => $orderCollection->agreement_id,
 | ||
|             'order_status' => Order::STATUS_SIGNED
 | ||
|         ];
 | ||
|         // 查询第几次扣款
 | ||
|         $count = Order::getCountByAgreementId($collection->m_agreement_id);
 | ||
|         if ($count < 2) {
 | ||
|             $order['price'] = config('cmb.continue_price');
 | ||
|         }
 | ||
|         // 生成连续包月订单
 | ||
|         Order::create($order);
 | ||
|         // 扣款参数
 | ||
|         $requestParams = [
 | ||
|             'agreementId' => $collection->agreement_id,
 | ||
|             'billNo' => $order['order_number'],
 | ||
|             'amount' => $order['price'] * 100,
 | ||
|             'notifyUrl' => config('cmb.pay_notify_url'),
 | ||
|             'productName' => $product['name']
 | ||
|         ];
 | ||
|         $funcName = 'agreementPay';
 | ||
|         return CmbHttpUtils::doPost($funcName, $requestParams);
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * 扣款失败,重试
 | ||
|      *
 | ||
|      * @param string $orderNumber
 | ||
|      * @param string $agreementId
 | ||
|      * @return array|mixed|string
 | ||
|      */
 | ||
|     public static function RetryPayOrder(string $orderNumber, string $agreementId)
 | ||
|     {
 | ||
|         //  判断订单状态是否正确
 | ||
|         $order = Order::getByOrderNumber($orderNumber);
 | ||
|         if ($order->isEmpty() || $order->pay_status != Order::PAY_STATUS_FAIL || $order->is_retry == Order::RETRY_STATUS_YES) {
 | ||
|             return ['respCode' => 1000, 'respMsg' => '处理成功'];
 | ||
|         }
 | ||
| 
 | ||
|         //  处理订单再次扣款
 | ||
|         //  创建新的订单
 | ||
|         $productId = $order->product_id;
 | ||
|         $product = Product::getBySupplierProductId($productId);
 | ||
|         $newOrder = [
 | ||
|             'user_id' => $order->user_id,
 | ||
|             'order_number' => StringUtil::makeOrderNumber(),
 | ||
|             'account' => $order->account,
 | ||
|             'type' => $order->type,
 | ||
|             'product_id' => $order->product_id,
 | ||
|             'price' => $order->price,
 | ||
|             'bonus' => $order->bonus,
 | ||
|             'agreement_id' => $order->agreement_id,
 | ||
|             'order_status' => Order::STATUS_SIGNED
 | ||
|         ];
 | ||
| 
 | ||
|         // 生成重试订单
 | ||
|         Db::startTrans();
 | ||
|         try {
 | ||
|             //  创建新订单
 | ||
|             Order::create($newOrder);
 | ||
|             //  更新老订单
 | ||
|             $order->is_retry = Order::RETRY_STATUS_YES;
 | ||
|             $order->retry_order_num = $newOrder['order_number'];
 | ||
|             $res = $order->save();
 | ||
|             if (!$res) {
 | ||
|                 throw new \Exception("重试支付,更新老订单失败,订单号:" . $orderNumber);
 | ||
|             }
 | ||
|             Db::commit();
 | ||
|         } catch (\Exception $e) {
 | ||
|             Db::rollback();
 | ||
|             Log::error("Job:PayOrderRetry,错误信息:" . $e->getMessage());
 | ||
|             return ['respCode' => 1001, 'respMsg' => $e->getMessage()];
 | ||
|         }
 | ||
| 
 | ||
|         // 扣款参数
 | ||
|         $requestParams = [
 | ||
|             'agreementId' => $agreementId,
 | ||
|             'billNo' => $newOrder['order_number'],
 | ||
|             'amount' => $newOrder['price'] * 100,
 | ||
|             'notifyUrl' => config('cmb.pay_notify_url'),
 | ||
|             'productName' => $product['name']
 | ||
|         ];
 | ||
|         $funcName = 'agreementPay';
 | ||
|         return CmbHttpUtils::doPost($funcName, $requestParams);
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * 协议扣款回调处理
 | ||
|      * @param array $data
 | ||
|      * @return int[]
 | ||
|      */
 | ||
|     public static function payNotify(array $data)
 | ||
|     {
 | ||
|         Log::info('付款回调:' . json_encode($data));
 | ||
|         $verifyRes = CmbLifeUtils::verifyForResponse($data);
 | ||
|         if (!$verifyRes) {
 | ||
|             return ['respCode' => 1001, 'respMsg' => '签名有误'];
 | ||
|         }
 | ||
|         $encryptBody = $data['encryptBody'] ?? '';
 | ||
|         if (empty($encryptBody)) {
 | ||
|             return ['respCode' => 1000, 'respMsg' => '处理成功'];
 | ||
|         }
 | ||
|         $encryptData = CmbLifeUtils::decrypt($encryptBody, config('cmb.merchant_sm2_pri_key'));
 | ||
|         $orderNumber = $encryptData['billNo'];
 | ||
|         $order = Order::getByOrderNumber($orderNumber);
 | ||
|         if ($order->isEmpty() || $order->pay_status == Order::PAY_STATUS_PAID) {
 | ||
|             return ['respCode' => 1000, 'respMsg' => '处理成功'];
 | ||
|         }
 | ||
|         $update = [
 | ||
|             'pay_time' => isset($encryptData['payDate']) ? strtotime($encryptData['payDate']) : time(),
 | ||
|             'pay_status' => $encryptData['result'],
 | ||
|             'ref_num' => $encryptData['refNum'] ?? '',
 | ||
|             'pay_type' => $encryptData['payType'] ?? '',
 | ||
|             'pay_card_no' => $encryptData['shieldCardNo'] ?? '',
 | ||
|         ];
 | ||
|         if ($encryptData['result'] == 2) {
 | ||
|             $update['order_status'] = Order::STATUS_WAIT_RECHARGE;
 | ||
|         }
 | ||
|         $res = Order::update($update, ['order_number' => $orderNumber]);
 | ||
|         if ($encryptData['result'] == 2) {
 | ||
|             RechargeService::rechargeOrder($orderNumber); // 支付成功,到直连天下充值
 | ||
|         }
 | ||
|         //  支付失败
 | ||
|         if ($encryptData['result'] == 3 && BaseService::isLastDayOfMonth()) {
 | ||
|             CmbService::releaseMerchant($order->agreement_id);
 | ||
|         }
 | ||
|         if ($res) {
 | ||
|             return ['respCode' => 1000, 'respMsg' => '处理成功'];
 | ||
|         } else {
 | ||
|             return ['respCode' => 1001, 'respMsg' => '处理失败'];
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * 解约处理
 | ||
|      * @param array $data
 | ||
|      * @return int[]
 | ||
|      */
 | ||
|     public static function releaseNotify(array $data)
 | ||
|     {
 | ||
|         Log::info('解约回调:' . json_encode($data));
 | ||
|         $verifyRes = CmbLifeUtils::verifyForResponse($data);
 | ||
|         if (!$verifyRes) {
 | ||
|             return ['respCode' => 1001, 'msg' => '验签不通过'];
 | ||
|         }
 | ||
|         $requestSerial = $data['requestSerial'];
 | ||
|         $sign = Sign::getByRequestSerial($requestSerial);
 | ||
|         if ($sign->isEmpty() || $sign->sign_status == Sign::SIGN_STATUS_RELEASE) {
 | ||
|             return ['respCode' => 1000, 'respMsg' => '处理成功'];
 | ||
|         }
 | ||
|         $sign->sign_status = Sign::SIGN_STATUS_RELEASE;
 | ||
|         $res = $sign->save();
 | ||
|         if ($res) {
 | ||
|             return ['respCode' => 1000, 'respMsg' => '处理成功'];
 | ||
|         } else {
 | ||
|             return ['respCode' => 1000, 'respMsg' => '处理失败'];
 | ||
|         }
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * 取消订阅
 | ||
|      * @param int $userId
 | ||
|      * @return mixed
 | ||
|      */
 | ||
|     public static function unsubscribe($params)
 | ||
|     {
 | ||
|         $userId = $params['user_id'];
 | ||
|         // 校验验证码
 | ||
|         if (!in_array($params['phone'], ['13205115489', '13305093595', '15107950285'])) {
 | ||
|             // 校验验证码
 | ||
|             $codeCacheKey = SmsUtil::getSmsKey($params['phone']);
 | ||
|             SmsUtil::compareSmsCode($codeCacheKey, $params['code']);
 | ||
|         }
 | ||
|         // 用户解约
 | ||
|         $signInfo = Sign::getSubscribeByUserId($userId);
 | ||
|         if ($signInfo->isEmpty()) {
 | ||
|             throw new LogicException('该用户无可退订商品');
 | ||
|         }
 | ||
|         // 解约
 | ||
|         $funcName = 'releaseForMerchant';
 | ||
|         $requestParams = [
 | ||
|             'mAgreementId' => $signInfo->m_agreement_id,
 | ||
|             'merchantUserId' => StringUtil::maskPhone($signInfo->mobile)
 | ||
|         ];
 | ||
|         $res = CmbHttpUtils::doPost($funcName, $requestParams);
 | ||
|         if ($res['respCode'] == '1000') {
 | ||
|             $signInfo->sign_status = Sign::SIGN_STATUS_RELEASE;
 | ||
|             $signInfo->save();
 | ||
|         }
 | ||
|         return true;
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * 校验是否能签约
 | ||
|      * @param int $userId
 | ||
|      * @param string $account
 | ||
|      * @return array
 | ||
|      * @throws \think\db\exception\DbException
 | ||
|      */
 | ||
|     public static function checkCanAgree(int $userId, string $account)
 | ||
|     {
 | ||
|         // 1. 该用户已经签约
 | ||
|         $sign = Sign::getSignLastYear($userId);
 | ||
|         if (!$sign->isEmpty()) {
 | ||
|             return ['success' => false, 'msg' => '该用户已签约'];
 | ||
|         }
 | ||
|         if (!empty($account)) {
 | ||
|             $mobileSign = Sign::getSignLastYearByMobile($account);
 | ||
|             if (!$mobileSign->isEmpty()) {
 | ||
|                 return ['success' => false, 'msg' => '该手机号已签约!'];
 | ||
|             }
 | ||
|         }
 | ||
|         // 3. 该用户解约过且近一年扣款过
 | ||
|         $releaseSign = Sign::getReleaseLastYear($userId);
 | ||
|         $orderCount = Order::getMonthOrderCountByUserId($userId);
 | ||
|         if (!$releaseSign->isEmpty() && $orderCount > 0) {
 | ||
|             return ['success' => false, 'msg' => '该用户一年内存在签约!'];
 | ||
|         }
 | ||
|         return ['success' => true];
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * 解约发送验证码
 | ||
|      * @throws BusinessException
 | ||
|      */
 | ||
|     public static function releaseSendSms($params)
 | ||
|     {
 | ||
|         $key = SmsUtil::getSmsKey($params['phone']);
 | ||
|         SmsUtil::requestLimit($key, BusinessCacheKey::RELEASE_SMS_CODE['ttl']);
 | ||
|         $code = SmsUtil::getSmsCode($key, BusinessCacheKey::RELEASE_SMS_CODE['ttl']);
 | ||
|         AliSms::sendSms(['phone_numbers' => $params['phone'], 'code' => $code]);
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * 获取绑定手机号
 | ||
|      * @param int $userId
 | ||
|      * @return array
 | ||
|      * @throws \think\db\exception\DataNotFoundException
 | ||
|      * @throws \think\db\exception\ModelNotFoundException
 | ||
|      */
 | ||
|     public static function getBindMobile(int $userId)
 | ||
|     {
 | ||
|         $user = User::getUserById($userId);
 | ||
|         return ['mobile' => $user->mobile];
 | ||
|     }
 | ||
| 
 | ||
|     /**
 | ||
|      * 判断是否能兑换
 | ||
|      * @param array $params
 | ||
|      * @return array
 | ||
|      * @throws \think\db\exception\DataNotFoundException
 | ||
|      * @throws \think\db\exception\ModelNotFoundException
 | ||
|      */
 | ||
|     public static function getExchangeStatus(array $params): array
 | ||
|     {
 | ||
|         $isCanAgree = self::checkCanAgree($params['user_id'], '');
 | ||
|         return ['is_exchange' => $isCanAgree['success']];
 | ||
|     }
 | ||
| } |