cmbYouku_Api/app/service/AgreementService.php

453 lines
16 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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("JobPayOrderRetry错误信息" . $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']];
}
}