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']]; } }