feat: nạp tiền thủ công mã 30 phút, gia hạn trừ số dư, đồng bộ domain từ Plesk
- Nạp tiền: mã thanh toán sống 30 phút (ManualDepositDraft), khôi phục/huỷ mã, lệnh dọn mã hết hạn; total_pending chỉ tính giao dịch đã nộp chứng từ, admin duyệt/từ chối/xoá giao dịch - Gia hạn dịch vụ: phát hành invoice gia hạn trên order gốc và trừ số dư ngay - Đồng bộ domain từ Plesk theo server, ServiceOrderRecorder tạo order/invoice cho dịch vụ admin dựng - Danh sách dịch vụ trả field theo loại (service_plan, dns_records_count), đếm dịch vụ theo loại Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
10af41c5cf
commit
c5115b52dd
@@ -12,6 +12,7 @@ use App\Models\Log;
|
||||
use App\Models\Setting;
|
||||
use App\Models\Transaction;
|
||||
use App\Services\ActivityLogger;
|
||||
use App\Support\ManualDepositDraft;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -77,28 +78,17 @@ class ManualDepositController extends Controller
|
||||
], 422);
|
||||
}
|
||||
|
||||
// Huỷ transaction prepare cũ (chưa có proof) nếu còn tồn tại
|
||||
$old = Transaction::where('customer_id', $user->id)
|
||||
->where('type', 'manual')
|
||||
->where('status', TransactionStatus::PENDING->value)
|
||||
->whereNull('proof_image')
|
||||
->latest()
|
||||
->first();
|
||||
|
||||
if ($old) {
|
||||
DB::transaction(function () use ($old, $user) {
|
||||
$user->total_pending = max(0, $user->total_pending - $old->amount);
|
||||
$user->save();
|
||||
$old->forceDelete();
|
||||
});
|
||||
}
|
||||
ManualDepositDraft::query()
|
||||
->where('customer_id', $user->id)
|
||||
->get()
|
||||
->each(fn (Transaction $draft) => ManualDepositDraft::discard($draft));
|
||||
|
||||
$uuid = Str::uuid()->toString();
|
||||
$publicCode = Transaction::newPublicCode('MAN');
|
||||
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
Transaction::create([
|
||||
$draft = Transaction::create([
|
||||
'uuid' => $uuid,
|
||||
'public_code' => $publicCode,
|
||||
'trans' => 'MAN' . time(),
|
||||
@@ -108,9 +98,6 @@ class ManualDepositController extends Controller
|
||||
'status' => TransactionStatus::PENDING->value,
|
||||
]);
|
||||
|
||||
$user->total_pending += $request->amount;
|
||||
$user->save();
|
||||
|
||||
DB::commit();
|
||||
} catch (\Exception) {
|
||||
DB::rollBack();
|
||||
@@ -126,13 +113,43 @@ class ManualDepositController extends Controller
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'data' => [
|
||||
'uuid' => $uuid,
|
||||
'public_code' => $publicCode,
|
||||
],
|
||||
'data' => $this->draftPayload($draft),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /v1/transaction/manual-deposit/{uuid}
|
||||
*/
|
||||
public function show(Request $request, string $uuid): JsonResponse
|
||||
{
|
||||
$draft = ManualDepositDraft::query()
|
||||
->where('uuid', $uuid)
|
||||
->where('customer_id', $request->user()->id)
|
||||
->first();
|
||||
|
||||
if (! $draft) {
|
||||
return response()->json(['error' => true, 'message' => 'Mã thanh toán không tồn tại'], 404);
|
||||
}
|
||||
|
||||
if (ManualDepositDraft::isExpired($draft)) {
|
||||
ManualDepositDraft::discard($draft);
|
||||
|
||||
return response()->json(['error' => true, 'message' => 'Mã thanh toán đã hết hạn, vui lòng tạo mã mới'], 410);
|
||||
}
|
||||
|
||||
return response()->json(['error' => false, 'data' => $this->draftPayload($draft)]);
|
||||
}
|
||||
|
||||
private function draftPayload(Transaction $draft): array
|
||||
{
|
||||
return [
|
||||
'uuid' => $draft->uuid,
|
||||
'public_code' => $draft->public_code,
|
||||
'amount' => (int) $draft->amount,
|
||||
'expires_at' => ManualDepositDraft::expiresAt($draft)->toISOString(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /v1/transaction/manual-deposit/{uuid}/cancel
|
||||
* Huỷ transaction đang prepare (chưa có proof, chưa submit).
|
||||
@@ -142,21 +159,15 @@ class ManualDepositController extends Controller
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
$transaction = Transaction::where('uuid', $uuid)
|
||||
$transaction = ManualDepositDraft::query()
|
||||
->where('uuid', $uuid)
|
||||
->where('customer_id', $user->id)
|
||||
->where('type', 'manual')
|
||||
->where('status', TransactionStatus::PENDING->value)
|
||||
->whereNull('proof_image')
|
||||
->firstOrFail();
|
||||
|
||||
$amount = $transaction->amount;
|
||||
$publicCode = $transaction->public_code;
|
||||
|
||||
DB::transaction(function () use ($transaction, $user) {
|
||||
$user->total_pending = max(0, $user->total_pending - $transaction->amount);
|
||||
$user->save();
|
||||
$transaction->forceDelete();
|
||||
});
|
||||
ManualDepositDraft::discard($transaction);
|
||||
|
||||
$this->activityLogger->log(
|
||||
event: 'deposit_cancel',
|
||||
@@ -194,22 +205,30 @@ class ManualDepositController extends Controller
|
||||
], 422);
|
||||
}
|
||||
|
||||
$transaction = Transaction::where('uuid', $request->uuid)
|
||||
->where('customer_id', $user->id)
|
||||
->where('type', 'manual')
|
||||
->where('status', TransactionStatus::PENDING->value)
|
||||
->whereNull('proof_image')
|
||||
->first();
|
||||
$transaction = DB::transaction(function () use ($request, $user) {
|
||||
$draft = ManualDepositDraft::query()
|
||||
->where('uuid', $request->uuid)
|
||||
->where('customer_id', $user->id)
|
||||
->lockForUpdate()
|
||||
->first();
|
||||
|
||||
if (! $draft || ManualDepositDraft::isExpired($draft)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$draft->update(['proof_image' => $request->proof_image]);
|
||||
$user->newQuery()->whereKey($user->getKey())->increment('total_pending', $draft->amount);
|
||||
|
||||
return $draft;
|
||||
});
|
||||
|
||||
if (!$transaction) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => 'Giao dịch không hợp lệ hoặc đã được xử lý',
|
||||
'message' => 'Mã thanh toán đã hết hạn hoặc đã được xử lý, vui lòng tạo mã mới',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$transaction->update(['proof_image' => $request->proof_image]);
|
||||
|
||||
try {
|
||||
(new Telegram())->SendManualDepositPendingApproval($user, $transaction);
|
||||
$this->sendManualDepositAdminEmail($user, $transaction, $request);
|
||||
|
||||
@@ -266,12 +266,11 @@ class TransactionController extends Controller
|
||||
'type' => 'deposit',
|
||||
'public_code' => $t->public_code,
|
||||
'trans' => $t->trans,
|
||||
'description' => 'Nạp tiền vào tài khoản',
|
||||
'description' => $t->typeLabel(),
|
||||
'amount' => (float) $t->amount,
|
||||
'status' => $t->status,
|
||||
'status_label' => TransactionStatus::from((int) $t->status)->label(),
|
||||
'reference_id' => null,
|
||||
'reference_num'=> null,
|
||||
'detail_key' => $t->uuid,
|
||||
'created_at' => $t->created_at?->toISOString(),
|
||||
]);
|
||||
|
||||
@@ -287,8 +286,7 @@ class TransactionController extends Controller
|
||||
'amount' => (float) $inv->total,
|
||||
'status' => $inv->status,
|
||||
'status_label' => \App\Enums\Invoice\InvoiceStatus::tryFrom((int) $inv->status)?->label() ?? '-',
|
||||
'reference_id' => $inv->order?->id,
|
||||
'reference_num'=> $inv->order?->order_num,
|
||||
'detail_key' => $inv->order?->order_num,
|
||||
'created_at' => $inv->created_at?->toISOString(),
|
||||
]);
|
||||
|
||||
@@ -303,6 +301,37 @@ class TransactionController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function getClientTransactionDetail(Request $request, string $uuid): JsonResponse
|
||||
{
|
||||
$transaction = Transaction::query()
|
||||
->where('uuid', $uuid)
|
||||
->where('customer_id', $request->user()->id)
|
||||
->first();
|
||||
|
||||
if (! $transaction) {
|
||||
return response()->json(['error' => true, 'message' => 'Giao dịch không tồn tại'], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'data' => [
|
||||
'uuid' => $transaction->uuid,
|
||||
'public_code' => $transaction->public_code,
|
||||
'trans' => $transaction->trans,
|
||||
'type' => $transaction->type,
|
||||
'type_label' => $transaction->typeLabel(),
|
||||
'direction' => $transaction->direction,
|
||||
'amount' => (int) $transaction->amount,
|
||||
'received_amount' => $transaction->received_amount !== null ? (int) $transaction->received_amount : null,
|
||||
'status' => (int) $transaction->status,
|
||||
'status_label' => TransactionStatus::from((int) $transaction->status)->label(),
|
||||
'has_proof' => $transaction->proof_image !== null,
|
||||
'created_at' => $transaction->created_at?->toISOString(),
|
||||
'updated_at' => $transaction->updated_at?->toISOString(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function getByPublicCode(Request $request)
|
||||
{
|
||||
$code = $request->query('code');
|
||||
|
||||
Reference in New Issue
Block a user