adminOnly(); } /** * AO1 订单列表 * GET /api/v1/admin/orders 入参 {type?,page,size} * → 分页 {list:[{id,orderNo,type,title,amount,payType,status,userId,userName,createdAt}],page,size,total,hasMore} */ public function index() { $type = trim((string)$this->input('type', '')); // 闭包重建查询:TP5.0 的 count() 会清空 Query::options,不能复用同一对象 list($rows, $total, $page, $size) = $this->pageQuery(function ($forCount) use ($type) { $q = Db::name('fc_order')->alias('o') ->join('user u', 'u.id = o.user_id', 'LEFT'); if ($type !== '') { $q->where('o.type', $type); } if ($forCount) { return $q; } return $q->field('o.*, u.nickname AS nickname') ->order('o.createtime desc'); }); $list = []; foreach ($rows as $row) { $list[] = [ 'id' => (int)$row['id'], 'orderNo' => (string)$row['order_no'], 'type' => (string)$row['type'], 'title' => (string)$row['title'], 'amount' => (float)$row['amount'], 'payType' => (string)$row['pay_type'], 'status' => (string)$row['status'], 'userId' => (int)$row['user_id'], 'userName' => (string)$row['nickname'], 'createdAt' => $this->iso($row['createtime']), ]; } $this->ok($this->paginate($list, $total, $page, $size)); } /** * AO2 订单退款 * POST /api/v1/admin/order/:id/refund 入参 {amount,reason?} → {id,refunded} * 规则 BR-10:退款金额需大于 0 */ public function refund() { $id = (int)$this->request->param('id', 0); $amount = $this->input('amount', null); $reason = trim((string)$this->input('reason', '')); $amount = ($amount === null || $amount === '') ? null : (float)$amount; try { $res = \app\common\library\OrderRefund::refund($id, $amount, $reason); } catch (\Throwable $e) { $this->fail($e->getMessage()); } $this->adminLog('订单退款 #' . $id . ' 金额' . $res['amount'] . ' 退款单号' . $res['refundNo']); $this->ok([ 'id' => $id, 'refunded' => true, 'refundNo' => $res['refundNo'], 'amount' => $res['amount'], ]); } /** * AO3 订单补单 * POST /api/v1/admin/order/supplement 入参 {orderNo} → {orderNo,supplemented} * 幂等:已支付直接返回 */ public function supplement() { $orderNo = trim((string)$this->input('orderNo', '')); if ($orderNo === '') { $this->fail('订单号不能为空'); } $order = Db::name('fc_order')->where('order_no', $orderNo)->find(); if (!$order) { $this->fail('订单不存在', 404); } if ((string)$order['status'] === 'paid') { $this->ok(['orderNo' => (string)$orderNo, 'supplemented' => false], '订单已支付,无需补单'); } if ((string)$order['status'] !== 'pending') { $this->fail('订单状态不支持补单(当前:' . $order['status'] . ')'); } $now = time(); $handled = false; try { Db::transaction(function () use ($order, $now, &$handled) { $locked = Db::name('fc_order')->where('id', (int)$order['id'])->lock(true)->find(); if (!$locked || (string)$locked['status'] !== 'pending') { return; } $affected = Db::name('fc_order') ->where('id', (int)$order['id']) ->where('status', 'pending') ->update(['status' => 'paid', 'paytime' => $now, 'updatetime' => $now]); if (!$affected) { return; } $locked['status'] = 'paid'; $locked['paytime'] = $now; $handled = true; // 补单必须真正发货:会员发放 / 余额入账 / 付费查看解锁,不能只改状态 \app\common\library\OrderFulfill::handle($locked); }); } catch (\Throwable $e) { \think\Log::record('补单失败:订单 ' . $orderNo . ',' . $e->getMessage(), 'error'); $this->fail('补单失败:' . $e->getMessage()); } $this->adminLog('订单补单 #' . $orderNo . ($handled ? '' : '(幂等命中)')); $this->ok(['orderNo' => (string)$orderNo, 'supplemented' => $handled]); } }