Files
oil/server-tp6/app/api/controller/OilCard.php
T
2026-09-10 20:56:46 +08:00

493 lines
22 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\model\qyt\Enterprise as EnterpriseModel;
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'] ?? ''));
// 企油通:司机身份证号。平安银行通道要求实名必传,支付宝企业码通道可不传
$idCard = strtoupper(trim((string)($data['idCard'] ?? '')));
// 请求参数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(司机姓名)');
}
// 解析本单使用的企油通企业:合作商绑定的企业 -> 默认企业
$enterprise = EnterpriseModel::resolve((int)$partners['qyt_enterprise_id']);
if(!$enterprise){
$this->saveLog('share 未匹配到可用的企油通企业,partners_id=' . $partners['partners_id']);
return $this->oilRenderError('99','9999', '', '系统未配置可用企业,请联系平台');
}
// 身份证号:传了就必须合法;该企业当前通道要求实名时不传直接拒单
if($idCard !== '' && !preg_match('/^\d{17}[\dX]$/', $idCard)){
return $this->oilRenderError('12','0013', '', 'idCard 身份证号格式不正确(18位)');
}
if($idCard === '' && $enterprise->requireIdCard()){
return $this->oilRenderError('12','0001', '', '缺少必填请求参数 idCard(司机身份证号,当前支付通道要求实名)');
}
// 金额:由下单平台直接传入(单位:元,最多两位小数)
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,
'id_card' => $idCard,
// 企业与通道随单固化:之后改绑定或切默认企业,在途单仍按下单时的企业发放
'qyt_enterprise_id' => $enterprise['enterprise_id'],
'passageway' => $enterprise['passageway'],
'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。
// creditcode 取本单固化的企业,把登录固定到该企业;企业须已通过企油通审核
$orderEnterprise = EnterpriseModel::detail((int)$order['qyt_enterprise_id']);
if(!$orderEnterprise){
return $this->oilRenderError('12','0013', '', '本单绑定的企业不存在,无法生成加油链接');
}
$h5Url = QytOil::forEnterprise($orderEnterprise)->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'] ?? '');
}
}
}