385 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			PHP
		
	
	
	
			
		
		
	
	
			385 lines
		
	
	
		
			14 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 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 ($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']];
 | |
|     }
 | |
| } |