Files
douba_shop/app/haichao/callback/event/JstTradeEvent.php
Karlcarlo 7e422335e4 feat(haichao): 新增聚水潭ERP集成功能
- 新增聚水潭回调接口及事件处理机制
- 添加聚水潭商品、订单、售后同步任务
- 实现聚水潭订单推送及售后状态同步
- 新增手动同步聚水潭商品功能
- 在各路由组添加聚水潭中间件支持
2026-04-01 11:55:18 +08:00

299 lines
15 KiB
PHP

<?php
namespace app\haichao\callback\event;
use app\Request;
use app\services\erp\OrderServices;
use app\services\order\StoreOrderCartInfoServices;
use app\services\order\StoreOrderRefundServices;
use app\services\order\StoreOrderServices;
use think\facade\Log;
class JstTradeEvent implements JstEvent
{
public function event(Request $request): array
{
$payload = [];
$content = (string)$request->getContent();
if ($content !== '') {
$decoded = json_decode($content, true);
if (is_array($decoded)) {
$payload = $decoded;
} else {
$output = [];
parse_str($content, $output);
if (is_array($output)) {
$payload = $output;
}
}
}
if (!$payload) {
$payload = $request->param();
}
$biz = [];
if (isset($payload['biz']) && $payload['biz'] !== '') {
if (is_array($payload['biz'])) {
$biz = $payload['biz'];
} else {
$bizDecoded = json_decode((string)$payload['biz'], true);
if (is_array($bizDecoded)) {
$biz = $bizDecoded;
}
}
}
$actionName = (string)($payload['action_name'] ?? $biz['action_name'] ?? ($payload['action'] ?? $biz['action'] ?? ''));
$shopStatus = strtoupper((string)($payload['shop_status'] ?? $biz['shop_status'] ?? ($payload['shopStatus'] ?? $biz['shopStatus'] ?? '')));
$outerAsId = (string)($payload['outer_as_id'] ?? $biz['outer_as_id'] ?? ($payload['outerAsId'] ?? $biz['outerAsId'] ?? ''));
$soId = (string)($payload['so_id'] ?? $biz['so_id'] ?? ($payload['soId'] ?? $biz['soId'] ?? ''));
$bizStatus = strtoupper((string)($biz['status'] ?? $payload['status'] ?? ''));
$bizType = (string)($biz['type'] ?? '');
$bizRemark = (string)($biz['remark'] ?? '');
Log::info('jst.cb.trade_after_sale.start ' . json_encode([
'action_name' => $actionName,
'shop_status' => $shopStatus,
'outer_as_id' => $outerAsId,
'so_id' => $soId,
'biz_status' => $bizStatus,
'biz_type' => $bizType,
'content_len' => strlen($content),
], JSON_UNESCAPED_UNICODE));
if ($outerAsId === '') {
Log::info('jst.cb.trade_after_sale.skip ' . json_encode([
'reason' => 'missing outer_as_id',
'shop_status' => $shopStatus,
'action_name' => $actionName,
], JSON_UNESCAPED_UNICODE));
return [];
}
try {
$applyType = 1;
if ($bizType !== '') {
if (mb_strpos($bizType, '仅退款') !== false) {
$applyType = 1;
} elseif (mb_strpos($bizType, '退货') !== false) {
$applyType = 2;
}
}
/** @var StoreOrderServices $storeOrderServices */
$storeOrderServices = app()->make(StoreOrderServices::class);
$soIdsToTry = array_values(array_filter(array_unique([
$soId,
(string)(explode('_', $soId)[0] ?? ''),
])));
$order = null;
foreach ($soIdsToTry as $trySoId) {
$order = $storeOrderServices->getOne(['erp_order_id' => $trySoId], 'id,uid,store_id,supplier_id,order_id,erp_order_id,total_num,pay_price,pay_postage,channel,pid');
if ($order) break;
$order = $storeOrderServices->getOne(['order_id' => $trySoId], 'id,uid,store_id,supplier_id,order_id,erp_order_id,total_num,pay_price,pay_postage,channel,pid');
if ($order) break;
}
if (!$order) {
Log::error('jst.cb.trade_after_sale.order_missing ' . json_encode([
'outer_as_id' => $outerAsId,
'so_id' => $soId,
'biz_status' => $bizStatus,
'biz_type' => $bizType,
], JSON_UNESCAPED_UNICODE));
return [];
}
/** @var StoreOrderRefundServices $storeOrderRefundServices */
$storeOrderRefundServices = app()->make(StoreOrderRefundServices::class);
$orderRefund = $storeOrderRefundServices->getOne(['order_id' => $outerAsId]);
if (!$orderRefund) {
/** @var StoreOrderCartInfoServices $storeOrderCartInfoServices */
$storeOrderCartInfoServices = app()->make(StoreOrderCartInfoServices::class);
$items = $biz['items'] ?? [];
$skuQtyMap = [];
if (is_array($items)) {
foreach ($items as $item) {
if (!is_array($item)) continue;
$sku = (string)($item['sku_id'] ?? $item['skuId'] ?? '');
$qty = (int)($item['qty'] ?? 0);
if ($sku !== '' && $qty > 0) {
$skuQtyMap[$sku] = ($skuQtyMap[$sku] ?? 0) + $qty;
}
}
}
$targetOrder = $order;
$leftSkuQtyMap = $skuQtyMap;
$cartInfos = $storeOrderCartInfoServices->getCartColunm(['oid' => (int)$order['id']], 'id,cart_id,cart_num,cart_info');
foreach ($cartInfos as &$cartInfo) {
$cartInfo['cart_info'] = is_string($cartInfo['cart_info']) ? json_decode($cartInfo['cart_info'], true) : $cartInfo['cart_info'];
}
unset($cartInfo);
if ($skuQtyMap && (int)($order['pid'] ?? 0) === -1) {
$candidateOrders = [$order];
$subOrders = $storeOrderServices->getColumn(['pid' => (int)$order['id'], 'is_system_del' => 0], 'id,uid,store_id,supplier_id,order_id,erp_order_id,total_num,pay_price,pay_postage,channel,pid');
if ($subOrders && is_array($subOrders)) {
$candidateOrders = array_merge($candidateOrders, $subOrders);
}
$bestScore = -1;
$bestCartInfos = $cartInfos;
$bestLeftSkuQtyMap = $skuQtyMap;
foreach ($candidateOrders as $cand) {
if (!is_array($cand) || !isset($cand['id'])) continue;
$candCartInfos = $storeOrderCartInfoServices->getCartColunm(['oid' => (int)$cand['id']], 'id,cart_id,cart_num,cart_info');
foreach ($candCartInfos as &$candCartInfo) {
$candCartInfo['cart_info'] = is_string($candCartInfo['cart_info']) ? json_decode($candCartInfo['cart_info'], true) : $candCartInfo['cart_info'];
}
unset($candCartInfo);
$need = $skuQtyMap;
$score = 0;
foreach ($candCartInfos as $candCart) {
$ci = $candCart['cart_info'];
if (!is_array($ci)) continue;
$sku = (string)($ci['productInfo']['attrInfo']['code'] ?? $ci['productInfo']['attrInfo']['sku'] ?? $ci['productInfo']['sku'] ?? '');
if ($sku === '' || !isset($need[$sku]) || (int)$need[$sku] <= 0) continue;
$cartNum = (int)($candCart['cart_num'] ?? 0);
$useNum = min($cartNum, (int)$need[$sku]);
if ($useNum <= 0) continue;
$score += $useNum;
$need[$sku] -= $useNum;
}
if ($score > $bestScore) {
$bestScore = $score;
$targetOrder = $cand;
$bestCartInfos = $candCartInfos;
$bestLeftSkuQtyMap = $need;
}
}
$cartInfos = $bestCartInfos;
$leftSkuQtyMap = $bestLeftSkuQtyMap;
Log::info('jst.cb.trade_after_sale.split_match ' . json_encode([
'outer_as_id' => $outerAsId,
'so_id' => $soId,
'best_score' => $bestScore,
'target_store_order_id' => (int)($targetOrder['id'] ?? 0),
'target_order_id' => (string)($targetOrder['order_id'] ?? ''),
'target_erp_order_id' => (string)($targetOrder['erp_order_id'] ?? ''),
], JSON_UNESCAPED_UNICODE));
}
$selectedCartInfo = [];
if ($skuQtyMap) {
foreach ($cartInfos as $cart) {
$ci = $cart['cart_info'];
if (!is_array($ci)) continue;
$sku = (string)($ci['productInfo']['attrInfo']['code'] ?? $ci['productInfo']['attrInfo']['sku'] ?? $ci['productInfo']['sku'] ?? '');
if ($sku === '' || !isset($skuQtyMap[$sku])) continue;
$cartNum = (int)($cart['cart_num'] ?? 0);
$applyNum = min($cartNum, (int)$skuQtyMap[$sku]);
if ($applyNum <= 0) continue;
$ci['id'] = $ci['id'] ?? (int)($cart['id'] ?? 0);
$ci['cart_id'] = $ci['cart_id'] ?? (int)($cart['cart_id'] ?? 0);
$ci['cart_num'] = $applyNum;
$selectedCartInfo[] = $ci;
$skuQtyMap[$sku] -= $applyNum;
}
}
if (!$selectedCartInfo) {
$selectedCartInfo = array_values(array_filter(array_column($cartInfos, 'cart_info'), 'is_array'));
}
$refundNum = 0;
$refundPrice = 0.00;
foreach ($selectedCartInfo as $ci) {
$refundNum += (int)($ci['cart_num'] ?? 0);
if (isset($ci['truePrice'])) {
$refundPrice = bcadd((string)$refundPrice, bcmul((string)$ci['truePrice'], (string)($ci['cart_num'] ?? 0), 4), 2);
}
}
if ($refundPrice <= 0 && (int)($targetOrder['total_num'] ?? 0) > 0 && $refundNum > 0) {
$refundPrice = (float)bcmul((string)$targetOrder['pay_price'], bcdiv((string)$refundNum, (string)$targetOrder['total_num'], 6), 2);
}
$refundData = [
'uid' => (int)$targetOrder['uid'],
'store_id' => (int)$targetOrder['store_id'],
'supplier_id' => (int)($targetOrder['supplier_id'] ?? 0),
'store_order_id' => (int)$targetOrder['id'],
'refund_num' => $refundNum > 0 ? $refundNum : (int)($targetOrder['total_num'] ?? 0),
'apply_type' => $applyType,
'refund_type' => $applyType,
'refund_goods_type' => in_array($applyType, [2, 3]) ? $applyType : 1,
'apply_price' => $refundPrice,
'refund_price' => $refundPrice,
'refund_reason' => $bizRemark !== '' ? $bizRemark : '聚水潭发起售后',
'order_id' => $outerAsId,
'add_time' => time(),
'cart_info' => json_encode($selectedCartInfo, JSON_UNESCAPED_UNICODE),
'channel' => (int)($targetOrder['channel'] ?? 0),
];
$res = $storeOrderRefundServices->save($refundData);
$orderRefund = $storeOrderRefundServices->get((int)$res->id);
Log::info('jst.cb.trade_after_sale.refund_created ' . json_encode([
'outer_as_id' => $outerAsId,
'so_id' => $soId,
'store_order_id' => (int)$targetOrder['id'],
'refund_id' => (int)$res->id,
'apply_type' => $applyType,
'refund_num' => $refundData['refund_num'],
'refund_price' => $refundData['refund_price'],
], JSON_UNESCAPED_UNICODE));
}
if ($shopStatus === 'CLOSED' || $bizStatus === 'CLOSED') {
$storeOrderRefundServices->update((int)$orderRefund['id'], [
'refund_type' => 3,
'refuse_reason' => $bizRemark !== '' ? $bizRemark : '聚水潭关闭售后',
'refunded_time' => time(),
]);
Log::info('jst.cb.trade_after_sale.refund_closed ' . json_encode([
'outer_as_id' => $outerAsId,
'so_id' => $soId,
'refund_id' => (int)$orderRefund['id'],
], JSON_UNESCAPED_UNICODE));
return [];
}
if (in_array($bizStatus, ['REFUNDED', 'REFUND_SUCCESS', 'FINISHED', 'COMPLETED'], true)) {
/** @var OrderServices $orderServices */
$orderServices = app()->make(OrderServices::class);
$ok = (bool)$orderServices->receivedCallback(['outer_as_id' => $outerAsId, 'so_id' => $soId], 1, $soId !== '');
Log::info('jst.cb.trade_after_sale.refund_finalize ' . json_encode([
'outer_as_id' => $outerAsId,
'so_id' => $soId,
'refund_id' => (int)$orderRefund['id'],
'success' => $ok ? 1 : 0,
], JSON_UNESCAPED_UNICODE));
} else {
Log::info('jst.cb.trade_after_sale.synced ' . json_encode([
'outer_as_id' => $outerAsId,
'so_id' => $soId,
'refund_id' => (int)$orderRefund['id'],
'apply_type' => (int)($orderRefund['apply_type'] ?? 0),
'refund_type' => (int)($orderRefund['refund_type'] ?? 0),
], JSON_UNESCAPED_UNICODE));
}
} catch (\Throwable $e) {
Log::error('jst.cb.trade_after_sale.error ' . json_encode([
'outer_as_id' => $outerAsId,
'so_id' => $soId,
'action_name' => $actionName,
'shop_status' => $shopStatus,
'biz_status' => $bizStatus,
'biz_type' => $bizType,
'error' => [
'class' => get_class($e),
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
],
], JSON_UNESCAPED_UNICODE));
}
return [];
}
}