Files
portal-api/app/Services/Api/Order/OrderService.php
T

201 lines
8.2 KiB
PHP

<?php
namespace App\Services\Api\Order;
use App\Repositories\Api\Service\ServiceRepository;
use App\Repositories\Api\Invoice\InvoiceRepository;
use App\Repositories\Api\Order\OrderRepository;
use Illuminate\Support\Facades\DB;
use App\Enums\Order\OrderStatus;
use Exception;
use App\Jobs\VirtualMachine\Create\CreateVMJobs;
use App\Models\Log as CustomerLog;
use App\Models\ModuleIpAddress;
use App\Models\ModuleVmware;
use App\Models\Os;
use Illuminate\Support\Facades\Log;
class OrderService
{
public function __construct(
protected OrderRepository $orderRepo,
protected ServiceRepository $productRepo,
protected InvoiceRepository $invoiceRepo
) {}
public function create(array $data, $customer)
{
// Check for existing pending orders
if ($this->orderRepo->hasPendingOrder($customer->id)) {
throw new Exception('Bạn có đơn hàng chưa thanh toán. Vui lòng thanh toán trước khi tạo đơn mới.');
}
$product = $this->productRepo->find($data['product_id']);
if (!$product) {
throw new Exception('Sản phẩm không tồn tại');
}
$price = $this->productRepo->getPricing($product->id, $data['billing_cycle']);
if (is_null($price)) {
throw new Exception('Không tìm thấy giá cho sản phẩm này');
}
// Tính tổng tiền bao gồm addons
$addonsPrice = 0;
if (!empty($data['addons'])) {
foreach ($data['addons'] as $addonData) {
$addon = \App\Models\Addon::find($addonData['addon_id']);
if ($addon) {
$addonsPrice += $addon->price * ($addonData['qty'] ?? 1);
}
}
}
$totalPrice = $price + $addonsPrice;
// Kiểm tra số dư tài khoản (cột total) trước khi tạo đơn
if ($customer->total < $totalPrice) {
throw new Exception('Số dư không đủ để đặt hàng. Vui lòng nạp thêm tiền. Số dư hiện tại: ' . number_format($customer->total) . ' VNĐ, cần: ' . number_format($totalPrice) . ' VNĐ');
}
return DB::transaction(function () use ($data, $customer, $product, $price, $totalPrice) {
$order = $this->orderRepo->create([
'order_num' => \App\Helpers\OrderHelper::generateOrderNumber(),
'customer_id' => $customer->id,
'amount' => $price,
'status' => OrderStatus::PENDING->value
]);
$this->orderRepo->createItem($order->id, [
'product_id' => $product->id,
'billing_cycle' => $data['billing_cycle'],
'server_name' => $data['server_name'] ?? null,
'os_id' => $data['os_id'] ?? null,
'price' => $price,
'addons' => $data['addons'] ?? []
]);
$invoice = $this->invoiceRepo->createFromOrder($order);
return [
'order' => $order,
'invoice' => $invoice
];
});
}
public function pay($order, string $method)
{
if (in_array($order->status, [OrderStatus::ACTIVE->value, OrderStatus::PROCESSING->value], true)) {
throw new Exception('Đơn hàng đã được thanh toán hoặc đang xử lý.');
}
return DB::transaction(function () use ($order, $method) {
$customer = $order->customer;
// Kiểm tra số dư và trừ tiền
if ($customer->total < $order->amount) {
throw new Exception('Số dư không đủ để thanh toán. Vui lòng nạp thêm tiền. Số dư hiện tại: ' . number_format($customer->total) . ' VNĐ, cần: ' . number_format($order->amount) . ' VNĐ');
}
$customer->total -= $order->amount;
$customer->save();
CustomerLog::create([
'agent' => 'api',
'client_ip' => request()?->ip(),
'customer_id' => $customer->id,
'description' => 'Thanh toán đơn hàng #' . $order->order_num
. ' thành công. Số tiền: ' . number_format((int) $order->amount) . ' VNĐ'
. '. Số dư sau thanh toán: ' . number_format((int) $customer->total) . ' VNĐ',
]);
// Cho phép thanh toán lại đơn đã hủy bằng cách đưa về pending trước khi process.
if ($order->status === OrderStatus::CANCELLED->value) {
$order->status = OrderStatus::PENDING->value;
$order->save();
}
$processedOrder = $this->orderRepo->processPayment($order, $method);
// Sau khi thanh toán, tạo module_vmware rồi dispatch CreateVMJobs.
foreach ($order->orderItems as $item) {
if (empty($item->product_id) || empty($item->os_id)) {
Log::warning('Skip VM job: product_id/os_id is missing', [
'order_id' => $order->id,
'order_item_id' => $item->id,
'product_id' => $item->product_id,
'os_id' => $item->os_id,
]);
continue;
}
try {
$os = Os::with('osGroup.host')->find($item->os_id);
if (!$os || !$os->osGroup || !$os->osGroup->module_host_id) {
Log::warning('Skip VM job: không tìm thấy OS/OsGroup/Host', [
'order_id' => $order->id,
'order_item_id' => $item->id,
'os_id' => $item->os_id,
]);
continue;
}
$ip = ModuleIpAddress::where('module_host_id', $os->osGroup->module_host_id)
->where('status', 0)
->lockForUpdate()
->first();
if (!$ip) {
Log::warning('Skip VM job: không còn IP khả dụng', [
'order_id' => $order->id,
'module_host_id' => $os->osGroup->module_host_id,
]);
continue;
}
$ip->update(['status' => 1, 'vm_name' => $item->server_name ?? 'VM-' . $order->id]);
$moduleVmware = ModuleVmware::create([
'name' => $item->server_name ?? 'VM-' . $order->id,
'module_ip_address_id' => $ip->id,
'port' => 22,
'order_id' => $order->id,
'first_payment' => $order->amount,
'next_payment' => $order->amount,
'next_due_date' => null,
'next_invoice' => 0,
'user' => 'Administrator',
'password' => '',
'hostname' => $item->server_name ?? 'vm-' . $order->id,
'vmware_id' => null,
'os_id' => $item->os_id,
'customer_id' => $order->customer_id,
'status' => 0,
]);
dispatch(new CreateVMJobs($moduleVmware->id))->delay(now()->addSeconds(10));
Log::info('Dispatched CreateVMJobs', [
'order_id' => $order->id,
'module_vmware_id' => $moduleVmware->id,
'ip' => $ip->ip,
]);
} catch (\Exception $e) {
Log::error('Lỗi khi tạo module_vmware hoặc dispatch job', [
'order_id' => $order->id,
'order_item_id' => $item->id,
'error' => $e->getMessage(),
]);
}
}
return $processedOrder;
});
}
}