feat(shopro_wechat_trade): 添加重新同步物流信息命令 新增 ResyncShipping 命令,支持强制重新上传微信物流信息, 不依赖 sync 表的 failed/pending 状态,适用于修改自提配置后 需要重传的场景。 同时修改 RetryFailed 命令,在没有待重试记录时提供操作提示, 并扩展 WxShippingService 的 retryFailed 方法支持处理 failed 状态的记录。 ```
202 行
6.6 KiB
PHP
202 行
6.6 KiB
PHP
<?php
|
||
|
||
namespace addons\shopro_wechat_trade\library;
|
||
|
||
use addons\shopro\model\Order;
|
||
use addons\shopro\model\OrderItem;
|
||
use think\Db;
|
||
use think\Log;
|
||
|
||
class WxShippingService
|
||
{
|
||
protected $client;
|
||
|
||
public function __construct()
|
||
{
|
||
$this->client = new WxTradeClient();
|
||
}
|
||
|
||
public function syncOrderShipping($orderId, $triggerItemId = 0)
|
||
{
|
||
if (!Bridge::isEnabled()) {
|
||
return false;
|
||
}
|
||
|
||
$order = Order::with('item')->where('id', $orderId)->find();
|
||
if (!$order || !Bridge::shouldHandleOrder($order)) {
|
||
return false;
|
||
}
|
||
|
||
$items = [];
|
||
if (!empty($order->item)) {
|
||
foreach ($order->item as $item) {
|
||
$items[] = is_object($item) && method_exists($item, 'toArray') ? $item->toArray() : (array) $item;
|
||
}
|
||
}
|
||
|
||
$triggerItem = null;
|
||
if ($triggerItemId) {
|
||
foreach ($items as $item) {
|
||
$itemArr = is_object($item) ? $item->toArray() : $item;
|
||
if (intval($itemArr['id']) === intval($triggerItemId)) {
|
||
$triggerItem = $itemArr;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
$built = WxShippingPayload::buildUploadPayload($order, $items, $triggerItem);
|
||
$payload = $built['payload'];
|
||
$logId = SyncLogger::create($order, 'shipping', $payload, $triggerItemId, $built['meta']['delivery_mode']);
|
||
|
||
try {
|
||
$response = $this->client->postJson('/wxa/sec/order/upload_shipping_info', $payload);
|
||
$this->client->callOrThrow($response, '上传发货信息');
|
||
SyncLogger::markSuccess($logId, $response, $payload);
|
||
return true;
|
||
} catch (\Exception $e) {
|
||
$response = isset($response) && is_array($response) ? $response : [];
|
||
SyncLogger::markFailed($logId, $e, $response, $payload);
|
||
Log::error('[shopro_wechat_trade] shipping sync fail order#' . $orderId . ': ' . $e->getMessage());
|
||
throw $e;
|
||
}
|
||
}
|
||
|
||
public function syncOrderConfirm($orderId)
|
||
{
|
||
if (!Bridge::isEnabled()) {
|
||
return false;
|
||
}
|
||
|
||
if (SyncLogger::hasSuccessfulConfirm($orderId)) {
|
||
return true;
|
||
}
|
||
|
||
if (!SyncLogger::hasSuccessfulShipping($orderId)) {
|
||
throw new \RuntimeException('须先成功 upload_shipping_info 再同步确认收货(notify_confirm_receive)');
|
||
}
|
||
|
||
$order = Order::with('item')->where('id', $orderId)->find();
|
||
if (!$order || !Bridge::shouldHandleOrder($order)) {
|
||
return false;
|
||
}
|
||
|
||
$pending = OrderItem::where('order_id', $orderId)
|
||
->where('dispatch_status', '<>', OrderItem::DISPATCH_STATUS_GETED)
|
||
->count();
|
||
if ($pending > 0) {
|
||
return false;
|
||
}
|
||
|
||
$payload = WxShippingPayload::buildConfirmPayload($order);
|
||
$logId = SyncLogger::create($order, 'confirm', $payload);
|
||
|
||
try {
|
||
$response = $this->client->postJson('/wxa/sec/order/notify_confirm_receive', $payload);
|
||
$this->client->callOrThrow($response, '同步确认收货');
|
||
SyncLogger::markSuccess($logId, $response, $payload);
|
||
return true;
|
||
} catch (\Exception $e) {
|
||
$response = isset($response) && is_array($response) ? $response : [];
|
||
SyncLogger::markFailed($logId, $e, $response, $payload);
|
||
Log::error('[shopro_wechat_trade] confirm sync fail order#' . $orderId . ': ' . $e->getMessage());
|
||
throw $e;
|
||
}
|
||
}
|
||
|
||
public function backfillShippedOrders($limit = 50)
|
||
{
|
||
$rows = Db::name('shopro_order')
|
||
->alias('o')
|
||
->join('shopro_order_item oi', 'oi.order_id = o.id')
|
||
->where('o.platform', 'wxMiniProgram')
|
||
->where('o.pay_type', 'in', ['wechat', 'huifu_wechat'])
|
||
->where('o.status', '>', 0)
|
||
->where('oi.dispatch_status', '>=', OrderItem::DISPATCH_STATUS_SENDED)
|
||
->group('o.id')
|
||
->order('o.id', 'desc')
|
||
->limit($limit)
|
||
->column('o.id');
|
||
|
||
$count = 0;
|
||
foreach ($rows as $orderId) {
|
||
$hasSuccess = Db::name('shopro_wechat_trade_sync')
|
||
->where('order_id', $orderId)
|
||
->where('sync_type', 'shipping')
|
||
->where('status', 'success')
|
||
->find();
|
||
if ($hasSuccess) {
|
||
continue;
|
||
}
|
||
try {
|
||
$this->syncOrderShipping($orderId);
|
||
$count++;
|
||
} catch (\Exception $e) {
|
||
// logged
|
||
}
|
||
}
|
||
return $count;
|
||
}
|
||
|
||
public function retryFailed($limit = 50)
|
||
{
|
||
$now = time();
|
||
$rows = Db::name('shopro_wechat_trade_sync')
|
||
->where(function ($q) use ($now) {
|
||
$q->where(function ($q2) use ($now) {
|
||
$q2->where('status', 'pending')
|
||
->where('retry_count', '>', 0)
|
||
->where('next_retry_time', '<=', $now);
|
||
})->whereOr('status', 'failed');
|
||
})
|
||
->order('id', 'asc')
|
||
->limit($limit)
|
||
->select();
|
||
|
||
$count = 0;
|
||
foreach ($rows as $row) {
|
||
try {
|
||
if ($row['sync_type'] === 'confirm') {
|
||
$this->syncOrderConfirm($row['order_id']);
|
||
} else {
|
||
$this->syncOrderShipping($row['order_id'], $row['order_item_id']);
|
||
}
|
||
$count++;
|
||
} catch (\Exception $e) {
|
||
// logged
|
||
}
|
||
}
|
||
return $count;
|
||
}
|
||
|
||
/**
|
||
* 按订单号再次调用 upload_shipping_info(已有 success 也会重传,用于改自提等)
|
||
*/
|
||
public function resyncShippingByOrderSns(array $orderSns)
|
||
{
|
||
$ok = 0;
|
||
$fail = 0;
|
||
foreach ($orderSns as $sn) {
|
||
$sn = trim((string) $sn);
|
||
if ($sn === '') {
|
||
continue;
|
||
}
|
||
$order = Db::name('shopro_order')->where('order_sn', $sn)->find();
|
||
if (!$order && strlen($sn) < 24) {
|
||
$order = Db::name('shopro_order')->where('order_sn', 'like', $sn . '%')->order('id', 'desc')->find();
|
||
}
|
||
if (!$order) {
|
||
$fail++;
|
||
continue;
|
||
}
|
||
try {
|
||
$this->syncOrderShipping((int) $order['id']);
|
||
$ok++;
|
||
} catch (\Exception $e) {
|
||
$fail++;
|
||
}
|
||
}
|
||
return ['ok' => $ok, 'fail' => $fail];
|
||
}
|
||
}
|