订单支付记录
This commit is contained in:
@@ -8,7 +8,6 @@ use App\Services\BillDetailService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Modules\AnnoRoute\Attribute\GetRoute;
|
||||
use Modules\AnnoRoute\Attribute\PostRoute;
|
||||
use Modules\AnnoRoute\Attribute\RequestAttribute;
|
||||
|
||||
/**
|
||||
@@ -17,17 +16,21 @@ use Modules\AnnoRoute\Attribute\RequestAttribute;
|
||||
#[RequestAttribute('/mini', 'mini', authGuard: 'users')]
|
||||
class BillController extends BaseMiniController
|
||||
{
|
||||
/** 账单列表:当前门店强制过滤,?page=&pageSize= */
|
||||
/** 账单列表:当前门店强制过滤,?status= 按支付状态筛选(0未支付 1已支付) */
|
||||
#[GetRoute('/bill', authorize: true)]
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$user = $this->currentUser($request);
|
||||
$store = $this->ensureStoreBound($user);
|
||||
|
||||
$data = BillModel::query()
|
||||
$query = BillModel::query()
|
||||
->where('store_id', $store->id)
|
||||
->with('purchase:id,purchase_no,purchase_date')
|
||||
->orderBy('bill_date', 'desc')
|
||||
->with('purchase:id,purchase_no,purchase_date');
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', (int) $request->input('status'));
|
||||
}
|
||||
|
||||
$data = $query->orderBy('bill_date', 'desc')
|
||||
->orderBy('id', 'desc')
|
||||
->paginate((int) $request->input('pageSize', 10))
|
||||
->toArray();
|
||||
@@ -60,13 +63,4 @@ class BillController extends BaseMiniController
|
||||
'orders' => $orders,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在线支付(预留接口,本次不实现;当前为线下收款,由后台手动登记)
|
||||
*/
|
||||
#[PostRoute('/bill/{id}/pay', authorize: true, where: ['id' => '[0-9]+'])]
|
||||
public function pay(int $id, Request $request): JsonResponse
|
||||
{
|
||||
throw new RepositoryException('在线支付暂未开通,请线下付款后由商家登记收款');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Mini;
|
||||
|
||||
use App\Exceptions\RepositoryException;
|
||||
use App\Models\BillModel;
|
||||
use App\Models\PaymentModel;
|
||||
use App\Services\BillNumberService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Modules\AnnoRoute\Attribute\GetRoute;
|
||||
use Modules\AnnoRoute\Attribute\PostRoute;
|
||||
use Modules\AnnoRoute\Attribute\RequestAttribute;
|
||||
use Modules\SystemTool\Models\SysFileModel;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* 小程序门店支付(选择本店账单合并付款,提交汇款凭证,后台审核通过后账单置已支付)
|
||||
*/
|
||||
#[RequestAttribute('/mini', 'mini', authGuard: 'users')]
|
||||
class PaymentController extends BaseMiniController
|
||||
{
|
||||
/** 支付配置:收款码图片与对公汇款信息(付款页展示) */
|
||||
#[GetRoute('/payment/config', authorize: true)]
|
||||
public function config(): JsonResponse
|
||||
{
|
||||
// 配置值支持图片URL或文件ID(文件ID解析为预览地址)
|
||||
$resolve = static function (mixed $value): string {
|
||||
$value = trim((string) $value);
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
if (is_numeric($value)) {
|
||||
return (string) (SysFileModel::query()->find((int) $value)?->preview_url ?? '');
|
||||
}
|
||||
return $value;
|
||||
};
|
||||
|
||||
return $this->success([
|
||||
'wechat_qrcode' => $resolve(site_config('pay.wechat_qrcode', '')),
|
||||
'alipay_qrcode' => $resolve(site_config('pay.alipay_qrcode', '')),
|
||||
'bank_info' => (string) site_config('pay.bank_info', ''),
|
||||
]);
|
||||
}
|
||||
|
||||
/** 支付记录列表:当前门店强制过滤,?status=&page=&pageSize= */
|
||||
#[GetRoute('/payment', authorize: true)]
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$user = $this->currentUser($request);
|
||||
$store = $this->ensureStoreBound($user);
|
||||
|
||||
$query = PaymentModel::query()
|
||||
->where('store_id', $store->id)
|
||||
->withCount('bills');
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', (int) $request->input('status'));
|
||||
}
|
||||
|
||||
$data = $query->orderBy('id', 'desc')
|
||||
->paginate((int) $request->input('pageSize', 10))
|
||||
->toArray();
|
||||
|
||||
return $this->success($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发起付款:合并选择本店未支付账单,提交支付方式与汇款凭证(后台审核)
|
||||
* @throws Throwable
|
||||
*/
|
||||
#[PostRoute('/payment', authorize: true)]
|
||||
public function create(Request $request): JsonResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'bill_ids' => 'required|array|min:1',
|
||||
'bill_ids.*' => 'integer|distinct',
|
||||
'pay_method' => 'required|integer|in:1,2,3',
|
||||
'voucher_ids' => 'required|array|min:1',
|
||||
'voucher_ids.*' => 'integer|distinct',
|
||||
'remark' => 'nullable|string|max:255',
|
||||
], [
|
||||
'bill_ids.required' => '请选择要付款的账单',
|
||||
'bill_ids.min' => '请选择要付款的账单',
|
||||
'pay_method.required' => '请选择支付方式',
|
||||
'pay_method.in' => '支付方式不正确',
|
||||
'voucher_ids.required' => '请上传汇款凭证',
|
||||
'voucher_ids.min' => '请上传汇款凭证',
|
||||
'remark.max' => '备注超过最大长度',
|
||||
]);
|
||||
|
||||
$user = $this->currentUser($request);
|
||||
$store = $this->ensureStoreBound($user);
|
||||
$billIds = array_map('intval', $data['bill_ids']);
|
||||
|
||||
$payment = DB::transaction(function () use ($store, $user, $data, $billIds) {
|
||||
$bills = BillModel::query()
|
||||
->where('store_id', $store->id)
|
||||
->whereIn('id', $billIds)
|
||||
->lockForUpdate()
|
||||
->get();
|
||||
if ($bills->count() !== count($billIds)) {
|
||||
throw new RepositoryException('包含不属于本店的账单,请刷新后重试');
|
||||
}
|
||||
foreach ($bills as $bill) {
|
||||
if ($bill->status === BillModel::STATUS_PAID) {
|
||||
throw new RepositoryException('账单 ' . $bill->bill_no . ' 已支付,请刷新后重试');
|
||||
}
|
||||
if ((int) $bill->payment_id !== 0) {
|
||||
throw new RepositoryException('账单 ' . $bill->bill_no . ' 已在支付审核中,请勿重复提交');
|
||||
}
|
||||
}
|
||||
|
||||
$amount = $bills->reduce(
|
||||
static fn (string $carry, BillModel $bill): string => bcadd($carry, (string) $bill->total_amount, 2),
|
||||
'0'
|
||||
);
|
||||
|
||||
$payment = PaymentModel::create([
|
||||
'payment_no' => app(BillNumberService::class)->make('ZF'),
|
||||
'store_id' => $store->id,
|
||||
'user_id' => $user->id,
|
||||
'amount' => $amount,
|
||||
'pay_method' => (int) $data['pay_method'],
|
||||
'voucher_ids' => array_map('intval', $data['voucher_ids']),
|
||||
'status' => PaymentModel::STATUS_PENDING,
|
||||
'remark' => (string) ($data['remark'] ?? ''),
|
||||
]);
|
||||
|
||||
// 锁定账单到本支付记录(审核拒绝后释放,可重新付款)
|
||||
BillModel::query()->whereIn('id', $bills->pluck('id'))->update(['payment_id' => $payment->id]);
|
||||
|
||||
return $payment;
|
||||
});
|
||||
|
||||
return $this->success([
|
||||
'id' => $payment->id,
|
||||
'payment_no' => $payment->payment_no,
|
||||
'amount' => $payment->amount,
|
||||
], '付款申请已提交,请等待商家审核');
|
||||
}
|
||||
|
||||
/** 支付记录详情(校验归属;含合并账单与凭证图片) */
|
||||
#[GetRoute('/payment/{id}', authorize: true, where: ['id' => '[0-9]+'])]
|
||||
public function detail(int $id, Request $request): JsonResponse
|
||||
{
|
||||
$user = $this->currentUser($request);
|
||||
$store = $this->ensureStoreBound($user);
|
||||
|
||||
$payment = PaymentModel::query()
|
||||
->where('store_id', $store->id)
|
||||
->find($id);
|
||||
if ($payment === null) {
|
||||
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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Mini;
|
||||
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Modules\AnnoRoute\Attribute\PostRoute;
|
||||
use Modules\AnnoRoute\Attribute\RequestAttribute;
|
||||
use Modules\SystemTool\Services\SysFileService;
|
||||
|
||||
/**
|
||||
* 小程序文件上传(汇款凭证等图片)
|
||||
*/
|
||||
#[RequestAttribute('/mini', 'mini', authGuard: 'users')]
|
||||
class UploadController extends BaseMiniController
|
||||
{
|
||||
/** 上传图片,返回文件ID与预览地址(5MB 内) */
|
||||
#[PostRoute('/upload', authorize: true)]
|
||||
public function upload(Request $request): JsonResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'file' => 'required|image|max:5120',
|
||||
], [
|
||||
'file.required' => '请选择要上传的图片',
|
||||
'file.image' => '仅支持图片文件',
|
||||
'file.max' => '图片不能超过 5MB',
|
||||
]);
|
||||
|
||||
$user = $this->currentUser($request);
|
||||
// 分组 4=用户上传,渠道 20=APP用户
|
||||
$result = app(SysFileService::class)->upload($data['file'], 4, 20, $user->id);
|
||||
|
||||
return $this->success([
|
||||
'id' => $result['id'],
|
||||
'url' => $result['preview_url'] ?? '',
|
||||
], '上传成功');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Recon;
|
||||
|
||||
use App\Exceptions\RepositoryException;
|
||||
use App\Models\BillModel;
|
||||
use App\Models\PaymentModel;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Modules\AnnoRoute\Attribute\GetRoute;
|
||||
use Modules\AnnoRoute\Attribute\PutRoute;
|
||||
use Modules\AnnoRoute\Attribute\RequestAttribute;
|
||||
use Modules\Common\Http\Controllers\BaseController;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* 支付记录(小程序合并付款提交汇款凭证;后台审核:通过后关联账单批量置已支付,拒绝释放账单)
|
||||
*/
|
||||
#[RequestAttribute('/recon/payment', 'recon.payment')]
|
||||
class PaymentController extends BaseController
|
||||
{
|
||||
protected array $searchField = [
|
||||
'store_id' => '=',
|
||||
'status' => '=',
|
||||
'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', 'user:id,nickname', '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', 'user:id,nickname', '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 . ')',
|
||||
]);
|
||||
$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() . ' 张账单已置为已支付'
|
||||
: '已拒绝,账单已释放可重新付款'
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user