第一次更新,完成了初版调整

This commit is contained in:
2026-09-07 22:05:51 +08:00
parent b5a26a8848
commit 4d6a7e7aac
198 changed files with 6142 additions and 3856 deletions
@@ -32,9 +32,6 @@ class Controller extends BaseController
{
// 当前商城ID
protected $storeId;
protected $latitude;
protected $longitude;
protected $province;
/**
* API基类初始化
@@ -44,12 +41,6 @@ class Controller extends BaseController
{
// 当前商城id
$this->storeId = $this->getStoreId();
$this->latitude = input('get.latitude');
$this->longitude = input('get.longitude');
$res = $this->httpCurl('https://apis.map.qq.com/ws/geocoder/v1/',['location'=>$this->latitude.','.$this->longitude,'key'=>'ETFBZ-F4XCW-YOQRI-RWBQM-BAZIQ-2VFS5']);
if(isset($res['status']) && $res['status'] == 0){
$this->province = $res['result']['address_component']['province'];
}
// 验证当前商城状态
$this->checkStore();
// 验证当前客户端状态
+72 -189
View File
@@ -15,13 +15,9 @@ 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\model\PartnersGoods as PartnersGoodsModel;
use app\common\model\Goods as GoodsModel;
use app\api\model\GoodsSku as GoodsSkuModel;
use app\common\crypto\RsaUtil;
use app\common\service\goods\source\Factory as StockFactory;
use app\common\service\IdingjuOil;
use app\store\model\Company as CompanyModel;
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;
@@ -83,10 +79,12 @@ class OilCard extends Controller
// 业务处理
$data = $result['data'];
$orgNo = $data['orgNo'] ?? '';
$goodsNo = $data['goodsNo'] ?? '';
$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');
@@ -94,78 +92,80 @@ class OilCard extends Controller
if(!preg_match('/^1[3-9]\d{9}$/', $mobile)) {
return $this->oilRenderError('12','0013', '', '手机号格式不正确');
}
// 销售平台订单号重复
$order = OrderModel::detail(['partners_id' => $partners['partners_id'], 'partners_order_no' => $saleOrderNo]);
if($order){
return $this->oilRenderError('12','2023');
if($driverName === ''){
return $this->oilRenderError('12','0001', '', '缺少必填请求参数 name(司机姓名)');
}
// 未找到卡券对应的信息
$goods = GoodsModel::detail(['goods_no' => $goodsNo, 'oilCardType' => $oilCardType]);
if(!$goods){
return $this->oilRenderError('12','2047');
// 金额:由下单平台直接传入(单位:元,最多两位小数)
if($amount === '' || !preg_match('/^\d{1,8}(\.\d{1,2})?$/', (string)$amount) || (float)$amount <= 0){
return $this->oilRenderError('12','0013', '', 'amount 金额格式不正确(单位:元,最多两位小数且大于0)');
}
// 判断商品库存
$detail = GoodsSkuModel::detail($goods['goods_id'], '0');
if (1 > $detail['stock_num']) {
return $this->oilRenderError('12','0013', '', "很抱歉,商品 [{$goods['goods_name']}] 平台库存不足");
$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'],
]);
}
$partnersGoods = PartnersGoodsModel::detail($partners['partners_id'], $goods['goods_id']);
if(!$partnersGoods || $partnersGoods['stock_num'] <= 0){
return $this->oilRenderError('01','2010');
// 额度管控:先原子扣减合作商余额(余额不足直接拒单,防并发超扣)
$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();
if($OrderModel->saveOrder([
'province' => $goods['province'],
'mobile' => $mobile,
'oilCardType' => $oilCardType,
'oilCardNo' => $oilCardNo,
'partners_order_no' => $saleOrderNo,
'partners_id' => $partners['partners_id'],
'goods_id' => $goods['goods_id'],
'goods_name' => $goods['goods_name'],
'goods_no' => $goods['goods_no'],
'goods_price' => $goods['goods_price_min'],
'total_num' => 1,
'total_price' => $goods['goods_price_min'],
'total_pay_price' => $goods['goods_price_min'],
'store_id' => $this->storeId,
])){
// 更新商品库存 (针对下单减库存的商品)
$this->updateGoodsStockNum([
[
'deduct_stock_type' => 10,
'goods_id' => $goods['goods_id'],
'goods_sku_id' => $detail['id'],
'total_num' => 1,
]
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' => $goods['goods_price_min'],
'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'] ?? '');
}
}
/**
* 更新商品库存 (针对下单减库存的商品)
* @param $order
* @return void
*/
private function updateGoodsStockNum($goodsList): void
{
StockFactory::getFactory(10)->updateGoodsStock($goodsList);
}
public static function createOrderNo(): string
{
// 获取当前时间戳到毫秒(13位)
@@ -177,36 +177,6 @@ class OilCard extends Controller
return (string)($time . $rand); // 拼接成纯数字字符串
}
/**
* @param $goodsNo
* @return string
*/
public function getCompany($goodsNo): string
{
switch ($goodsNo) {
case 'djgdface100':
case 'djgdface200':
case 'djgdface300':
return 'umQZhIHKofyA';
case 'jmgxyyface100':
case 'jmgxyyface200':
case 'jmgxyyface300':
case 'jmgzface100':
case 'jmgzface200':
case 'jmgzface300':
case 'jmfjface100':
case 'jmfjface200':
case 'jmfjface300':
return 'HpWqXIgYvTBg';
case 'face003':
return 'NKJBgaaRNdiL'; // 测试参数
default:
return '';
}
}
/**
* 付款二维码申请
* @return Json
@@ -266,105 +236,22 @@ class OilCard extends Controller
if(!$order) {
return $this->oilRenderError('12','0013', '', '未找到对应订单');
}
$company = CompanyModel::detail(['orgNo' => $this->getCompany($order['goods_no'])]);
if (!$company) {
return $this->oilRenderError('12','0013', '', '没有可用参数:【' . $order['goods_no'] . '】');
// 企油通:生成司机免登加油 H5 地址(7.15)。出参 qrCode 结构不变,值由二维码链接改为 H5 URL。
$h5Url = (new QytOil())->h5Url($order['mobile']);
if(!$h5Url){
return $this->oilRenderError('12','0013', '', '生成加油链接失败');
}
$IdingjuOil = new IdingjuOil();
// 对接平台接口
$res = $IdingjuOil->payQrCodeRequest(['order_no'=>$oilCardNo], $company);
if(isset($res['code']) && $res['code'] == '1' && isset($res['data']['useQrCodeUrl'])){
// 插入支付码记录
$QrCodeModel = new QrCodeModel();
$QrCodeModel->add([
'oilCardNo'=>$order['oilCardNo'],
'qrCode'=>$res['data']['useQrCodeUrl'],
'amount'=>$order['goods_price'],
'order_id'=>$order['order_id'],
'user_id'=>0,
'store_id'=>$order['store_id'],
]);
return $this->oilRenderSuccess([
'qrCode'=>$res['data']['useQrCodeUrl']
]);
}else{
return $this->oilRenderError('12','0013', '', $res['message'] ?? '');
}
} else {
// 错误处理
return $this->oilRenderError($result['code'], $result['errCode'], '', $result['errMessage'] ?? '');
}
}
/**
* 油券库存查询
* @return Json
* @throws BaseException
* @throws \think\db\exception\DbException
*/
public function stock()
{
// 获取所有 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.stockList'){
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'] ?? '';
$goodsNo = $data['goodsNo'] ?? '';
// 请求参数orgNo与请求头appKey不存在对应关系
if($partners['orgNo'] != $orgNo){
return $this->oilRenderError('12','0014');
}
// 未找到卡券对应的信息
$goods = GoodsModel::detail(['goods_no' => $goodsNo]);
if(!$goods){
return $this->oilRenderError('12','2047');
}
$partnersGoods = PartnersGoodsModel::detail($partners['partners_id'], $goods['goods_id']);
if(!$partnersGoods){
return $this->oilRenderError('01','2010');
}
$orderModel = new OrderModel();
$sucNum = $orderModel->getCount(['partners_id' => $partners['partners_id'], 'goods_id' => $goods['goods_id']], 'all');
$useNum = $orderModel->getCount(['partners_id' => $partners['partners_id'], 'goods_id' => $goods['goods_id']], 'used');
return $this->oilRenderSuccess([
'goodsNo'=>$goods['goods_no'], // 商品编号
'canOrderNum'=>$partnersGoods['stock_num'], // 剩余可下单数量
'unUseNum'=>floatval($sucNum) - floatval($useNum), // 未使用商品数量
'useNum'=>$useNum, // 已使用商品数量
// 插入申请记录(留痕)
$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'] ?? '');
@@ -478,7 +365,6 @@ class OilCard extends Controller
'shareNo' => $item['shareNo'],
'mobile' => $item['mobile'],
'oilCardNo' => $item['oilCardNo'],
'productName' => $item['goods_name'],
'num' => $item['total_num'],
'amount' => $item['total_price'],
'createTime' => $item['create_time'],
@@ -561,13 +447,10 @@ class OilCard extends Controller
$detail = OrderModel::detail(['oilCardNo' => $data['oilCardNo']]);
// 组装返回格式
$result = [
'province' => $detail['province'],
'mobile' => $detail['mobile'],
'oilCardNo' => $detail['oilCardNo'],
'shareNo' => $detail['shareNo'],
'saleOrderNo' => $detail['partners_order_no'],
'goods_name' => $detail['goods_name'],
'goods_no' => $detail['goods_no'],
'total_price' => $detail['total_price'],
'oil_status_zsy' => $detail['oil_status_zsy'],
'oil_status' => $detail['oil_status'],
+6 -71
View File
@@ -309,36 +309,9 @@ class Order extends Controller
*/
public function qrCode(): Json
{
$IdingjuOil = new IdingjuOil();
$postData = $this->request->param();
$postData['order_goods_id'] = $this->request->param('orderGoodsId');
$orderGoods = OrderGoodsModel::detail($postData['order_goods_id']);
if($orderGoods['use_status'] != 0){
return $this->renderError('油券已核销');
}else {
$company = CompanyModel::detail($orderGoods['company_id']);
// 对接平台接口
$res = $IdingjuOil->payQrCodeRequest(['order_no'=>$orderGoods['oilCardNo']], $company);
if($res['code'] == '1' && isset($res['data']['useQrCodeUrl'])){
// 插入支付码记录
$QrCodeModel = new QrCodeModel();
$QrCodeModel->add([
'oilCardNo'=>$orderGoods['oilCardNo'],
'qrCode'=>$res['data']['useQrCodeUrl'],
'amount'=>$orderGoods['goods_price'],
'order_id'=>$orderGoods['order_id'],
'user_id'=>$orderGoods['user_id'],
'store_id'=>$orderGoods['store_id'],
]);
$res = [
'qrCode'=>$res['data']['useQrCodeUrl']
];
return $this->renderSuccess($res, '提交成功');
}else{
return $this->renderError('平台创建用户失败【'.($res['errMessage'] ?? $res['message']).'】');
}
}
// 企油通改造:小程序油品发放/核销链路已停用(上游定聚已下线)。
// 保留接口避免小程序端报错,统一返回提示;如需恢复请改走 QytOil。
return $this->renderError('该功能已停用');
}
/**
@@ -348,47 +321,9 @@ class Order extends Controller
*/
public function qrCodeImg(): Json
{
// $res = [
// 'base64'=> 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAIAAAAP3aGbAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAGwElEQVR4nO3cwY4bRxAFQdPY//9l+roQ4IULVoGVmoizwO0ZUok+vdf7/f4LoODvTx8A4L8SLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CAjK/tP/B6vbb/xKO83+/Rv99+/9PzTE3Pf+39PM3278ENC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsICM9T2sqe09nWuu7VVNz3Pt/NuunWfbtb0wNywgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMs7tYU1d2+up7yVdO//0+62ff9u19zPlhgVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhARn4Pi5/V96SunYfPcsMCMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIMMe1h9uuid1bT/r2nn4LDcsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjLye1j2j36va/tWdX6fv5cbFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZ5/awnraXtG36Pqf7Tdc+f9u18zyNGxaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGa/pPhF8t72HBd+5YQEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQ8fXpA/xf23tM08/fdu382+e59vnbrp3/2nncsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIWN/DuranM3Vtj+navtX25197n9d+n0/jhgVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAxuvavs+1faLt/ampa9/X1LX3OWVv67PcsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIWN/Detoe0PbzPu19TtXfz/ZeWP335oYFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQMbXpw/wq2t7QNuu7Q1t87w/u7ZPd40bFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZ63tY9T2jqWvPe21f7Nr7mdret7q2n3Xt+3LDAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCDjtb13s73H9DTX9ommru1zXTvP1LX/X9vP64YFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQMbXpw/wq/re09T2nlF972nq2j7U097/NjcsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjLO7WFNXds/eto+0dT0/Vzbk5qeZ/v3sH2ea8/rhgVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhARn4Pi5/V97nqe1LX1J/XDQvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADHtYf7hr+0fT80xNz3/teZ92/ik3LCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsIAMwQIy8ntY2/s7ddP3c23/aOra+et7Vde+XzcsIEOwgAzBAjIEC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjLO7WFN933gu2v7Tdf2uaaund8NC8gQLCBDsIAMwQIyBAvIECwgQ7CADMECMgQLyBAsIEOwgAzBAjIEC8gQLCBDsICM17X9HYB/44YFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWECGYAEZggVkCBaQIVhAhmABGYIFZAgWkCFYQIZgARmCBWQIFpAhWEDGP+OBJg1/1F6rAAAAAElFTkSuQmCC'
//// 'base64'=> ''
// ];
// return $this->renderSuccess($res, '提交成功');
$IdingjuOil = new IdingjuOil();
$postData = $this->request->param();
$postData['order_goods_id'] = $this->request->param('orderGoodsId');
$orderGoods = OrderGoodsModel::detail($postData['order_goods_id']);
if($orderGoods['use_status'] != 0){
return $this->renderError('油券已核销');
}else {
/*$res = [
'base64'=> $this->getBase64($orderGoods['qrCode'])
];
return $this->renderSuccess($res, '提交成功');*/
$company = CompanyModel::detail($orderGoods['company_id']);
// 对接平台接口
$res = $IdingjuOil->payQrCodeRequest(['order_no'=>$orderGoods['oilCardNo']], $company);
//$res = $IdingjuOil->payQrCodeRequest(['oilCardNo'=>$orderGoods['oilCardNo'],'shareNo'=>$orderGoods['shareNo'],'mobile'=>$orderGoods['mobile'],'oilCardType'=>$orderGoods['oilCardType']], $company);
//if($res['code'] == '00' && isset($res['data']['qrCode'])){
if($res['code'] == '1' && isset($res['data']['useQrCodeUrl'])){
// 插入支付码记录
$QrCodeModel = new QrCodeModel();
$QrCodeModel->add([
'oilCardNo'=>$orderGoods['oilCardNo'],
'qrCode'=>$res['data']['useQrCodeUrl'],
'amount'=>$orderGoods['goods_price'],
'order_id'=>$orderGoods['order_id'],
'user_id'=>$orderGoods['user_id'],
'store_id'=>$orderGoods['store_id'],
]);
$res = [
'base64'=> $this->getBase64($res['data']['useQrCodeUrl'])
];
return $this->renderSuccess($res, '提交成功');
}else{
return $this->renderError('平台创建用户失败【'.($res['errMessage'] ?? $res['message']).'】');
}
}
// 企油通改造:小程序油品发放/核销链路已停用(上游定聚已下线)。
// 保留接口避免小程序端报错,统一返回提示;如需恢复请改走 QytOil。
return $this->renderError('该功能已停用');
}
public function getBase64($url){
@@ -0,0 +1,56 @@
<?php
// +----------------------------------------------------------------------
// | 企油通 · 回调接收
// +----------------------------------------------------------------------
// | 入口:public/qytconsume.php -> /qytNotify/consume
// | 对应企油通 7.10「运单加油明细推送(异步)」,我方须返回 {"code":"100","msg":"Success"}
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\api\controller;
use app\common\crypto\Qyt as QytCrypto;
use app\common\service\QueueService;
use app\common\service\QytOil;
use think\facade\Request;
class QytNotify extends Controller
{
/**
* 7.10 运单加油明细推送
* @return \think\response\Json
*/
public function consume()
{
$raw = Request::instance()->getContent();
(new QytOil())->saveLog('7.10 消费回调原文:' . $raw);
$body = json_decode($raw, true);
// 兼容:外层信封 {appId,timestamp,sign,params(JSON字符串)} 或 直接推 body
$params = $body;
if (is_array($body) && isset($body['params'])) {
$params = is_string($body['params']) ? json_decode($body['params'], true) : $body['params'];
}
// TODO 验签(规则待对接人确认)
// if (is_array($body) && !empty($body['sign'])) {
// $crypto = new QytCrypto();
// if (!$crypto->verify($params, $body['sign'])) {
// return json(['code' => '99', 'msg' => '验签失败']);
// }
// }
// 无明细也回 100,避免企油通重复推送
if (is_array($params) && !empty($params['order_list'])) {
(new QueueService())->push('callback_queue', json_encode([
'header' => ['method' => 'qyt_consume'],
'res' => $params,
], 320));
} else {
(new QytOil())->saveLog('7.10 消费回调无 order_list,已忽略');
}
return json(['code' => '100', 'msg' => 'Success']);
}
}
+2 -40
View File
@@ -32,47 +32,9 @@ class Station extends Controller
*/
public function list(): Json
{
$SybOil = new SybOil();
$company = CompanyModel::detail('2');
$param = $this->request->param();
$province = Db::name('area')->where(array(
['name', '=', $param['province']],
['level', '=', 1],
))->find();
$city = Db::name('area')->where(array(
['name', '=', $param['city']],
['pid', '=', $province['code']],
['level', '=', 2],
))->find();
$goods = Db::name('goods')->where(array(
['oilCardType', '=', 'zsy_ticket'],
['province', '=', $param['province']],
['status', '=', 10],
))->order('stock_total', 'desc')
->find();
// 企油通改造:本功能依赖已下线的上游(智慧油客/定聚),现已停用。
// 企油通无油站查询能力,返回空列表,避免小程序端报错。
$list = [];
if($goods) {
// 对接平台接口
$res = $SybOil->baseGasStationList(['goodsNo' => $goods['goods_no'], 'longitude' => $param['longitude'], 'latitude' => $param['latitude'], 'province' => $province['code'], 'city' => $city['code']], $company);
if (isset($res['data']['zsyResp']['count']) && $res['data']['zsyResp']['count'] > 0) {
$lists = $res['data']['zsyResp']['lists'];
foreach ($lists as $k => $array) {
$list[] = [
'id' => $k + 1,
'name' => $array['name'],
'brand' => $array['brand'] == 1 ? '中国石油' : '中国BP',
'latitude' => $array['lat'],
'longitude' => $array['lng'],
'address' => '',
'distance' => $array['nearBy'],
'width' => 30,
'height' => 30,
'iconPath' => '/static/marker.png',
'stationCode' => $array['stationCode'],
];
}
}
}
return $this->renderSuccess(compact('list'));
}
}
@@ -1,27 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | 萤火商城系统 [ 致力于通过产品和服务,帮助商家高效化开拓市场 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2017~2023 https://www.yiovo.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed 这不是一个自由软件,不允许对程序代码以任何形式任何目的的再发行
// +----------------------------------------------------------------------
// | Author: 萤火科技 <admin@yiovo.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\api\model;
use app\common\model\PartnersGoods as PartnersGoodsModel;
use app\common\library\helper;
use cores\exception\BaseException;
/**
* 用户优惠券模型
* Class PartnersGoods
* @package app\api\model
*/
class PartnersGoods extends PartnersGoodsModel
{
}
+2 -9
View File
@@ -13,7 +13,6 @@ declare (strict_types=1);
namespace app\api\model\partners;
use app\common\model\partners\Order as OrderModel;
use app\api\model\PartnersGoods as PartnersGoodsModel;
use app\common\library\helper;
use cores\exception\BaseException;
@@ -32,14 +31,8 @@ class Order extends OrderModel
*/
public function saveOrder(array $data)
{
// 事务处理
$this->transaction(function () use ($data) {
// 写入订单记录
$this->save($data);
// 修改合作商商品库存
PartnersGoodsModel::setIncUseNum(['partners_id' => $data['partners_id'], 'goods_id' => $data['goods_id']], 1);
PartnersGoodsModel::setDecStockNum(['partners_id' => $data['partners_id'], 'goods_id' => $data['goods_id']], 1);
});
// 企油通改造:下单改为按金额(amount)直传,不再挂商品与合作商商品库存
$this->save($data);
return true;
}
@@ -255,6 +255,11 @@ class PaySuccess extends BaseService
$IdingjuOil = new IdingjuOil();
foreach ($order->goods as $goods) {
/* @var GoodsModel $goods */
// 企油通改造:小程序油品发放链路已停用,支付成功后不再触发上游分油(保留优惠券发放)。
// 如需恢复小程序侧发放,改走 QytOil 并补齐司机三要素来源。
if ($goods['oilCardType'] == 'zsy_ticket') {
continue;
}
if($goods['oilCardType'] == 'zsy_ticket') {
$data['amount'] = $goods['total_price']; // 金额
$company = CompanyModel::detail(['orgNo' => $this->getCompany($goods['goods_no'])]);
+182
View File
@@ -0,0 +1,182 @@
<?php
// +----------------------------------------------------------------------
// | 企油通 · 签名 / 验签 / 请求信封
// +----------------------------------------------------------------------
// | 第二阶段 A-1 骨架。标 【TODO 待对接人确认】 处需按企油通实际规则微调。
// |
// | 三种签名场景(见《企油通接口对接文档.md》§2.1 / §2.4):
// | 1. 业务信封:外层 { appId, timestamp, sign, params(JSON字符串) }
// | 2. 7.15 H5 免登地址:ksort → http_build_query → urldecode → 大写MD5 → SHA1withRSA → base64
// | 3. 7.16 / 7.17:必须真实 RSA 验签调用(本质同 1,但企油通侧强校验)
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\common\crypto;
class Qyt
{
/** @var string 我方私钥资源 */
protected $privateKey;
/** @var string|false 企油通平台公钥资源 */
protected $platformPublicKey;
/** @var array config('qyt') */
protected $conf;
public function __construct(array $conf = null)
{
$this->conf = $conf ?: (array) config('qyt');
$this->privateKey = $this->loadPrivateKey($this->conf['private_key'] ?? '');
$this->platformPublicKey = $this->loadPublicKey($this->conf['platform_public_key'] ?? '');
}
// ================================================================
// 1. 业务信封
// ================================================================
/**
* 构建业务请求信封
* @param array $bizParams 业务参数(会被 json_encode 成字符串放进 params)
* @return array{appId:string,timestamp:int,sign:string,params:string}
*/
public function envelope(array $bizParams): array
{
$appId = (string) ($this->conf['app_id'] ?? '');
$timestamp = time();
// ⚠️ params 必须是 JSON 字符串(转义),不是对象
$paramsJson = json_encode($bizParams, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$sign = $this->bizSign([
'appId' => $appId,
'timestamp' => $timestamp,
'params' => $paramsJson,
]);
return [
'appId' => $appId,
'timestamp' => $timestamp,
'sign' => $sign,
'params' => $paramsJson,
];
}
/**
* 业务信封签名
* 【TODO 待对接人确认】业务接口的具体拼签字段与顺序,文档 §2.4 只给了 7.15 的算法,
* 业务接口写的是「密钥、公私钥与具体拼签字段请向对接人员索取」。
* 当前默认实现:对参与字段 ksort 后 http_build_query → urldecode → 大写MD5 → SHA1withRSA → base64
* (与 7.15 同族算法,落地时按实际规则替换本方法即可,调用方无需改动)
*
* @param array $fields 参与签名的字段(appId / timestamp / params)
*/
public function bizSign(array $fields): string
{
$mode = $this->conf['sign_mode'] ?? 'ksort_md5_sha1rsa';
switch ($mode) {
// 预留:'raw_concat_sha256rsa' 等其它模式
case 'ksort_md5_sha1rsa':
default:
return $this->md5Sha1RsaSign($fields);
}
}
// ================================================================
// 2. 7.15 H5 免登地址
// ================================================================
/**
* 生成司机免登加油 H5 跳转地址(7.15)
* @param string $mobile 司机手机号
* @param string $creditcode 企业营业执照号;传空则不带(不参与签名)
* @return string 完整可跳转 URL
*/
public function h5Url(string $mobile, string $creditcode = ''): string
{
$base = rtrim((string) ($this->conf['h5_base_url'] ?? ''), '/');
$appId = (string) ($this->conf['h5_app_id'] ?: $this->conf['app_id']);
$timestamp = date('YmdHis');
$params = [
'appId' => $appId,
'mobile' => $mobile,
'timestamp' => $timestamp,
];
if ($creditcode !== '') {
$params['creditcode'] = $creditcode; // 传了必须一并入签
}
$params['sign'] = $this->md5Sha1RsaSign($params);
// 文档示例形如 https://paytaskpage.worknoya.vip/#/?appId=..&mobile=..&timestamp=..&sign=..#/
return $base . '/?' . http_build_query($params) . '#/';
}
// ================================================================
// 3. 验签(企油通响应 / 回调)
// ================================================================
/**
* 验签企油通返回 / 回调报文
* @param array $data 业务数据(通用响应里的 data,或回调 params)
* @param string $sign 报文里的 sign
* 【TODO 待对接人确认】验签串的构造(对 data 的 json 形态 / 是否含外层字段)
*/
public function verify($data, string $sign): bool
{
if ($sign === '' || $this->platformPublicKey === false) {
return false;
}
$payload = is_string($data) ? $data : json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$ok = openssl_verify($payload, base64_decode($sign), $this->platformPublicKey, OPENSSL_ALGO_SHA256);
return $ok === 1;
}
// ================================================================
// 内部:算法与密钥
// ================================================================
/**
* 同族签名算法:ksort → http_build_query → urldecode → 大写MD5 → SHA1withRSA → base64
*/
protected function md5Sha1RsaSign(array $params): string
{
ksort($params);
$raw = urldecode(http_build_query($params));
$md5Upper = strtoupper(md5($raw));
$signature = '';
openssl_sign($md5Upper, $signature, $this->privateKey, OPENSSL_ALGO_SHA1);
return base64_encode($signature);
}
/**
* 载入私钥:支持 PEM 全文 / 去头尾的单行 base64
* @return \OpenSSLAsymmetricKey|resource|string
*/
protected function loadPrivateKey(string $key)
{
if ($key === '') {
return '';
}
if (strpos($key, '-----BEGIN') === false) {
$key = "-----BEGIN PRIVATE KEY-----\n" . chunk_split(trim($key), 64, "\n") . "-----END PRIVATE KEY-----\n";
}
$res = openssl_pkey_get_private($key);
return $res === false ? '' : $res;
}
/**
* 载入公钥:支持 PEM 全文 / 去头尾的单行 base64
* @return \OpenSSLAsymmetricKey|resource|false
*/
protected function loadPublicKey(string $key)
{
if ($key === '') {
return false;
}
if (strpos($key, '-----BEGIN') === false) {
$key = "-----BEGIN PUBLIC KEY-----\n" . chunk_split(trim($key), 64, "\n") . "-----END PUBLIC KEY-----\n";
}
return openssl_pkey_get_public($key);
}
}
@@ -1,85 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | 萤火商城系统 [ 致力于通过产品和服务,帮助商家高效化开拓市场 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2017~2023 https://www.yiovo.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed 这不是一个自由软件,不允许对程序代码以任何形式任何目的的再发行
// +----------------------------------------------------------------------
// | Author: 萤火科技 <admin@yiovo.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\common\model;
use cores\BaseModel;
use app\common\library\helper;
/**
* 合作商关联商品券模型
* Class PartnersGoods
* @package app\common\model
*/
class PartnersGoods extends BaseModel
{
// 定义表名
protected $name = 'partners_goods';
// 定义主键
protected $pk = 'id';
/**
* 关联模型:商品
* @return \think\model\relation\HasOne
*/
public function goods(): \think\model\relation\HasOne
{
return $this->hasOne('Goods', 'id', 'goods_id');
}
/**
* 获取商品信息详情
* @param int $partnersId
* @param int $goodsId
* @return array|null|static
*/
public static function detail(int $partnersId, int $goodsId)
{
return static::get(['partners_id' => $partnersId, 'goods_id' => $goodsId]);
}
/**
* 获取合作商商品列表
* @param int $partnersId 合作商ID
* @return \think\Collection
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public static function getGoodsList(int $partnersId): \think\Collection
{
return (new static)->where('partners_id', '=', $partnersId)->order(['id' => 'asc'])->select();
}
/**
* 累计合作商使用数量
* @param int|array $where
* @param int $num
* @return mixed
*/
public static function setIncUseNum($where, int $num)
{
return (new static)->setInc($where, 'use_num', $num);
}
/**
* 减少合作商库存数量
* @param int|array $where
* @param int $num
* @return mixed
*/
public static function setDecStockNum($where, int $num)
{
return (new static)->setDec($where, 'stock_num', $num);
}
}
@@ -1,54 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | 萤火商城系统 [ 致力于通过产品和服务,帮助商家高效化开拓市场 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2017~2023 https://www.yiovo.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed 这不是一个自由软件,不允许对程序代码以任何形式任何目的的再发行
// +----------------------------------------------------------------------
// | Author: 萤火科技 <admin@yiovo.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\common\model\partners;
use cores\BaseModel;
/**
* 合作商库存变动记录
* Class StockLog
* @package app\common\model\partners
*/
class StockLog extends BaseModel
{
// 定义表名
protected $name = 'partners_stock_log';
// 定义主键
protected $pk = 'id';
protected $updateTime = false;
/**
* 关联合作商记录表
* @return \think\model\relation\BelongsTo
*/
public function partners()
{
$module = self::getCalledModule();
return $this->belongsTo("app\\{$module}\\model\\Partners");
}
/**
* 新增记录
* @param array $data
*/
public static function add(array $data)
{
$model = new static;
$model->save(array_merge([
'store_id' => $model::$storeId
], $data));
}
}
@@ -0,0 +1,105 @@
<?php
// +----------------------------------------------------------------------
// | 合作商(下单平台)余额管控
// +----------------------------------------------------------------------
// | 企油通改造后,下单按金额直传,不再有商品/库存额度概念,
// | 改由 yoshop_partners.balance 承担下单额度管控:
// | 下单时原子扣减(余额不足直接拒单),发放失败/撤单时原路回补。
// | 扣减用 UPDATE ... WHERE balance >= amount,天然防并发超扣。
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\common\service;
use app\common\enum\partners\balanceLog\Scene as SceneEnum;
use think\facade\Db;
class PartnersBalance
{
/**
* 原子扣减合作商余额(余额不足返回 false,不做任何变更)
* @param int $partnersId 合作商ID
* @param float $amount 扣减金额(元)
* @param string $orderNo 业务单号(写入流水描述)
* @param int $orderId 关联订单ID
* @param int $storeId 商城ID
* @return int|false 成功返回流水ID(供建单后回填 order_id),余额不足返回 false
*/
public static function deduct(int $partnersId, float $amount, string $orderNo, int $orderId = 0, int $storeId = 0)
{
if ($amount <= 0) {
return false;
}
$affected = Db::name('partners')
->where('partners_id', '=', $partnersId)
->where('balance', '>=', $amount)
->update(['balance' => Db::raw('`balance` - ' . self::money($amount))]);
if (!$affected) {
return false;
}
return self::log(SceneEnum::CONSUME, $partnersId, -$amount, $orderNo, $orderId, $storeId);
}
/**
* 回填流水的关联订单ID
*/
public static function bindOrder($logId, int $orderId): void
{
if ($logId && $orderId) {
Db::name('partners_balance_log')->where('log_id', '=', (int)$logId)->update(['order_id' => $orderId]);
}
}
/**
* 回补合作商余额(发放失败 / 撤单)
*/
public static function refund(int $partnersId, float $amount, string $orderNo, int $orderId = 0, int $storeId = 0): void
{
if ($amount <= 0) {
return;
}
Db::name('partners')
->where('partners_id', '=', $partnersId)
->update(['balance' => Db::raw('`balance` + ' . self::money($amount))]);
self::log(SceneEnum::REFUND, $partnersId, $amount, $orderNo, $orderId, $storeId);
}
/**
* 查询当前余额
*/
public static function balance(int $partnersId): float
{
return (float) Db::name('partners')->where('partners_id', '=', $partnersId)->value('balance');
}
/**
* 写余额变动流水(失败不影响主流程),返回流水ID
* @return int
*/
protected static function log(int $scene, int $partnersId, float $money, string $orderNo, int $orderId, int $storeId): int
{
try {
return (int) Db::name('partners_balance_log')->insertGetId([
'partners_id' => $partnersId,
'scene' => $scene,
'money' => round($money, 2),
'describe' => vsprintf(SceneEnum::data()[$scene]['describe'], [$orderNo]),
'remark' => '',
'order_id' => $orderId,
'store_id' => $storeId,
'create_time' => time(),
]);
} catch (\Throwable $e) {
log_record(['name' => '合作商余额流水写入失败', 'partners_id' => $partnersId, 'msg' => $e->getMessage()]);
return 0;
}
}
/**
* 金额安全格式化(仅允许数字与小数点,防拼接注入)
*/
protected static function money(float $amount): string
{
return number_format(round($amount, 2), 2, '.', '');
}
}
@@ -44,19 +44,21 @@ class QueueService
/**
* 检查延迟队列,将到期任务迁移到普通队列
* 并发安全:先 zrem,只有「移除成功(返回>0)」的进程才把任务放进就绪队列,
* 避免多个消费者同时迁移导致同一任务被投递多次。
* @param string $queueName
*/
public function migrateDelayedToReady($queueName)
{
$now = time();
// 获取到期任务
$tasks = $this->redis->zrangebyscore($queueName . ':delayed', 0, $now);
if (!empty($tasks)) {
foreach ($tasks as $task) {
// 放回普通队列
if (empty($tasks)) {
return;
}
foreach ($tasks as $task) {
// 先抢占式移除,再入队
if ($this->redis->zrem($queueName . ':delayed', $task) > 0) {
$this->push($queueName, $task);
// 从延迟队列中移除
$this->redis->zrem($queueName . ':delayed', $task);
}
}
}
+189
View File
@@ -0,0 +1,189 @@
<?php
// +----------------------------------------------------------------------
// | 企油通 · 上游接口客户端(支付宝企业码通道)
// +----------------------------------------------------------------------
// | 本次实际对接仅 3 个接口:
// | 7.7 运单油费下发 createAndSendQuota —— 本类 createAndSendQuota()
// | 7.15 H5 跳转地址 (本地签名生成 URL) —— 本类 h5Url()
// | 7.10 运单加油明细推送(异步回调) —— app\api\controller\QytNotify
// | 通道固定为支付宝企业码(passageway=1),司机手机号 + 姓名必填。
// | 金额:对外「元」入参,7.7 内部换算为「分」。
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\common\service;
use app\common\crypto\Qyt as QytCrypto;
class QytOil extends BaseService
{
// 支付宝企业码通道
const PASSAGEWAY_ALIPAY = 1;
/** @var array config('qyt') */
protected $conf;
/** @var QytCrypto */
protected $crypto;
protected function initialize()
{
$this->conf = (array) config('qyt');
$this->crypto = new QytCrypto($this->conf);
}
// ================================================================
// 7.7 运单油费下发(一张订单 = 1 batch / 1 waybill / 1 employee)
// ================================================================
/**
* @param array $data ['batch_no','waybill_no','waybill_no_code','mobile','name','amount'(元)]
* @return array 归一化 ['code'=>'00'|其它, 'data'=>[], 'message'=>'', 'dup_batch'=>bool]
*/
public function createAndSendQuota(array $data): array
{
$params = [
'batch_no' => $data['batch_no'],
'business_license' => $this->conf['business_license'],
'passageway' => (string) self::PASSAGEWAY_ALIPAY,
'institution_id' => $this->conf['institution_id'],
'waybill_list' => [[
'waybill_no' => $data['waybill_no'],
'employee_list' => [[
'mobile' => $data['mobile'],
'name' => $data['name'],
'quota_amount' => self::yuanToFen($data['amount']), // 分
'voucher_name' => $this->conf['voucher_name'],
'effective_start' => '', // 空=当日生效
'effective_end' => '', // 空=长期有效
'waybill_no_code' => $data['waybill_no_code'],
]],
]],
];
return $this->post('/enterprise/api/zhyk/createAndSendQuota', $params);
}
// ================================================================
// 7.15 H5 免登加油地址(本地签名生成 URL,不发起接口请求)
// ================================================================
public function h5Url(string $mobile): string
{
$creditcode = !empty($this->conf['h5_with_creditcode']) ? (string) $this->conf['business_license'] : '';
return $this->crypto->h5Url($mobile, $creditcode);
}
// ================================================================
// 内部:统一请求
// ================================================================
/**
* @return array ['code' => '00'成功 | 其它, 'data' => array, 'message' => string, 'dup_batch' => bool, 'raw' => array]
*/
protected function post(string $path, array $bizParams): array
{
$url = rtrim((string) $this->conf['base_url'], '/') . $path;
$body = $this->crypto->envelope($bizParams);
$this->saveLog("[请求] {$path} " . json_encode(['url' => $url, 'params' => $bizParams], JSON_UNESCAPED_UNICODE));
$resp = $this->curlPost($url, json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
$this->saveLog("[响应] {$path} " . $resp);
$json = json_decode($resp, true);
if (!is_array($json)) {
return ['code' => 'ERR', 'data' => [], 'message' => '响应解析失败', 'dup_batch' => false, 'raw' => ['body' => $resp]];
}
// 通用响应结构:{ status: ok|error, data, message, serialNo, sign }
$ok = ($json['status'] ?? '') === 'ok';
// 【TODO 待对接人确认】是否所有响应都带 sign、验签串如何构造
// if ($ok && !empty($json['sign'])) { $ok = $this->crypto->verify($json['data'] ?? [], $json['sign']); }
$message = (string) ($json['message'] ?? '');
// 幂等:batch_no 已存在 —— 视为「已受理」,供 7.7 重试判定
$dupBatch = false;
if (!$ok && $message !== '') {
foreach ((array) (config('qyt.dup_batch_hints') ?: ['已存在', '重复', 'duplicate', 'exist']) as $hint) {
if (mb_stripos($message, $hint) !== false) {
$dupBatch = true;
break;
}
}
}
return [
'code' => $ok ? '00' : 'ERR',
'data' => $json['data'] ?? [],
'message' => $message,
'dup_batch' => $dupBatch,
'raw' => $json,
];
}
protected function curlPost(string $url, string $jsonBody): string
{
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $jsonBody,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => (int) ($this->conf['timeout'] ?? 15),
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => false,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Charset: UTF-8'],
]);
$resp = curl_exec($ch);
if ($resp === false) {
$err = curl_error($ch);
curl_close($ch);
return json_encode(['status' => 'error', 'message' => 'cURL: ' . $err], JSON_UNESCAPED_UNICODE);
}
curl_close($ch);
return (string) $resp;
}
// ================================================================
// 工具
// ================================================================
/** 元 → 分 */
public static function yuanToFen($yuan): int
{
return (int) bcmul((string) $yuan, '100', 0);
}
/** 分 → 元(字符串,保留 2 位) */
public static function fenToYuan($fen): string
{
return bcdiv((string) (int) $fen, '100', 2);
}
/**
* 生成企油通全平台唯一编号(加企业前缀),确定性派生 -> 重试保持一致
* @param string $type B=batch / W=waybill / C=waybill_no_code
* @param string $seed 订单号 / order_id 等业务种子
*/
public static function genNo(string $type, string $seed): string
{
$prefix = (string) (config('qyt.no_prefix') ?: 'HM');
return $prefix . $type . $seed;
}
/** 记录日志到 runtime/storelog */
public function saveLog($data): void
{
if (empty($this->conf['log'])) {
return;
}
$dir = runtime_path('storelog/') . date('Ym') . '/' . date('Ymd');
is_dir($dir) || @mkdir($dir, 0777, true);
@file_put_contents(
$dir . '/qyt_' . date('YmdH') . '.txt',
date('Y-m-d H:i:s ') . (is_string($data) ? $data : json_encode($data, JSON_UNESCAPED_UNICODE)) . "\r\n",
FILE_APPEND
);
}
}
@@ -330,6 +330,70 @@ class CallbackQueue extends Command
// $SybOil->saveLogStore('重新加入队列:【'.$message.'】');
}
break;
case "qyt_consume":
// 企油通 7.10 运单加油明细推送(一次可能多条;企油通可能重推 -> 全流程幂等)
foreach ((array)($res['order_list'] ?? []) as $c) {
$code = $c['waybill_no_code'] ?? '';
$order = Db::name('partners_order')->where('waybill_no_code', $code)->find();
if (!$order) {
$SybOil->saveLogStore('企油通消费回调未匹配订单:' . json_encode($c, 320));
continue;
}
$payTime = !empty($c['pay_time']) ? (strtotime($c['pay_time']) ?: time()) : time();
// 落消费流水(uk_consume 去重:重复回调直接忽略)
try {
Db::name('qyt_consume')->insert([
'order_id' => $order['order_id'],
'waybill_no' => $c['waybill_no'] ?? '',
'waybill_no_code' => $code,
'order_code' => $c['order_code'] ?? '',
'station_code' => $c['station_code'] ?? '',
'station_name' => $c['station_name'] ?? '',
'oil_name' => $c['oil_name'] ?? '',
'litter' => $c['litter'] ?? '',
'oil_price' => (int)($c['oil_price'] ?? 0),
'actual_oil_price' => (int)($c['actual_oil_price'] ?? 0),
'pay_fee' => (int)($c['pay_fee'] ?? 0),
'consume_amount' => (int)($c['consume_amount'] ?? 0),
'pay_time' => $payTime,
'raw' => json_encode($c, 320),
'store_id' => $order['store_id'],
'create_time' => time(),
]);
} catch (\Exception $e) {
if (strpos($e->getMessage(), 'Duplicate') === false) {
throw $e;
}
$SybOil->saveLogStore('企油通消费回调重复,忽略流水:' . $code . '/' . ($c['order_code'] ?? ''));
}
// 条件跃迁 use_status 0 -> 1(要求已发放),仅胜出者通知下单平台
$affected = Db::name('partners_order')->where('order_id', $order['order_id'])
->where('use_status', 0)
->where('oil_status', 1)
->update([
'use_status' => 1,
'use_time' => time(),
'use_text' => '加油',
'qyt_order_code' => $c['order_code'] ?? '',
'shareNo' => $c['order_code'] ?: $order['shareNo'],
'station_name' => $c['station_name'] ?? '',
'oil_name' => $c['oil_name'] ?? '',
'litter' => $c['litter'] ?? '',
'pay_fee' => (int)($c['pay_fee'] ?? 0),
'consume_time' => $payTime,
'update_time' => time(),
]);
if ($affected) {
$queueService = new QueueService();
$queueService->push('notify_queue', json_encode([
'order_id' => $order['order_id'],
'optType' => 'add_oil',
'retry_count' => 0,
], 320));
$SybOil->saveLogStore('企油通消费回调处理成功:' . json_encode($c, 320));
}
}
break;
default:
$SybOil->saveLogStore('队列数据处理异常,未知:【'.$message.'】');
}
+163 -91
View File
@@ -1,36 +1,54 @@
<?php
// +----------------------------------------------------------------------
// | 下单平台订单 -> 企油通「运单油费下发」(7.7)
// +----------------------------------------------------------------------
// | OilCard::share 创建 yoshop_partners_order 后 push 'partners_queue',本命令消费:
// | 1. 每个 order_id 用 RedisLock 串行化,锁内重读订单状态
// | 2. 生成企油通全平台唯一编号(确定性派生,可安全重试)
// | 3. 调 7.7 createAndSendQuota;支付宝企业码通道下「受理成功即完成发放」
// | 4. 发放成功 -> oil_status=1 / oil_status_zsy=2,投 notify_queue 回调下单平台
// |
// | 并发/原子性:
// | · oil_status_zsy 只在 7.7 结果确定后离开 0;空闲时 reconcile() 补投丢失的队列消息
// | · 状态跃迁为条件 UPDATE(... WHERE oil_status_zsy=0),affected=1 才回调,天然幂等
// | · batch_no 确定性派生 -> 崩溃重试用同一值,依赖企油通按 batch_no 幂等去重
// | 启动:php think partners:queue(可多进程;RedisLock 保证同一订单串行)
// +----------------------------------------------------------------------
namespace app\console\command;
use app\common\service\IdingjuOil;
use app\common\lock\RedisLock;
use app\common\model\partners\Order as OrderModel;
use app\common\service\PartnersBalance;
use app\common\service\QueueService;
use app\store\model\Company as CompanyModel;
use app\common\service\QytOil;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use app\common\model\partners\Order as OrderModel;
use think\facade\Db;
class PartnersQueue extends Command
{
/** @var QueueService */
protected $queueService;
/** @var int 上次兜底巡检时间 */
protected $lastReconcile = 0;
protected function configure()
{
$this->setName('partners:queue')
->setDescription('Process messages from the Redis queue');
->setDescription('下单平台订单 -> 企油通运单油费下发(7.7)');
}
protected function execute(Input $input, Output $output)
{
$this->queueService = new QueueService();
while (true) {
$message = $this->queueService->pop('partners_queue');
if ($message) {
// 处理消息
$this->processMessage($message);
} else {
// 暂停一段时间再检查队列
$this->reconcile();
sleep(1);
}
}
@@ -38,103 +56,157 @@ class PartnersQueue extends Command
protected function processMessage($message)
{
$IdingjuOil = new IdingjuOil();
$IdingjuOil->saveLogStore('partners_queue队列数据处理:【'.$message.'】');
// 拿到队列数据处理
$data = json_decode($message, true);
// 检查解码是否成功
if ($data === false) {
$IdingjuOil->saveLogStore('partners_queue队列数据解码失败:' . json_last_error_msg());
$qyt = new QytOil();
$data = is_array($message) ? $message : json_decode($message, true);
$orderId = $data['order_id'] ?? null;
if (!$orderId) {
$qyt->saveLog('partners_queue 消息缺少 order_id:' . json_encode($message, 320));
return;
}
$order = OrderModel::detail($data['order_id']);
if (!$order) {
$IdingjuOil->saveLogStore('未找到对应订单:【' . $data['order_id'] . '】');
// 每个订单串行处理;锁 60s 自动过期,崩溃不会死锁
$lock = new RedisLock('qyt:send:' . $orderId, 60);
if (!$lock->acquireLock()) {
$qyt->saveLog("partners_queue 订单处理中,跳过 order={$orderId}");
return;
}
// 此处可以根据业务逻辑处理接收到的数据
try {
if($order['order_status'] == 10 && $order['oil_status'] == 0 && $order['oilCardType'] == 'zsy_ticket'){
$data['amount'] = $order['total_price']; // 金额
$company = CompanyModel::detail(['orgNo' => $this->getCompany($order['goods_no'])]);
if (!$company) {
$IdingjuOil->saveLogStore('没有可用参数:【' . $order['goods_no'] . '】');
return;
}
// 对接平台接口
$res = $IdingjuOil->oilOilCardShare([
'goodsNo'=>$order['goods_no'],
'amount'=>$data['amount'],
'userNo'=>'',
'mobile'=>$order['mobile'],
'saleOrderNo'=>$order['oilCardNo'],
'oilCardType'=>$order['oilCardType'],
'notify_url'=>config('app.base_url') . 'partnersoil.php',
'use_notify_url'=>config('app.base_url') . 'partnersoiluse.php'
], $company);
if ($res['code'] != 1) {
$IdingjuOil->saveLogStore(($res['errMessage'] ?? $res['message']));
return;
}
$item['shareNo'] = $res['data']['trade_no'] ?? '';
$item['qrCode'] = $res['data']['useUrl'] ?? '';
$item['oil_status_zsy'] = $res['data']['state'] ?? 0;
$item['oil_time'] = time();
$item['oil_status'] = $item['oil_status_zsy'] == 2 ? 1 : 0; // 已发券 为1
$resUpdate = $order->save($item);
if (!$resUpdate) {
$IdingjuOil->saveLogStore("订单更新失败,order_id: {$data['order_id']}");
}
if($item['oil_status_zsy'] == 2) {
// 存入队列处理
$queueService = new QueueService();
$queueService->push('notify_queue', json_encode([
'order_id' => $order['order_id'],
'type' => 'add',
'optType' => 'share_oil',
'retry_count' => 0,
], 320));
}
$order = OrderModel::detail($orderId);
if (!$order) {
$qyt->saveLog('partners_queue 未找到订单:' . $orderId);
return;
}
} catch (\Exception $e) {
$IdingjuOil->saveLogStore(
'partners_queue队列处理异常:' . $e->getMessage() .
' 在文件:' . $e->getFile() .
' 第:' . $e->getLine() .
' 行。堆栈跟踪:' . $e->getTraceAsString()
);
if ($order['order_status'] != 10 || $order['oil_status'] != 0 || (int) $order['oil_status_zsy'] !== 0) {
$qyt->saveLog("partners_queue 订单状态不满足下发条件 order={$orderId}");
return;
}
// 1. 确定性派生编号(含企业前缀),重试时保持一致
$seed = $order['partners_order_no'] ?: (string) $order['order_id'];
$batchNo = QytOil::genNo('B', $seed);
$waybillNo = QytOil::genNo('W', $seed);
$waybillNoCode = QytOil::genNo('C', $seed);
$name = trim((string) $order['driver_name']);
if ($name === '' || $order['mobile'] === '') {
$failed = Db::name('partners_order')->where('order_id', $orderId)->where('oil_status_zsy', 0)
->update(['oil_status_zsy' => 3, 'oil_text' => '缺司机姓名/手机号,无法发放', 'update_time' => time()]);
$failed && $this->refundBalance($order);
return;
}
// 2. 先落编号(状态不动,仍为 0)
Db::name('partners_order')->where('order_id', $orderId)->update([
'batch_no' => $batchNo,
'waybill_no' => $waybillNo,
'waybill_no_code' => $waybillNoCode,
'oil_text' => '发放中',
'update_time' => time(),
]);
// 3. 7.7 运单油费下发
$res = $qyt->createAndSendQuota([
'batch_no' => $batchNo,
'waybill_no' => $waybillNo,
'waybill_no_code' => $waybillNoCode,
'mobile' => $order['mobile'],
'name' => $name,
'amount' => $order['total_price'],
]);
// 失败(且非 batch 已存在)
if ($res['code'] !== '00' && empty($res['dup_batch'])) {
$failed = Db::name('partners_order')->where('order_id', $orderId)->where('oil_status_zsy', 0)->update([
'oil_status_zsy' => 3,
'oil_text' => '下发失败:' . $res['message'],
'update_time' => time(),
]);
$failed && $this->refundBalance($order);
$qyt->saveLog("7.7 下发失败 order={$orderId}:" . $res['message']);
return;
}
// data.status: 1 成功 / 2 失败(dup_batch 视为已受理成功)
$status = empty($res['dup_batch']) ? (string) ($res['data']['status'] ?? '1') : '1';
if ($status === '2') {
$failed = Db::name('partners_order')->where('order_id', $orderId)->where('oil_status_zsy', 0)->update([
'oil_status_zsy' => 3,
'oil_text' => $res['data']['msg'] ?? '下发失败',
'update_time' => time(),
]);
$failed && $this->refundBalance($order);
return;
}
// 4. 受理成功 = 发放成功(支付宝企业码通道);条件跃迁 0 -> 2,胜出者回调下单平台
$affected = Db::name('partners_order')->where('order_id', $orderId)->where('oil_status_zsy', 0)->update([
'oil_status' => 1,
'oil_status_zsy' => 2,
'oil_time' => time(),
'shareNo' => $waybillNoCode,
'oil_text' => $res['data']['msg'] ?? '发放成功',
'update_time' => time(),
]);
if ($affected) {
$this->queueService->push('notify_queue', json_encode([
'order_id' => $orderId,
'type' => 'add',
'optType' => 'share_oil',
'retry_count' => 0,
], 320));
$qyt->saveLog("7.7 发放成功 order={$orderId} batch_no={$batchNo}"
. (empty($res['dup_batch']) ? '' : '(batch 已存在,按已发放处理)'));
}
} catch (\Throwable $e) {
// 不改状态(仍为 0),交由 reconcile 兜底重投
$qyt->saveLog('partners_queue 异常 order=' . $orderId . ':' . $e->getMessage()
. ' @ ' . $e->getFile() . ':' . $e->getLine());
} finally {
$lock->releaseLock();
}
}
/**
* @param $goodsNo
* @return string
* 发放失败 -> 回补合作商余额(仅在状态跃迁胜出时调用,天然幂等)
*/
public function getCompany($goodsNo): string
protected function refundBalance($order)
{
switch ($goodsNo) {
case 'djgdface100':
case 'djgdface200':
case 'djgdface300':
return 'umQZhIHKofyA';
case 'jmgxyyface100':
case 'jmgxyyface200':
case 'jmgxyyface300':
PartnersBalance::refund(
(int) $order['partners_id'],
(float) $order['total_price'],
(string) $order['partners_order_no'],
(int) $order['order_id'],
(int) $order['store_id']
);
(new QytOil())->saveLog("发放失败已回补合作商余额 order={$order['order_id']} amount={$order['total_price']}");
}
case 'jmgzface100':
case 'jmgzface200':
case 'jmgzface300':
case 'jmfjface100':
case 'jmfjface200':
case 'jmfjface300':
return 'HpWqXIgYvTBg';
case 'face003':
return 'NKJBgaaRNdiL'; // 测试参数
default:
return '';
/**
* 兜底巡检(限频 60s):补投丢失的 partners_queue 消息。
* 因下发为条件跃迁,重复投递安全。
*/
protected function reconcile()
{
if (time() - $this->lastReconcile < 60) {
return;
}
$this->lastReconcile = time();
$now = time();
try {
$ids = Db::name('partners_order')
->where('order_status', 10)
->where('oil_status_zsy', 0)
->where('oilCardType', 'zsy_ticket')
->where('create_time', '<', $now - 120)
->where('create_time', '>', $now - 86400)
->limit(200)->column('order_id');
foreach ($ids as $oid) {
$this->queueService->push('partners_queue', json_encode(['order_id' => $oid], 320));
}
if ($ids) {
(new QytOil())->saveLog('partners_queue reconcile 补投 ' . count($ids) . ' 单');
}
} catch (\Throwable $e) {
(new QytOil())->saveLog('partners_queue reconcile 异常:' . $e->getMessage());
}
}
}
+2 -42
View File
@@ -224,48 +224,8 @@ class Company extends Controller
*/
public function stock(): Json
{
$arr = $this->postData();
// 商品详情
foreach ((array)$arr as $company_id) {
$company = CompanyModel::detail($company_id);
$IdingjuOil = new IdingjuOil();
$goodsNo = $this->getGoodsNo($company['orgNo']);
$goodsList = GoodsModel::getAll([['goods_no','in',$goodsNo]]);
$canOrderNum = $unUseNum = $useNum = 0;
foreach ($goodsList as $array){
// 对接平台接口
$res = $IdingjuOil->stockList(['goodsNo' => $array['goods_no']], $company);
// 更新记录
if ($res['code'] == '1') {
/*$res['data']['canOrderNum'] = $res['data']['canOrderNum']?:0;
$res['data']['unUseNum'] = $res['data']['unUseNum']?:0;
$res['data']['useNum'] = $res['data']['useNum']?:0;
$canOrderNum += $res['data']['canOrderNum'];
$unUseNum += $res['data']['unUseNum'];
$useNum += $res['data']['useNum'];
Db::name('goods')
->where('goods_id', $array['goods_id'])
->update(['stock_total' => $res['data']['canOrderNum'], 'unUseNum' => $res['data']['unUseNum'], 'useNum' => $res['data']['useNum']]);
Db::name('goods_sku')
->where('goods_id', $array['goods_id'])
->update(['stock_num' => $res['data']['canOrderNum'], 'unUseNum' => $res['data']['unUseNum'], 'useNum' => $res['data']['useNum']]);
*/
$res['data']['quantity'] = $res['data']['quantity']?:0;
$canOrderNum += $res['data']['quantity'];
Db::name('goods')
->where('goods_id', $array['goods_id'])
->update(['stock_total' => $res['data']['quantity']]);
Db::name('goods_sku')
->where('goods_id', $array['goods_id'])
->update(['stock_num' => $res['data']['quantity']]);
}
}
$company->save(['canOrderNum'=>$canOrderNum,'unUseNum'=>$unUseNum,'useNum'=>$useNum]);
}
return $this->renderSuccess($arr);
// 企油通改造:本功能依赖已下线的上游(智慧油客/定聚),现已停用。
return $this->renderError('上游库存同步已停用(企油通无油券库存概念)');
}
}
+2 -11
View File
@@ -183,17 +183,8 @@ class Goods extends Controller
*/
public function onReloadStock(int $goodsId): Json
{
// 商品详情
$model = GoodsModel::detail($goodsId);
$company = CompanyModel::detail(['status'=>10]);
$SybOil = new SybOil();
// 对接平台接口
$res = $SybOil->stockList(['goodsNo' => $model['goods_no']], $company);
// 更新记录
if ($res['code'] == '00') {
return $this->renderSuccess($res['data']);
}
return $this->renderError($model->getError() ?: $res['errMessage']);
// 企油通改造:上游库存同步已停用(企油通无油券库存概念,库存改为平台内部维护)。
return $this->renderError('上游库存同步已停用,请直接在「合作商」中维护可下单额度');
}
/**
+6 -126
View File
@@ -153,47 +153,8 @@ class Order extends Controller
*/
public function cancelShare(): Json
{
$postData = $this->postForm();
$postData['order_goods_id'] = $this->request->param('orderGoodsId');
$order_goods_id = $postData['order_goods_id'];
$password = $postData['password'];
//验证密码
$user = UserModel::detail($this->store['user']['store_user_id']);
if(!password_verify($password, $user['password'])){
return $this->renderError('登录密码错误');
}
$model = OrderGoodsModel::detail($order_goods_id);
if ($model['oil_status'] != 1) {
return $this->renderError('该订单状态不是已分油状态');
}
if ($model['use_status'] != 0) {
return $this->renderError('油卡已核销,无法撤销');
}
$order = OrderModel::detail($model['order_id']);
$company = CompanyModel::detail($model['company_id']);
// 对接平台接口,撤销分油
$res = $this->oilOilCardCancelShare(['shareNo'=>$model['shareNo'],'amount'=>$model['goods_price'],'saleOrderNo'=>$order['order_no'],'type'=>'recovery','oilCardType'=>$model['oilCardType']], $company);
if($res['code'] == '00'){
if ($model->cancelShare($postData)) {
// 更新商户余额(增加)
MerchantModel::setIncBalance((int)$model['merchant_id'], (float)$model['goods_price']);
BalanceLogModel::add(SceneEnum::REFUND, [
'merchant_id' => (int)$model['merchant_id'],
'money' => (float)$model['goods_price'],
], ['saleOrderNo' => $order['order_no']]);
// 更新公司余额(增加)
CompanyModel::setIncBalance((int)$model['company_id'], (float)$model['goods_price']);
CompanyBalanceLogModel::add(SceneEnum::REFUND, [
'company_id' => (int)$model['company_id'],
'money' => (float)$model['goods_price'],
], ['saleOrderNo' => $order['order_no']]);
return $this->renderSuccess('操作成功');
}
return $this->renderError($model->getError() ?: '操作失败');
}else{
return $this->renderError('平台撤销分油失败【'.($res['errMessage'] ?? $res['message']).'】');
}
// 企油通改造:本功能依赖已下线的上游(智慧油客/定聚),现已停用。
return $this->renderError('小程序油品链路已停用');
}
/**
@@ -203,36 +164,8 @@ class Order extends Controller
*/
public function qrCode(): Json
{
$postData = $this->postForm();
$postData['order_goods_id'] = $this->request->param('orderGoodsId');
$orderGoods = OrderGoodsModel::detail($postData['order_goods_id']);
if($orderGoods['use_status'] != 0){
return $this->renderError('油券已核销');
}else {
$company = CompanyModel::detail($orderGoods['company_id']);
$IdingjuOil = new IdingjuOil();
// 对接平台接口
$res = $IdingjuOil->payQrCodeRequest(['order_no'=>$orderGoods['oilCardNo']], $company);
if($res['code'] == '1' && isset($res['data']['useQrCodeUrl'])){
// 插入支付码记录
$QrCodeModel = new QrCodeModel();
$QrCodeModel->add([
'oilCardNo'=>$orderGoods['oilCardNo'],
'qrCode'=>$res['data']['useQrCodeUrl'],
'amount'=>$orderGoods['goods_price'],
'order_id'=>$orderGoods['order_id'],
'user_id'=>$orderGoods['user_id'],
'store_id'=>$orderGoods['store_id'],
]);
$res = [
'qrCode'=>$res['data']['useQrCodeUrl']
];
return $this->renderSuccess($res, '提交成功');
}else{
return $this->renderError($res['errMessage'] ?? $res['message']);
}
}
// 企油通改造:本功能依赖已下线的上游(智慧油客/定聚),现已停用。
return $this->renderError('小程序油品链路已停用');
}
/**
@@ -317,60 +250,7 @@ class Order extends Controller
*/
public function share(): Json
{
$IdingjuOil = new IdingjuOil();
$postData = $this->request->param();
$postData['order_goods_id'] = $this->request->param('orderGoodsId');
$goods = OrderGoodsModel::detail($postData['order_goods_id']);
if($goods['oil_status'] != 0){
return $this->renderError('已分油');
}else {
if($goods['oilCardType']) {
$data['amount'] = $goods['total_price']; // 金额
$company = CompanyModel::detail(['orgNo' => $this->getCompany($goods['goods_no'])]);
if (!$company) {
return $this->renderError('已分油');
}
try {
// 查找一个未被调用的用户
$account = $this->processUserOrders($company['company_id']);
} catch (\Throwable $e) {
return $this->renderError($e->getMessage());
}
// 查询公司余额
/*if ($data['amount'] > $company['balance']) {
$this->error = '公司余额不足';
return false;
}*/
// 对接平台接口
$res = $IdingjuOil->oilOilCardShare([
'goodsNo'=>$goods['goods_no'],
'amount'=>$data['amount'],
'userNo'=>$account['userNo'],
'mobile'=>$account['mobile'],
'saleOrderNo'=>$goods['oilCardNo'],
'oilCardType'=>$goods['oilCardType']
], $company);
//$res = json_decode('{"code":"00","message":"请求成功","errCode":null,"errMessage":null,"data":{"oilCardNo":null,"amount":100,"balance":null,"shareNo":"CDV2408021928160001"}}', true);
if ($res['code'] != 1) {
return $this->renderError($res['errMessage'] ?? $res['message']);
}
$IdingjuOil->saveLogStore("分油接口结果".json_encode($res,320));
$item['account_id'] = $account['account_id'];
$item['userNo'] = $account['userNo'];
$item['mobile'] = $account['mobile'];
$item['company_id'] = $company['company_id'];
$item['shareNo'] = $res['data']['trade_no'] ?? '';
$item['qrCode'] = $res['data']['useUrl'] ?? '';
$item['oil_status_zsy'] = $res['data']['state'] ?? 0;
$item['oil_time'] = time();
$item['oil_status'] = $item['oil_status_zsy'] == 2 ? 1 : 0; // 已发券 为1
$r = $goods->save($item);
$IdingjuOil->saveLogStore("保存分油结果".json_encode($r,320));
return $this->renderSuccess('分油成功');
}
}
// 企油通改造:本功能依赖已下线的上游(智慧油客/定聚),现已停用。
return $this->renderError('小程序油品链路已停用');
}
}
@@ -5,7 +5,6 @@ namespace app\store\controller;
use app\store\controller\Controller;
use app\store\model\Partners as PartnersModel;
use app\store\model\PartnersGoods as PartnersGoodsModel;
use app\store\model\partners\Order as OrderModel;
use app\store\model\store\User;
use think\response\Json;
@@ -94,13 +93,6 @@ class Partners extends Controller
public function detail(int $partnersId): Json
{
$partnersInfo = PartnersModel::detail($partnersId);
// 商品列表
$partnersInfo['goodsList'] = PartnersGoodsModel::getGoodsList($partnersInfo['partners_id']);
// 给每个元素加上 allot_num
foreach ($partnersInfo['goodsList'] as &$item) {
$item['allot_num'] = 0; // 默认值,可以改成其他逻辑
}
unset($item); // 解除引用,避免后续误操作
return $this->renderSuccess(compact('partnersInfo'));
}
+4 -108
View File
@@ -64,40 +64,8 @@ class User extends Controller
*/
public function add()
{
$model = new UserModel;
$data = $this->postForm();
if (empty($data['company_id'])) {
return $this->renderError('请填写分油公司');
}
if (empty($data['merchant_id'])) {
return $this->renderError('请填写商户');
}
if (empty($data['userType'])) {
return $this->renderError('请填写用户角色');
}
if (empty($data['userName'])) {
return $this->renderError('请填写用户姓名');
}
if (empty($data['mobile'])) {
return $this->renderError('请填写用户手机号');
}
if (empty($data['userIdCard'])) {
return $this->renderError('请填写用户身份证号');
}
$company = CompanyModel::detail($data['company_id']);
// 对接平台接口
$res = $this->userOptAdd(['mobile'=>$data['mobile'],'userName'=>$data['userName'],'userIdCard'=>$data['userIdCard'],'userType'=>$data['userType']], $company);
if($res['code'] == '00' && isset($res['data']['userNo'])){
$data['userNo'] = $res['data']['userNo'];
}else{
return $this->renderError('平台创建用户失败【'.($res['errMessage'] ?? $res['message']).'】');
}
// 新增记录
if ($model->add($data,$this->store['user'])) {
return $this->renderSuccess('添加成功');
}
return $this->renderError($model->getError() ?: '添加失败');
// 企油通改造:分油用户池已停用(司机信息改由下单平台随单传入),不再向上游注册用户。
return $this->renderError('分油用户池已停用');
}
/**
@@ -201,80 +169,8 @@ class User extends Controller
*/
public function oil(int $userId, string $target)
{
$data = $this->postForm();
$user = UserModel::detail($userId);
$data['amount'] = $data['amount']??0;
$data['goodsNo'] = $data['goodsNo']??'';
// 用户详情
$model = new OrderModel();
if (empty($data['oilCardType'])) {
return $this->renderError('请填写卡券类型');
}
if ($data['oilCardType'] == 'card' && empty($data['amount'])) {
return $this->renderError('请填写分油金额');
}
if ($data['oilCardType'] != 'card' && empty($data['goods_id'])) {
return $this->renderError('请选择油券');
}
if($data['goods_id']) {
$goods = GoodsModel::detail($data['goods_id']);
$data['goodsNo'] = $goods['goods_no'];
$data['amount'] = $goods['goods_price_max']; // 金额
}
// 查询用户余额
if($data['amount'] > $user['balance']){
return $this->renderError('用户余额不足');
}
$saleOrderNo = OrderService::createOrderNo();
try {
// 查找一个未被调用的用户
$account = $this->processUserOrders();
} catch (Exception $e) {
return $this->renderError($e->getMessage());
}
$company = CompanyModel::detail($account['company_id']);
// 查询公司余额
if($data['amount'] > $company['balance']){
return $this->renderError('公司余额不足');
}
$SybOil = new SybOil();
// 对接平台接口
$res = $SybOil->oilOilCardShare(['goodsNo'=>$data['goodsNo'],'amount'=>$data['amount'],'userNo'=>$account['userNo'],'mobile'=>$account['mobile'],'saleOrderNo'=>$saleOrderNo,'oilCardType'=>$data['oilCardType']], $company);
//$res = json_decode('{"code":"00","message":"请求成功","errCode":null,"errMessage":null,"data":{"oilCardNo":null,"amount":100,"balance":null,"shareNo":"CDV2407271355570001"}}',true);
if($res['code'] == '00'){
$data['saleOrderNo'] = $saleOrderNo;
$data['oilCardNo'] = $data['oilCardType'] == 'zsy_ticket' ? $res['data']['shareNo'] : $res['data']['oilCardNo'];
$data['amount'] = $res['data']['amount'];
$data['shareNo'] = $res['data']['shareNo'] ?? '';
$data['company_id'] = $account['company_id'];
$data['account_id'] = $account['account_id'];
$data['userNo'] = $account['userNo'];
$data['mobile'] = $account['mobile'];
}else{
return $this->renderError('平台分油失败【'.($res['errMessage'] ?? $res['message']).'】');
}
$data['user_id'] = $user['user_id'];
$data['store_id'] = $user['store_id'];
$data['payType'] = 10;
if ($model->addOil($data)) {
// 更新用户余额
UserModel::setDecBalance((int)$user['user_id'], (float)$data['amount']);
BalanceLogModel::add(SceneEnum::CONSUME, [
'user_id' => (int)$user['user_id'],
'money' => -(float)$data['amount'],
], ['saleOrderNo' => $saleOrderNo]);
// 累积用户总消费金额
UserModel::setIncPayMoney($user['user_id'], (float)$data['amount']);
// 更新公司余额
CompanyModel::setDecBalance((int)$user['company_id'], (float)$data['amount']);
CompanyBalanceLogModel::add(SceneEnum::CONSUME, [
'company_id' => (int)$user['company_id'],
'money' => -(float)$data['amount'],
], ['saleOrderNo' => $saleOrderNo]);
return $this->renderSuccess('操作成功');
}
return $this->renderError($model->getError() ?: '操作失败');
// 企油通改造:后台给小程序用户手动分油依赖已下线的上游(智慧油客)+ 分油用户池,现已停用。
return $this->renderError('小程序油品链路已停用');
}
/**
+126 -188
View File
@@ -1,34 +1,31 @@
<?php
// +----------------------------------------------------------------------
// | 萤火商城系统 [ 致力于通过产品和服务,帮助商家高效化开拓市场 ]
// | 后台 · 下单平台(合作商)订单管理
// +----------------------------------------------------------------------
// | Copyright (c) 2017~2023 https://www.yiovo.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed 这不是一个自由软件,不允许对程序代码以任何形式任何目的的再发行
// +----------------------------------------------------------------------
// | Author: 萤火科技 <admin@yiovo.com>
// | 企油通改造后:
// | list() 订单列表(含企油通批次号/运单号/发放态/加油明细)
// | detail() 订单详情 + 加油消费流水
// | share() 重新发放:把订单打回未提交态并重投 partners_queue,由 7.7 下发
// | qrCode() 获取司机免登加油地址(7.15),出参 qrCode 为 H5 URL
// | oilCallback() / useCallback() 重推「发放/加油」结果给下单平台
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\store\controller\partners;
use app\store\controller\Controller;
use app\store\model\partners\Order as OrderModel;
use app\store\model\Company as CompanyModel;
use app\common\service\IdingjuOil;
use app\store\model\order\QrCode as QrCodeModel;
use app\common\service\PartnersBalance;
use app\common\service\QueueService;
use app\common\service\QytOil;
use app\store\controller\Controller;
use app\store\model\order\QrCode as QrCodeModel;
use app\store\model\partners\Order as OrderModel;
use think\facade\Db;
use think\response\Json;
/**
* 余额明细
* Class Order
* @package app\store\controller\user
*/
class Order extends Controller
{
/**
* 订单明细
* 订单列表
* @return mixed
*/
public function list()
@@ -39,207 +36,148 @@ class Order extends Controller
}
/**
* 获取支付二维码
* 订单详情(含企油通加油消费流水)
* @return Json
*/
public function detail(): Json
{
$orderId = (int) $this->request->param('orderId');
$order = OrderModel::detail($orderId);
if (!$order) {
return $this->renderError('订单不存在');
}
$consume = Db::name('qyt_consume')
->where('order_id', $orderId)
->order('id', 'desc')
->select()
->toArray();
return $this->renderSuccess(['order' => $order, 'consume' => $consume]);
}
/**
* 获取司机免登加油地址(企油通 7.15)
* @return Json
* @throws \think\db\exception\DbException
*/
public function qrCode(): Json
{
$postData = $this->postForm();
$postData['order_id'] = $this->request->param('orderId');
$order = OrderModel::detail($postData['order_id']);
if($order['use_status'] != 0){
return $this->renderError('油券已核销');
}else {
$company = CompanyModel::detail(['orgNo' => $this->getCompany($order['goods_no'])]);
$IdingjuOil = new IdingjuOil();
// 对接平台接口
$res = $IdingjuOil->payQrCodeRequest(['order_no'=>$order['oilCardNo']], $company);
if($res['code'] == '1' && isset($res['data']['useQrCodeUrl'])){
// 插入支付码记录
$QrCodeModel = new QrCodeModel();
$QrCodeModel->add([
'oilCardNo'=>$order['oilCardNo'],
'qrCode'=>$res['data']['useQrCodeUrl'],
'amount'=>$order['goods_price'],
'order_id'=>$order['order_id'],
'user_id'=>0,
'store_id'=>$order['store_id'],
]);
$res = [
'qrCode'=>$res['data']['useQrCodeUrl']
];
return $this->renderSuccess($res, '提交成功');
}else{
return $this->renderError($res['errMessage'] ?? $res['message']);
}
$orderId = (int) $this->request->param('orderId');
$order = OrderModel::detail($orderId);
if (!$order) {
return $this->renderError('订单不存在');
}
if ($order['use_status'] != 0) {
return $this->renderError('该单已加油核销');
}
if ($order['oil_status'] != 1) {
return $this->renderError('该单尚未发放成功,无法获取加油地址');
}
$h5Url = (new QytOil())->h5Url((string) $order['mobile']);
if (!$h5Url) {
return $this->renderError('生成加油地址失败,请检查企油通配置');
}
// 留痕
(new 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->renderSuccess(['qrCode' => $h5Url], '获取成功');
}
/**
* @param $goodsNo
* @return string
*/
public function getCompany($goodsNo): string
{
switch ($goodsNo) {
case 'djgdface100':
case 'djgdface200':
case 'djgdface300':
return 'umQZhIHKofyA';
case 'jmgxyyface100':
case 'jmgxyyface200':
case 'jmgxyyface300':
case 'jmgzface100':
case 'jmgzface200':
case 'jmgzface300':
case 'jmfjface100':
case 'jmfjface200':
case 'jmfjface300':
return 'HpWqXIgYvTBg';
case 'face003':
return 'NKJBgaaRNdiL'; // 测试参数
default:
return '';
}
}
/**
* 手动分油
* 重新发放(把订单打回未提交态并重投队列,由 partners:queue 调用企油通 7.7)
* @return Json
* @throws \think\db\exception\DbException
*/
public function share(): Json
{
$IdingjuOil = new IdingjuOil();
$postData = $this->request->param();
$postData['order_id'] = $this->request->param('orderId');
$order = OrderModel::detail($postData['order_id']);
if($order['oil_status'] != 0){
return $this->renderError('已分油');
}else {
if($order['oilCardType']) {
$data['amount'] = $order['total_price']; // 金额
$company = CompanyModel::detail(['orgNo' => $this->getCompany($order['goods_no'])]);
if (!$company) {
return $this->renderError('已分油');
}
// 对接平台接口
$res = $IdingjuOil->oilOilCardShare([
'goodsNo'=>$order['goods_no'],
'amount'=>$data['amount'],
'userNo'=>0,
'mobile'=>$order['mobile'],
'saleOrderNo'=>$order['oilCardNo'],
'oilCardType'=>$order['oilCardType']
], $company);
if ($res['code'] != 1) {
return $this->renderError($res['errMessage'] ?? $res['message']);
}
$IdingjuOil->saveLogStore("分油接口结果".json_encode($res,320));
// $item['company_id'] = $company['company_id'];
$item['shareNo'] = $res['data']['trade_no'] ?? '';
$item['qrCode'] = $res['data']['useUrl'] ?? '';
$item['oil_status_zsy'] = $res['data']['state'] ?? 0;
$item['oil_time'] = time();
$item['oil_status'] = $item['oil_status_zsy'] == 2 ? 1 : 0; // 已发券 为1
$r = $order->save($item);
$IdingjuOil->saveLogStore("保存分油结果".json_encode($r,320));
return $this->renderSuccess('分油成功');
}
$orderId = (int) $this->request->param('orderId');
$order = OrderModel::detail($orderId);
if (!$order) {
return $this->renderError('订单不存在');
}
if ($order['oil_status'] == 1) {
return $this->renderError('该单已发放成功,无需重发');
}
if ($order['use_status'] != 0) {
return $this->renderError('该单已加油核销,不可重发');
}
// 发放失败时余额已回补,重发需重新扣减合作商余额
$logId = PartnersBalance::deduct(
(int) $order['partners_id'],
(float) $order['total_price'],
(string) $order['partners_order_no'],
$orderId,
(int) $order['store_id']
);
if ($logId === false) {
return $this->renderError('合作商余额不足,请先充值后再重发');
}
// 仅允许从「发放失败(3)」重置为「未提交(0)」,避免与正在处理的单冲突
$reset = Db::name('partners_order')->where('order_id', $orderId)
->where('oil_status', 0)
->where('oil_status_zsy', 3)
->update(['oil_status_zsy' => 0, 'oil_text' => '后台重新发放', 'update_time' => time()]);
if (!$reset) {
// 状态已被其它进程改变 -> 回补刚扣的余额
PartnersBalance::refund(
(int) $order['partners_id'],
(float) $order['total_price'],
(string) $order['partners_order_no'],
$orderId,
(int) $order['store_id']
);
return $this->renderError('当前状态不允许重发(仅「发放失败」的订单可重发)');
}
(new QueueService())->push('partners_queue', json_encode(['order_id' => $orderId], 320));
return $this->renderSuccess('已提交重新发放,请稍后刷新查看结果');
}
/**
* 分油回调
* 重推「发放结果」给下单平台
* @return Json
* @throws \think\db\exception\DbException
*/
public function oilCallback(): Json
{
/*
$model = new OrderModel;
$list = $model->getAll([
['partners_id', '=', 4],
['oil_status', '=', 1],
['oil_callback', '=', 0],
])->toArray();
$queueService = new QueueService();
foreach ($list as $order){
// 存入队列处理
$queueService->push('notify_queue', json_encode([
'order_id' => $order['order_id'],
'type' => 'add',
'optType' => 'share_oil',
'retry_count' => 0,
], 320));
$orderId = (int) $this->request->param('orderId');
$order = OrderModel::detail($orderId);
if (!$order) {
return $this->renderError('订单不存在');
}
return $this->renderSuccess('回调成功');*/
$postData = $this->request->param();
$postData['order_id'] = $this->request->param('orderId');
$order = OrderModel::detail($postData['order_id']);
if($order['oil_status'] == 0){
return $this->renderError('未分油');
}else {
// 存入队列处理
$queueService = new QueueService();
$queueService->push('notify_queue', json_encode([
'order_id' => $order['order_id'],
'type' => 'add',
'optType' => 'share_oil',
'retry_count' => 0,
], 320));
return $this->renderSuccess('回调成功');
if ($order['oil_status'] != 1) {
return $this->renderError('该单未发放成功,无可回调内容');
}
(new QueueService())->push('notify_queue', json_encode([
'order_id' => $orderId,
'type' => 'add',
'optType' => 'share_oil',
'retry_count' => 0,
], 320));
return $this->renderSuccess('已重新推送发放结果');
}
/**
* 使用回调
* 重推「加油核销」结果给下单平台
* @return Json
* @throws \think\db\exception\DbException
*/
public function useCallback(): Json
{
/*
$model = new OrderModel;
$list = $model->getAll([
['partners_id', '=', 4],
['use_status', '=', 1],
['use_callback', '=', 0],
])->toArray();
$queueService = new QueueService();
foreach ($list as $order){
// 存入队列处理
$queueService->push('notify_queue', json_encode([
'order_id' => $order['order_id'],
'optType' => 'add_oil',
'retry_count' => 0,
], 320));
$orderId = (int) $this->request->param('orderId');
$order = OrderModel::detail($orderId);
if (!$order) {
return $this->renderError('订单不存在');
}
return $this->renderSuccess('回调成功');*/
$postData = $this->request->param();
$postData['order_id'] = $this->request->param('orderId');
$order = OrderModel::detail($postData['order_id']);
if($order['use_status'] == 0){
return $this->renderError('未使用');
}else {
// 通知对接公司
$queueService = new QueueService();
$queueService->push('notify_queue', json_encode([
'order_id' => $order['order_id'],
'optType' => 'add_oil',
'retry_count' => 0,
], 320));
return $this->renderSuccess('回调成功');
if ($order['use_status'] != 1) {
return $this->renderError('该单未加油核销,无可回调内容');
}
(new QueueService())->push('notify_queue', json_encode([
'order_id' => $orderId,
'type' => 'sub',
'optType' => 'add_oil',
'retry_count' => 0,
], 320));
return $this->renderSuccess('已重新推送加油结果');
}
}
-14
View File
@@ -8,10 +8,8 @@ use think\Session;
use app\common\model\Partners as PartnersModel;
use app\store\model\Order as OrderModel;
use app\store\model\store\User as UserModel;
use app\store\model\PartnersGoods as PartnersGoodsModel;
use app\store\model\partners\BalanceLog as BalanceLogModel;
use app\store\model\partners\StockLog as StockLogModel;
use app\store\service\store\User as StoreUserService;
use app\common\enum\partners\balanceLog\Scene as SceneEnum;
use app\common\library\helper;
@@ -51,8 +49,6 @@ class Partners extends PartnersModel
$this->error = '请填写商户名称';
return false;
}
// 检查商品数据
$goodsList = PartnersGoodsModel::checkGoodsData($data['goodsList']);
$this->startTrans();
try {
$data['orgNo'] = $this->createNo();
@@ -75,10 +71,6 @@ class Partners extends PartnersModel
$userModel->save($newData);
// 新增角色关系记录
UserRole::increased((int)$userModel['store_user_id'], [10014]);
// 新增商品关系记录
PartnersGoodsModel::increased((int)$this['partners_id'], $goodsList);
// 新增合作商商品库存变动记录
StockLogModel::increased((int)$this['partners_id'], $goodsList);
$this->commit();
return true;
} catch (\Exception $e) {
@@ -146,16 +138,10 @@ class Partners extends PartnersModel
$this->error = '请填写商户名称';
return false;
}
// 检查商品数据
$goodsList = PartnersGoodsModel::checkGoodsData($data['goodsList']);
$this->startTrans();
try {
// 更新管理员记录
$this->save($data);
// 更新商品关系记录
PartnersGoodsModel::updates((int)$this['partners_id'], $goodsList);
// 新增合作商商品库存变动记录
StockLogModel::increased((int)$this['partners_id'], $goodsList);
$this->commit();
return true;
} catch (\Exception $e) {
@@ -1,85 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | 萤火商城系统 [ 致力于通过产品和服务,帮助商家高效化开拓市场 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2017~2023 https://www.yiovo.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed 这不是一个自由软件,不允许对程序代码以任何形式任何目的的再发行
// +----------------------------------------------------------------------
// | Author: 萤火科技 <admin@yiovo.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\store\model;
use app\common\library\helper;
use app\common\model\PartnersGoods as PartnersGoodsModel;
use cores\exception\BaseException;
/**
* 合作商商品关系模型
* Class PartnersGoods
* @package app\store\model
*/
class PartnersGoods extends PartnersGoodsModel
{
/**
* 验证商品是否合法
* @param array $goodsList
* @throws BaseException
*/
public static function checkGoodsData(array $goodsList)
{
$values = helper::getArrayColumn($goodsList, 'goods_id');
if (count($goodsList) != count(array_unique($values))) {
throwError('很抱歉,不能存在重复的商品 请检查您的选择');
}
foreach ($goodsList as &$item) {
if($item['allot_num'] < 0 && abs($item['allot_num']) > $item['stock_num']){
throwError('很抱歉,分配数量减少不能低于库存数量 请检查您的输入');
}elseif($item['allot_num'] > 100){
throwError('很抱歉,分配数量不能大于100 请检查您的输入');
}
$item['before_num'] = $item['stock_num'];
$item['stock_num'] += $item['allot_num'];
$item['after_num'] = $item['stock_num'];
$item['change_type'] = $item['allot_num'] < 0 ? 2 : 1;
}
unset($item);
return $goodsList;
}
/**
* 批量写入合作商与商品关系记录
* @param int $partnersId
* @param array $goodsList
* @return array|false
*/
public static function increased(int $partnersId, array $goodsList)
{
$dataset = [];
foreach ($goodsList as $item) {
$dataset[] = [
'partners_id' => $partnersId,
'goods_id' => $item['goods_id'],
'stock_num' => $item['stock_num'],
'store_id' => self::$storeId
];
}
return (new static)->addAll($dataset);
}
/**
* 批量更新合作商和商品关系记录
* @param int $partnersId
* @param array $goodsList
* @return array|false
*/
public static function updates(int $partnersId, array $goodsList)
{
// 删除所有的记录
static::deleteAll(['partners_id' => $partnersId]);
// 批量新增记录
return static::increased($partnersId, $goodsList);
}
}
+20 -11
View File
@@ -31,13 +31,13 @@ class Order extends OrderModel
// 设置查询条件
$filter = $this->getFilter($param);
// 先查询总金额
$totalPayPrice = $this->with(['goods.images.file', 'partners'])
$totalPayPrice = $this->with(['partners'])
->alias('order')
->field('order.*')
->where($filter)
->sum('order.total_pay_price');
// 获取列表数据
$list = $this->with(['goods.images.file', 'partners'])
$list = $this->with(['partners'])
->alias('order')
->field('order.*')
->where($filter)
@@ -60,23 +60,32 @@ class Order extends OrderModel
// 设置默认的检索数据
$params = $this->setQueryDefaultValue($param, [
'partners_id' => 0, // 合作商ID
'search' => '', // 用户昵称
'goods_no' => '', // 商品编码
'oil_status' => -1, // 分油状态
'use_status' => -1, // 使用状态
'search' => '', // 司机手机号
'driver_name' => '', // 司机姓名
'orderNo' => '', // 单号:油卡编号/下单平台单号/批次号/运单号
'oil_status' => -1, // 分油(发放)状态
'oil_status_zsy' => -1, // 企油通发放态 0未提交 2成功 3失败
'use_status' => -1, // 加油核销状态
'betweenTime' => [] // 起止时间
]);
// 检索查询条件
$filter = [];
// 合作商ID
$params['partners_id'] > 0 && $filter[] = ['order.partners_id', '=', $params['partners_id']];
// 用户昵称
// 司机手机号
!empty($params['search']) && $filter[] = ['order.mobile', 'like', "%{$params['search']}%"];
// 商品编码
!empty($params['goods_no']) && $filter[] = ['order.goods_no', 'like', "%{$params['goods_no']}%"];
// 分油状态
// 司机姓名
!empty($params['driver_name']) && $filter[] = ['order.driver_name', 'like', "%{$params['driver_name']}%"];
// 单号模糊检索(油卡编号 / 下单平台销售单号 / 企油通批次号 / 运单号)
if (!empty($params['orderNo'])) {
$kw = $params['orderNo'];
$filter[] = ['order.oilCardNo|order.partners_order_no|order.batch_no|order.waybill_no', 'like', "%{$kw}%"];
}
// 分油(发放)状态
$params['oil_status'] > -1 && $filter[] = ['oil_status', '=', (int)$params['oil_status']];
// 使用状态
// 企油通发放态
$params['oil_status_zsy'] > -1 && $filter[] = ['oil_status_zsy', '=', (int)$params['oil_status_zsy']];
// 加油核销状态
$params['use_status'] > -1 && $filter[] = ['use_status', '=', (int)$params['use_status']];
// 起止时间
if (!empty($params['betweenTime'])) {
@@ -1,95 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | 萤火商城系统 [ 致力于通过产品和服务,帮助商家高效化开拓市场 ]
// +----------------------------------------------------------------------
// | Copyright (c) 2017~2023 https://www.yiovo.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed 这不是一个自由软件,不允许对程序代码以任何形式任何目的的再发行
// +----------------------------------------------------------------------
// | Author: 萤火科技 <admin@yiovo.com>
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\store\model\partners;
use app\common\model\partners\StockLog as StockLogModel;
/**
* 合作商商品库存变动明细模型
* Class StockLog
* @package app\store\model\partners
*/
class StockLog extends StockLogModel
{
/**
* 获取合作商商品库存变动明细列表
* @param array $param
* @return \think\Paginator
*/
public function getList(array $param = [])
{
// 设置查询条件
$filter = $this->getFilter($param);
// 获取列表数据
return $this->alias('log')
->field('log.*,partners.name')
->where($filter)
->join('partners', 'partners.partners_id = log.partners_id')
->order(['log.create_time' => 'desc'])
->paginate(15);
}
/**
* 设置查询条件
* @param array $param
* @return array
*/
private function getFilter(array $param): array
{
// 设置默认的检索数据
$params = $this->setQueryDefaultValue($param, [
'partners_id' => 0, // 合作商ID
'search' => '', // 用户昵称
'betweenTime' => [] // 起止时间
]);
// 检索查询条件
$filter = [];
// 用户ID
$params['partners_id'] > 0 && $filter[] = ['log.partners_id', '=', $params['partners_id']];
// 用户昵称
!empty($params['search']) && $filter[] = ['partners.name', 'like', "%{$params['search']}%"];
// 起止时间
if (!empty($params['betweenTime'])) {
$times = between_time($params['betweenTime']);
$filter[] = ['log.create_time', '>=', $times['start_time']];
$filter[] = ['log.create_time', '<', $times['end_time'] + 86400];
}
return $filter;
}
/**
* 批量写入合作商与商品库存变动记录
* @param int $partnersId
* @param array $goodsList
* @return array|false
*/
public static function increased(int $partnersId, array $goodsList)
{
$dataset = [];
foreach ($goodsList as $item) {
if(!empty($item['allot_num'])) {
$dataset[] = [
'partners_id' => $partnersId,
'goods_id' => $item['goods_id'],
'before_num' => $item['before_num'],
'after_num' => $item['after_num'],
'change_type' => $item['change_type'],
'change_num' => $item['allot_num'],
'store_id' => self::$storeId
];
}
}
return (new static)->addAll($dataset);
}
}