'=', 'status' => '=', 'pay_type' => '=', 'pay_method' => '=', 'payment_no' => 'like', ]; /** 支付记录列表(待审核优先) */ #[GetRoute(authorize: 'query')] public function query(Request $request): JsonResponse { $params = $request->all(); $pageSize = $params['pageSize'] ?? 10; $data = $this->buildSearch($params, PaymentModel::query() ->with(['store:id,name', 'auditor:id,nickname']) ->withCount('bills')) ->orderBy('status') ->orderBy('id', 'desc') ->paginate($pageSize) ->toArray(); return $this->success($data); } /** 支付记录详情:支付信息 + 凭证图片 + 合并付款的账单 */ #[GetRoute(route: '/{id}', authorize: 'query', where: ['id' => '[0-9]+'])] public function detail(int $id): JsonResponse { $payment = PaymentModel::with(['store:id,name,contact,phone', 'auditor:id,nickname'])->find($id); if (empty($payment)) { throw new RepositoryException('支付记录不存在'); } $bills = $payment->bills() ->orderBy('id') ->get(['id', 'bill_no', 'bill_date', 'product_amount', 'delivery_fee', 'added_amount', 'total_amount', 'status']) ->toArray(); $data = $payment->toArray(); $data['voucher_urls'] = $payment->voucherUrls(); return $this->success([ 'payment' => $data, 'bills' => $bills, ]); } /** * 审核支付记录:通过 → 关联账单全部置已支付;拒绝 → 释放账单(可重新发起付款) * @throws Throwable */ #[PutRoute(route: '/{id}/audit', authorize: 'audit', where: ['id' => '[0-9]+'])] public function audit(int $id, Request $request): JsonResponse { $data = $request->validate([ 'result' => 'required|string|in:pass,reject', 'audit_remark' => 'nullable|string|max:255|required_if:result,reject', ], [ 'result.required' => '请选择审核结果', 'result.in' => '审核结果不正确', 'audit_remark.required_if' => '拒绝时请填写原因', 'audit_remark.max' => '审核备注超过最大长度', ]); return DB::transaction(function () use ($id, $data, $request) { $payment = PaymentModel::query()->lockForUpdate()->find($id); if (empty($payment)) { throw new RepositoryException('支付记录不存在'); } if ($payment->status !== PaymentModel::STATUS_PENDING) { throw new RepositoryException('该支付记录已审核,请勿重复操作'); } $bills = $payment->bills()->lockForUpdate()->get(); $auditorId = (int) $request->user()->id; $now = now(); if ($data['result'] === 'pass') { // 任一账单已通过其他方式收款(如线下登记)则整批中止,避免重复收款 $paid = $bills->where('status', BillModel::STATUS_PAID); if ($paid->isNotEmpty()) { throw new RepositoryException( '账单 ' . $paid->pluck('bill_no')->implode('、') . ' 已收款,请核实后再审核' ); } $methodName = PaymentModel::METHOD_NAMES[$payment->pay_method] ?? '线上支付'; BillModel::query()->whereIn('id', $bills->pluck('id'))->update([ 'status' => BillModel::STATUS_PAID, 'paid_at' => $now, 'paid_operator_id' => $auditorId, 'pay_remark' => $methodName . '(支付单号 ' . $payment->payment_no . ')', ]); // 按门店累加总采购金额(只统计商品金额,不含配送费/附加金额) foreach ($bills->groupBy('store_id')->sortKeys() as $storeId => $storeBills) { $amount = '0'; foreach ($storeBills as $storeBill) { $amount = bcadd($amount, (string) $storeBill->product_amount, 2); } $store = StoreModel::query()->lockForUpdate()->find((int) $storeId); if ($store !== null) { $store->total_purchase_amount = bcadd((string) $store->total_purchase_amount, $amount, 2); $store->save(); } } $payment->status = PaymentModel::STATUS_APPROVED; } else { // 拒绝:释放账单,门店可重新发起付款 BillModel::query()->whereIn('id', $bills->pluck('id'))->update(['payment_id' => 0]); $payment->status = PaymentModel::STATUS_REJECTED; } $payment->audited_at = $now; $payment->auditor_id = $auditorId; $payment->audit_remark = (string) ($data['audit_remark'] ?? ''); $payment->save(); return $this->success( [], $payment->status === PaymentModel::STATUS_APPROVED ? '审核通过,' . $bills->count() . ' 张账单已置为已支付' : '已拒绝,账单已释放可重新付款' ); }); } }