setName('shopro_wechat_trade:confirm-orders') ->addOption('sn', null, Option::VALUE_REQUIRED, '订单号,多个用英文逗号分隔') ->addOption('id', null, Option::VALUE_REQUIRED, '订单 id,多个用英文逗号分隔') ->addOption('skip-mall', null, Option::VALUE_NONE, '跳过商城 getedItem,仅调微信 confirm(商城已全部收货时用)') ->addOption('skip-wechat', null, Option::VALUE_NONE, '仅商城确认收货,不同步微信') ->addOption('dry-run', null, Option::VALUE_NONE, '只打印将执行的操作') ->setDescription('Confirm orders in mall and sync WeChat notify_confirm_receive'); } protected function execute(Input $input, Output $output) { if (!Bridge::isEnabled()) { $output->writeln('[FAIL] shopro_wechat_trade 未启用或 wechat_trade_enable=0'); return 1; } $orderIds = $this->resolveOrderIds($input); if (!$orderIds) { $output->writeln('请指定 --sn=订单号 或 --id=订单id'); return 1; } $skipMall = (bool) $input->getOption('skip-mall'); $skipWechat = (bool) $input->getOption('skip-wechat'); $dryRun = (bool) $input->getOption('dry-run'); $service = new WxShippingService(); $orderModel = new Order(); $ok = 0; $fail = 0; foreach ($orderIds as $orderId) { $order = Order::with('item')->where('id', $orderId)->find(); if (!$order) { $output->writeln("[SKIP] 订单不存在 id={$orderId}"); $fail++; continue; } $output->writeln('--- order#' . $order['id'] . ' ' . ($order['order_sn'] ?? '') . ' ---'); if (!Bridge::shouldHandleOrder($order)) { $output->writeln('[SKIP] 非小程序 wechat/huifu_wechat 或缺少 transaction_id'); $fail++; continue; } try { $this->prepareSelfetchOrder($orderModel, $order, $dryRun, $output); } catch (\Exception $e) { $output->writeln(' [FAIL] 自提准备: ' . $e->getMessage()); $fail++; continue; } if (!$skipMall) { try { $this->mallConfirmItems($orderModel, $order, $dryRun, $output); } catch (\Exception $e) { $output->writeln(' [FAIL] 商城确认: ' . $e->getMessage()); $fail++; continue; } } else { $output->writeln(' [info] skip-mall:不修改商城收货状态'); } if ($skipWechat) { $output->writeln(' [info] skip-wechat:跳过微信同步'); $ok++; continue; } if (!SyncLogger::hasSuccessfulShipping($orderId)) { $output->writeln(' [WARN] 无 shipping success,先尝试 upload_shipping_info'); if ($dryRun) { $output->writeln(' [dry-run] syncOrderShipping'); } else { try { $service->syncOrderShipping($orderId); $output->writeln(' [OK] shipping 同步成功'); } catch (\Exception $e) { $output->writeln(' [FAIL] shipping: ' . $e->getMessage()); $fail++; continue; } } } if (SyncLogger::hasSuccessfulConfirm($orderId)) { $output->writeln(' [OK] 微信 confirm 已是 success,跳过'); $ok++; continue; } if ($dryRun) { $output->writeln(' [dry-run] syncOrderConfirm'); $ok++; continue; } try { $service->syncOrderConfirm($orderId); $output->writeln(' [OK] 微信 confirm 同步成功'); $ok++; } catch (\Exception $e) { $output->writeln(' [FAIL] confirm: ' . $e->getMessage()); $fail++; } } $output->writeln(''); $output->writeln("完成:成功 {$ok},失败 {$fail}"); if (!$skipWechat && !$dryRun && $ok > 0) { $output->writeln('汇付分账(非 FREEZE 后):php think shopro_huifu:split-retry'); } return $fail > 0 ? 1 : 0; } protected function resolveOrderIds(Input $input) { $ids = []; $snOpt = trim((string) $input->getOption('sn')); if ($snOpt !== '') { foreach (preg_split('/[\s,,]+/', $snOpt) as $sn) { $sn = trim($sn); if ($sn === '') { continue; } $row = Db::name('shopro_order')->where('order_sn', $sn)->find(); if (!$row && strlen($sn) < 24) { $row = Db::name('shopro_order')->where('order_sn', 'like', $sn . '%')->order('id', 'desc')->find(); } if ($row) { $ids[] = intval($row['id']); } } } $idOpt = trim((string) $input->getOption('id')); if ($idOpt !== '') { foreach (preg_split('/[\s,,]+/', $idOpt) as $id) { $id = intval($id); if ($id > 0) { $ids[] = $id; } } } return array_values(array_unique($ids)); } /** * 自提/到店:未发货时先走 selfetchSendItem,便于 upload_shipping_info(logistics_type=4) */ protected function prepareSelfetchOrder(Order $orderModel, &$order, $dryRun, Output $output) { $needsShip = false; foreach ($order->item ?? [] as $item) { $itemArr = is_object($item) && method_exists($item, 'toArray') ? $item->toArray() : (array) $item; if (($itemArr['dispatch_type'] ?? '') !== 'selfetch') { continue; } if (intval($itemArr['dispatch_status'] ?? 0) < OrderItem::DISPATCH_STATUS_SENDED) { $needsShip = true; break; } } if (!$needsShip) { return; } if ($dryRun) { $output->writeln(' [dry-run] 自提发货 checkDispatchAndSend'); return; } $orderModel->checkDispatchAndSend($order, []); $order = Order::with('item')->where('id', $order['id'])->find(); if (!$order) { throw new \RuntimeException('自提发货后订单不存在'); } $output->writeln(' [OK] 自提已发货(待同步微信 upload_shipping_info)'); sleep(2); } protected function mallConfirmItems(Order $orderModel, $order, $dryRun, Output $output) { $items = $order->item ?? []; foreach ($items as $item) { $itemArr = is_object($item) && method_exists($item, 'toArray') ? $item->toArray() : (array) $item; $status = intval($itemArr['dispatch_status'] ?? 0); if ($status >= OrderItem::DISPATCH_STATUS_GETED) { continue; } if ($status < OrderItem::DISPATCH_STATUS_SENDED) { $output->writeln(' [FAIL] item#' . ($itemArr['id'] ?? '') . ' 未发货,请先发货再确认'); throw new \RuntimeException('存在未发货商品'); } if (in_array(intval($itemArr['refund_status'] ?? 0), [OrderItem::REFUND_STATUS_OK, OrderItem::REFUND_STATUS_FINISH], true)) { $output->writeln(' [SKIP] item#' . ($itemArr['id'] ?? '') . ' 已退款'); continue; } if ($dryRun) { $output->writeln(' [dry-run] getedItem item#' . ($itemArr['id'] ?? '')); continue; } Db::transaction(function () use ($orderModel, $order, $item) { $orderModel->getedItem($order, $item, ['oper_type' => 'admin']); }); $output->writeln(' [OK] 商城确认 item#' . ($itemArr['id'] ?? '')); } } }