cmbYouku_Api/app/service/AgreementService.php

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