Files
oil/server-tp6/app/api/controller/OilCard.php
T

468 lines
20 KiB
PHP
Raw 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
// +----------------------------------------------------------------------
// | 萤火商城系统 [ 致力于通过产品和服务,帮助商家高效化开拓市场 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2017~2023 https://www.yiovo.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed 这不是一个自由软件,不允许对程序代码以任何形式任何目的的再发行
// +----------------------------------------------------------------------
// | Author: 萤火科技 <admin@yiovo.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\api\controller;
use app\api\controller\Controller;
use app\api\model\partners\Order as OrderModel;
use app\common\model\Partners as PartnersModel;
use app\common\crypto\RsaUtil;
use app\common\service\PartnersBalance;
use app\common\service\QytOil;
use app\store\model\order\QrCode as QrCodeModel;
use cores\exception\BaseException;
use think\facade\Request;
use think\response\Json;
use think\facade\Db;
use think\db\exception\PDOException;
use app\common\service\QueueService;
use think\facade\Validate;
/**
* 用户分油
* Class OilCard
* @package app\api\controller\v1_0_0\oil
*/
class OilCard extends Controller
{
/**
* 用户分油
* @return Json
* @throws BaseException
* @throws \think\db\exception\DbException
*/
public function share()
{
// 获取所有 HTTP 请求头
$headers = Request::header();
// 获取 JSON 格式的请求体
$jsonData = Request::instance()->getContent();
$data = json_decode($jsonData, true);
$appKey = $headers['appkey'] ?? '';
$requestId = $headers['requestid'] ?? '';
$method = $headers['method'] ?? '';
// 请求头METHOD参数不正确
if($method != 'syb.oil.oilCard.share'){
return $this->oilRenderError('12', '0005');
}
try {
Db::name('request_log')->insert([
'request_id' => $requestId,
'app_key' => $appKey,
'create_time' => time()
]);
} catch (\think\Exception $e) {
// 判断是否唯一索引冲突
if (strpos($e->getMessage(), 'Duplicate entry') !== false) {
return $this->oilRenderError('12', '0012'); // 请求流水号重复
}
// 其他数据库异常
return $this->oilRenderError('99', '9999'); // 未知错误
}
$partners = PartnersModel::detail(['appKey' => $appKey]);
if(!$partners){
return $this->oilRenderError('02','0004');
}
$crypto = new RsaUtil($partners);
$result = $crypto->processResponse(['header'=>$headers,'encryptData'=>$data]);
if ($result['code'] === '00') {
// 业务处理
$data = $result['data'];
$orgNo = $data['orgNo'] ?? '';
$amount = $data['amount'] ?? '';
$mobile = $data['mobile'] ?? '';
$saleOrderNo = $data['saleOrderNo'] ?? '';
$oilCardType = $data['oilCardType'] ?? '';
// 企油通:司机姓名(下单平台必传,用于运单油费下发)
$driverName = trim((string)($data['name'] ?? ''));
// 请求参数orgNo与请求头appKey不存在对应关系
if($partners['orgNo'] != $orgNo){
return $this->oilRenderError('12','0014');
}
if(!preg_match('/^1[3-9]\d{9}$/', $mobile)) {
return $this->oilRenderError('12','0013', '', '手机号格式不正确');
}
if($driverName === ''){
return $this->oilRenderError('12','0001', '', '缺少必填请求参数 name(司机姓名)');
}
// 金额:由下单平台直接传入(单位:元,最多两位小数)
if($amount === '' || !preg_match('/^\d{1,8}(\.\d{1,2})?$/', (string)$amount) || (float)$amount <= 0){
return $this->oilRenderError('12','0013', '', 'amount 金额格式不正确(单位:元,最多两位小数且大于0)');
}
$amount = round((float)$amount, 2);
// 销售平台订单号幂等:已存在则返回原订单结果(发放失败态除外)
$exist = OrderModel::detail(['partners_id' => $partners['partners_id'], 'partners_order_no' => $saleOrderNo]);
if($exist){
if((int)$exist['oil_status_zsy'] === 3){
return $this->oilRenderError('12','2023','','该销售订单号已提交且发放失败,请使用新订单号');
}
return $this->oilRenderSuccess([
'oilCardNo' => $exist['oilCardNo'],
'amount' => $exist['total_price'],
]);
}
// 额度管控:先原子扣减合作商余额(余额不足直接拒单,防并发超扣)
$balanceLogId = PartnersBalance::deduct((int)$partners['partners_id'], (float)$amount, $saleOrderNo, 0, (int)$this->storeId);
if ($balanceLogId === false) {
return $this->oilRenderError('01','2028', '', '合作商余额不足,请先充值');
}
$OrderModel = new OrderModel();
$oilCardNo = 'OIL' . self::createOrderNo();
try {
$created = $OrderModel->saveOrder([
'mobile' => $mobile,
'driver_name' => $driverName,
'oilCardType' => $oilCardType,
'oilCardNo' => $oilCardNo,
'partners_order_no' => $saleOrderNo,
'partners_id' => $partners['partners_id'],
'goods_price' => $amount,
'total_num' => 1,
'total_price' => $amount,
'total_pay_price' => $amount,
'store_id' => $this->storeId,
]);
} catch (\Throwable $e) {
// 建单失败 -> 回补刚扣减的余额
PartnersBalance::refund((int)$partners['partners_id'], (float)$amount, $saleOrderNo, 0, (int)$this->storeId);
// 并发下 (partners_id, partners_order_no) 唯一键冲突 -> 幂等返回已存在订单
if (strpos($e->getMessage(), 'Duplicate') !== false) {
$again = OrderModel::detail(['partners_id' => $partners['partners_id'], 'partners_order_no' => $saleOrderNo]);
if ($again) {
return $this->oilRenderSuccess(['oilCardNo' => $again['oilCardNo'], 'amount' => $again['total_price']]);
}
}
$this->saveLog('share saveOrder 异常:' . $e->getMessage());
return $this->oilRenderError('99', '9999');
}
if($created){
// 回填流水的订单ID(建单前尚不知 order_id)
PartnersBalance::bindOrder($balanceLogId, (int)$OrderModel->order_id);
// 存入队列处理
$queueService = new QueueService();
$queueService->push('partners_queue', json_encode(['order_id'=>$OrderModel->order_id],320));
return $this->oilRenderSuccess([
'oilCardNo' => $oilCardNo,
'amount' => $amount,
]);
}
// 未建单成功 -> 回补余额
PartnersBalance::refund((int)$partners['partners_id'], (float)$amount, $saleOrderNo, 0, (int)$this->storeId);
return $this->oilRenderError('99', '9999');
} else {
// 错误处理
return $this->oilRenderError($result['code'], $result['errCode'], '', $result['errMessage'] ?? '');
}
}
public static function createOrderNo(): string
{
// 获取当前时间戳到毫秒(13位)
$time = (int)(microtime(true) * 1000); // 返回形如 1716544103986
// 拼接一个 4 位随机数
$rand = mt_rand(1000, 9999);
return (string)($time . $rand); // 拼接成纯数字字符串
}
/**
* 付款二维码申请
* @return Json
* @throws BaseException
* @throws \think\db\exception\DbException
*/
public function getPayQrCode()
{
// 获取所有 HTTP 请求头
$headers = Request::header();
// 获取 JSON 格式的请求体
$jsonData = Request::instance()->getContent();
$data = json_decode($jsonData, true);
$appKey = $headers['appkey'] ?? '';
$requestId = $headers['requestid'] ?? '';
$method = $headers['method'] ?? '';
// 请求头METHOD参数不正确
if($method != 'syb.pay.qrCode.request'){
return $this->oilRenderError('12', '0005');
}
try {
Db::name('request_log')->insert([
'request_id' => $requestId,
'app_key' => $appKey,
'create_time' => time()
]);
} catch (\think\Exception $e) {
// 判断是否唯一索引冲突
if (strpos($e->getMessage(), 'Duplicate entry') !== false) {
return $this->oilRenderError('12', '0012'); // 请求流水号重复
}
// 其他数据库异常
return $this->oilRenderError('99', '9999'); // 未知错误
}
$partners = PartnersModel::detail(['appKey' => $appKey]);
if(!$partners){
return $this->oilRenderError('02','0004');
}
$crypto = new RsaUtil($partners);
$result = $crypto->processResponse(['header'=>$headers,'encryptData'=>$data]);
if ($result['code'] === '00') {
// 业务处理
$data = $result['data'];
$orgNo = $data['orgNo'] ?? '';
$oilCardNo = $data['oilCardNo'] ?? '';
$oilCardType = $data['oilCardType'] ?? '';
$mobile = $data['mobile'] ?? '';
// 请求参数orgNo与请求头appKey不存在对应关系
if($partners['orgNo'] != $orgNo){
return $this->oilRenderError('12','0014');
}
if(!preg_match('/^1[3-9]\d{9}$/', $mobile)) {
return $this->oilRenderError('12','0013', '', '手机号格式不正确');
}
$order = OrderModel::detail(['oilCardNo' => $oilCardNo]);
if(!$order) {
return $this->oilRenderError('12','0013', '', '未找到对应订单');
}
// 企油通:生成司机免登加油 H5 地址(7.15)。出参 qrCode 结构不变,值由二维码链接改为 H5 URL。
$h5Url = (new QytOil())->h5Url($order['mobile']);
if(!$h5Url){
return $this->oilRenderError('12','0013', '', '生成加油链接失败');
}
// 插入申请记录(留痕)
$QrCodeModel = new QrCodeModel();
$QrCodeModel->add([
'oilCardNo'=>$order['oilCardNo'],
'qrCode'=>$h5Url,
'amount'=>$order['goods_price'],
'order_id'=>$order['order_id'],
'user_id'=>0,
'store_id'=>$order['store_id'],
]);
return $this->oilRenderSuccess(['qrCode'=>$h5Url]);
} else {
// 错误处理
return $this->oilRenderError($result['code'], $result['errCode'], '', $result['errMessage'] ?? '');
}
}
/**
* 用户分油记录查询
* @return Json
* @throws BaseException
* @throws \think\db\exception\DbException
*/
public function shareList()
{
// 获取所有 HTTP 请求头
$headers = Request::header();
// 获取 JSON 格式的请求体
$jsonData = Request::instance()->getContent();
$data = json_decode($jsonData, true);
$appKey = $headers['appkey'] ?? '';
$requestId = $headers['requestid'] ?? '';
$method = $headers['method'] ?? '';
// 请求头METHOD参数不正确
if($method != 'syb.oil.oilCard.shareList'){
return $this->oilRenderError('12', '0005');
}
try {
Db::name('request_log')->insert([
'request_id' => $requestId,
'app_key' => $appKey,
'create_time' => time()
]);
} catch (\think\Exception $e) {
// 判断是否唯一索引冲突
if (strpos($e->getMessage(), 'Duplicate entry') !== false) {
return $this->oilRenderError('12', '0012'); // 请求流水号重复
}
// 其他数据库异常
return $this->oilRenderError('99', '9999'); // 未知错误
}
$partners = PartnersModel::detail(['appKey' => $appKey]);
if(!$partners){
return $this->oilRenderError('02','0004');
}
$crypto = new RsaUtil($partners);
$result = $crypto->processResponse(['header'=>$headers,'encryptData'=>$data]);
if ($result['code'] === '00') {
// 业务处理
$data = $result['data'];
$data['orgNo'] = $data['orgNo'] ?? '';
$data['mobile'] = $data['mobile'] ?? '';
$data['shareNo'] = $data['shareNo'] ?? '';
$data['saleOrderNo'] = $data['saleOrderNo'] ?? '';
$data['beginDate'] = $data['beginDate'] ?? '';
$data['endDate'] = $data['endDate'] ?? '';
$data['pageLimit'] = $data['pageLimit'] ?? 10;
$data['pageNum'] = $data['pageNum'] ?? 1;
$data['partners_id'] = $partners['partners_id'];
// 请求参数orgNo与请求头appKey不存在对应关系
if($partners['orgNo'] != $data['orgNo']){
return $this->oilRenderError('12','0014');
}
if(!preg_match('/^1[3-9]\d{9}$/', $data['mobile'])) {
return $this->oilRenderError('12','0013', '', '手机号格式不正确');
}
$rule = [
'beginDate' => 'require|dateFormat:Ymd',
'endDate' => 'require|dateFormat:Ymd',
'pageLimit' => 'number|between:1,30',
'pageNum' => 'number|min:1'
];
$message = [
'beginDate.require' => 'beginDate 不能为空',
'beginDate.dateFormat' => 'beginDate 格式错误,正确格式为 yyyyMMdd',
'endDate.require' => 'endDate 不能为空',
'endDate.dateFormat' => 'endDate 格式错误,正确格式为 yyyyMMdd',
'pageLimit.number' => 'pageLimit 必须是数字',
'pageLimit.between' => 'pageLimit 必须在 1-30 之间',
'pageNum.number' => 'pageNum 必须是数字',
'pageNum.min' => 'pageNum 必须大于等于 1',
];
$validate = Validate::rule($rule)->message($message);
if (!$validate->check($data)) {
return $this->oilRenderError('12','0013', '', $validate->getError());
}
// 检查日期间隔
$begin = date_create_from_format('Ymd', $data['beginDate']);
$end = date_create_from_format('Ymd', $data['endDate']);
if ($begin > $end) {
return $this->oilRenderError('12','0013', '', 'endDate 必须大于或等于 beginDate');
}
if ($begin->diff($end)->days > 365) {
return $this->oilRenderError('12','0013', '', '日期间隔不能超过 365 天');
}
$model = new OrderModel;
$list = $model->getList($data);
// 组装返回格式
$result = [
'totalNum' => $list->total(),
'totalPage' => $list->lastPage(),
'pageNum' => $list->currentPage(),
'pageLimit' => $list->listRows(),
'orderList' => array_map(function ($item) {
return [
'shareNo' => $item['shareNo'],
'mobile' => $item['mobile'],
'oilCardNo' => $item['oilCardNo'],
'num' => $item['total_num'],
'amount' => $item['total_price'],
'createTime' => $item['create_time'],
];
}, $list->items())
];
return $this->oilRenderSuccess($result);
} else {
// 错误处理
return $this->oilRenderError($result['code'], $result['errCode'], '', $result['errMessage'] ?? '');
}
}
/**
* 订单状态查询
* @return Json
* @throws BaseException
* @throws \think\db\exception\DbException
*/
public function orderStatus()
{
// 获取所有 HTTP 请求头
$headers = Request::header();
// 获取 JSON 格式的请求体
$jsonData = Request::instance()->getContent();
$data = json_decode($jsonData, true);
$appKey = $headers['appkey'] ?? '';
$requestId = $headers['requestid'] ?? '';
$method = $headers['method'] ?? '';
// 请求头METHOD参数不正确
if($method != 'syb.oil.oilCard.orderStatus'){
return $this->oilRenderError('12', '0005');
}
try {
Db::name('request_log')->insert([
'request_id' => $requestId,
'app_key' => $appKey,
'create_time' => time()
]);
} catch (\think\Exception $e) {
// 判断是否唯一索引冲突
if (strpos($e->getMessage(), 'Duplicate entry') !== false) {
return $this->oilRenderError('12', '0012'); // 请求流水号重复
}
// 其他数据库异常
return $this->oilRenderError('99', '9999'); // 未知错误
}
$partners = PartnersModel::detail(['appKey' => $appKey]);
if(!$partners){
return $this->oilRenderError('02','0004');
}
$crypto = new RsaUtil($partners);
$result = $crypto->processResponse(['header'=>$headers,'encryptData'=>$data]);
if ($result['code'] === '00') {
// 业务处理
$data = $result['data'];
$data['orgNo'] = $data['orgNo'] ?? '';
$data['oilCardNo'] = $data['oilCardNo'] ?? '';
// $data['saleOrderNo'] = $data['saleOrderNo'] ?? '';
// 请求参数orgNo与请求头appKey不存在对应关系
if($partners['orgNo'] != $data['orgNo']){
return $this->oilRenderError('12','0014');
}
$rule = [
'oilCardNo' => 'require',
];
$message = [
'oilCardNo.require' => 'oilCardNo 不能为空',
];
$validate = Validate::rule($rule)->message($message);
if (!$validate->check($data)) {
return $this->oilRenderError('12','0013', '', $validate->getError());
}
$detail = OrderModel::detail(['oilCardNo' => $data['oilCardNo']]);
// 组装返回格式
$result = [
'mobile' => $detail['mobile'],
'oilCardNo' => $detail['oilCardNo'],
'shareNo' => $detail['shareNo'],
'saleOrderNo' => $detail['partners_order_no'],
'total_price' => $detail['total_price'],
'oil_status_zsy' => $detail['oil_status_zsy'],
'oil_status' => $detail['oil_status'],
'oil_time' => $detail['oil_time'],
'use_status' => $detail['use_status'],
'use_time' => $detail['use_time'],
];
return $this->oilRenderSuccess($result);
} else {
// 错误处理
return $this->oilRenderError($result['code'], $result['errCode'], '', $result['errMessage'] ?? '');
}
}
}