update email template
This commit is contained in:
@@ -2,15 +2,14 @@
|
||||
|
||||
namespace App\Jobs\Mail\Customer;
|
||||
|
||||
use App\Mail\Customer\OrderCreated;
|
||||
use App\Models\Template;
|
||||
use App\Services\Email\EmailTemplateService;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class OrderCreatedJob implements ShouldQueue
|
||||
{
|
||||
@@ -18,17 +17,15 @@ class OrderCreatedJob implements ShouldQueue
|
||||
|
||||
public function __construct(public readonly array $data) {}
|
||||
|
||||
public function handle(): void
|
||||
public function handle(EmailTemplateService $emailTemplateService): void
|
||||
{
|
||||
$template = Template::where('name', 'Order Created')->where('status', 1)->first();
|
||||
|
||||
if (!$template) {
|
||||
Log::warning('OrderCreatedJob: Template "Order Created" not found or inactive.');
|
||||
if (!Template::where('scenario', 'order_created')->where('status', 1)->exists()) {
|
||||
Log::warning('OrderCreatedJob: Template "order_created" not found or inactive.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Mail::to($this->data['customer_email'])->send(new OrderCreated($template, $this->data));
|
||||
$emailTemplateService->send('order_created', $this->data['customer_email'], $this->data);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('OrderCreatedJob: Failed to send email. ' . $e->getMessage(), [
|
||||
'customer_email' => $this->data['customer_email'],
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\Mail;
|
||||
|
||||
use App\Mail\GenericTemplateMail;
|
||||
use App\Services\Email\EmailTemplateRenderer;
|
||||
use App\Services\Email\EmailTemplateService;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class SendTemplateEmailJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public int $tries = 3;
|
||||
public int $backoff = 30;
|
||||
|
||||
/**
|
||||
* @param array<int, string> $to
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $scenario,
|
||||
public readonly array $to,
|
||||
public readonly array $data = [],
|
||||
) {}
|
||||
|
||||
public function handle(EmailTemplateService $service, EmailTemplateRenderer $renderer): void
|
||||
{
|
||||
$template = $service->loadTemplate($this->scenario);
|
||||
$rendered = $renderer->render($template, $this->data);
|
||||
|
||||
Mail::to($this->to)->send(new GenericTemplateMail(
|
||||
$rendered['subject'],
|
||||
$rendered['html'],
|
||||
$template->mailConfig(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -57,15 +57,15 @@ class GetTransactionPayment implements ShouldQueue
|
||||
foreach ($history as $item) {
|
||||
if (empty($item->tracingType) && str_contains($item['description'], $this->message)) {
|
||||
$transaction = str_replace("\\BNK", "", $item['refNo']);
|
||||
$transCheck = Transaction::where('trans', $transaction)->first();
|
||||
if (!$transCheck) {
|
||||
Transaction::create(
|
||||
[
|
||||
'trans' => $transaction,
|
||||
'amount' => (int)$item['creditAmount'] ,
|
||||
'customer_id' => $this->user
|
||||
]
|
||||
);
|
||||
|
||||
// firstOrCreate is atomic against the unique(customer_id, trans, type) index —
|
||||
// a duplicate bank-statement poll (job retried, matched twice) cannot credit twice.
|
||||
$created = Transaction::firstOrCreate(
|
||||
['trans' => $transaction, 'customer_id' => $this->user],
|
||||
['amount' => (int) $item['creditAmount']]
|
||||
);
|
||||
|
||||
if ($created->wasRecentlyCreated) {
|
||||
Customer::find($this->user)->increment('credit', (int)$item['creditAmount']);
|
||||
Customer::find($this->user)->increment('total', (int)$item['creditAmount']);
|
||||
|
||||
|
||||
@@ -1,356 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\VirtualMachine;
|
||||
|
||||
use App\Enums\Invoice\InvoiceStatus;
|
||||
use App\Enums\Order\OrderStatus;
|
||||
use App\Http\Services\Telegram;
|
||||
use App\Http\Services\Invoice\InvoiceService;
|
||||
use App\Jobs\VirtualMachine\Create\CreateVirtualMachineJob;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Order;
|
||||
use App\Models\OrderItem;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ProcessOrderPaymentJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public Order $order;
|
||||
public int $retryCount = 0;
|
||||
public const MAX_RETRIES = 3;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(Order $order, int $retryCount = 0)
|
||||
{
|
||||
$this->order = $order;
|
||||
$this->retryCount = $retryCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(InvoiceService $invoiceService, Telegram $telegram): void
|
||||
{
|
||||
try {
|
||||
$user = $this->order->customer;
|
||||
$invoice = $this->order->invoice;
|
||||
|
||||
if (!$user || !$invoice) {
|
||||
Log::error("Không tìm thấy thông tin user hoặc invoice cho order #{$this->order->order_num}");
|
||||
return;
|
||||
}
|
||||
|
||||
// Kiểm tra credit
|
||||
if ($user->credit < $this->order->amount) {
|
||||
$errorMessage = "Không đủ credit để thanh toán đơn hàng #{$this->order->order_num}";
|
||||
Log::error($errorMessage, [
|
||||
'user_credit' => $user->credit,
|
||||
'order_amount' => $this->order->amount
|
||||
]);
|
||||
$telegram->SendErrorMessage($errorMessage, 'ProcessOrderPaymentJob - Insufficient Credit');
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* OPTIMIZED PAYMENT FLOW
|
||||
*
|
||||
* Thay đổi: TRỪ TIỀN TRƯỚC → TẠO VM SAU
|
||||
*
|
||||
* Lý do:
|
||||
* 1. User đã có credit trong hệ thống (đã nạp qua VNPay)
|
||||
* 2. Nếu tạo VM fail → refund credit dễ dàng
|
||||
* 3. Tránh tình huống: VM tạo ok nhưng chưa trừ tiền
|
||||
* 4. Đảm bảo tính nhất quán: Đã trừ tiền = đã có order valid
|
||||
*/
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
// STEP 1: Trừ tiền người dùng
|
||||
$user->decrement('credit', (int) $this->order->amount);
|
||||
|
||||
// STEP 2: Cập nhật trạng thái hóa đơn và đơn hàng
|
||||
$invoiceService->markAsPaid($invoice);
|
||||
$this->order->update(['status' => OrderStatus::ACTIVE->value]);
|
||||
|
||||
DB::commit();
|
||||
|
||||
Log::info("✓ Đã trừ credit và cập nhật order #{$this->order->order_num}");
|
||||
} catch (\Throwable $e) {
|
||||
DB::rollBack();
|
||||
Log::error("Lỗi khi trừ credit cho order #{$this->order->order_num}: " . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// STEP 3: Tạo VM cho các sản phẩm VPS (sau khi đã trừ tiền)
|
||||
$vmCreationResults = [];
|
||||
$allVMsCreated = true;
|
||||
$hasVMCreationAttempt = false;
|
||||
|
||||
foreach ($this->order->orderItems as $item) {
|
||||
try {
|
||||
// Chỉ tạo VM cho sản phẩm VPS (product_type = 4)
|
||||
if ($item->product_type == 4) {
|
||||
$hasVMCreationAttempt = true;
|
||||
|
||||
// Tạo VM và lấy kết quả
|
||||
$vmCreationResult = $this->createVirtualMachine($item);
|
||||
|
||||
$vmCreationResults[] = [
|
||||
'order_item_id' => $item->id,
|
||||
'product_name' => $item->getProductAttribute()?->name ?? 'Unknown',
|
||||
'success' => $vmCreationResult
|
||||
];
|
||||
|
||||
if (!$vmCreationResult) {
|
||||
$allVMsCreated = false;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Log::error("Lỗi khi tạo VM cho order item #{$item->id}: " . $e->getMessage());
|
||||
$vmCreationResults[] = [
|
||||
'order_item_id' => $item->id,
|
||||
'product_name' => $item->getProductAttribute()?->name ?? 'Unknown',
|
||||
'success' => false,
|
||||
'error' => $e->getMessage()
|
||||
];
|
||||
$allVMsCreated = false;
|
||||
}
|
||||
}
|
||||
|
||||
// STEP 4: Xử lý kết quả tạo VM
|
||||
if (!$hasVMCreationAttempt) {
|
||||
// Không có VPS nào trong order → OK, chỉ là Hosting/Domain
|
||||
$telegram->SendOrderPaidNotification($invoice);
|
||||
$this->sendSuccessNotification([]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($vmCreationResults)) {
|
||||
$errorMessage = "Không có kết quả tạo VM nào được trả về cho đơn hàng #{$this->order->order_num}";
|
||||
Log::error($errorMessage);
|
||||
$this->handleVMCreationFailure($user, $invoice, $vmCreationResults, $telegram);
|
||||
return;
|
||||
}
|
||||
|
||||
// Kiểm tra xem có ít nhất một VM thành công không
|
||||
$successfulVMs = array_filter($vmCreationResults, fn($vm) => $vm['success']);
|
||||
$failedVMs = array_filter($vmCreationResults, fn($vm) => !$vm['success']);
|
||||
|
||||
// Nếu tất cả VM được tạo thành công
|
||||
if ($allVMsCreated && count($successfulVMs) > 0) {
|
||||
// Gửi thông báo thành công
|
||||
$telegram->SendOrderPaidNotification($invoice);
|
||||
$this->sendSuccessNotification($vmCreationResults);
|
||||
|
||||
Log::info("✓ Hoàn tất order #{$this->order->order_num} - Tất cả VM đã được tạo");
|
||||
} else {
|
||||
// Có lỗi khi tạo VM
|
||||
$this->handleVMCreationFailure($user, $invoice, $vmCreationResults, $telegram);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Log::error("Lỗi nghiêm trọng trong ProcessOrderPaymentJob: " . $e->getMessage(), [
|
||||
'order_id' => $this->order->id,
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
$telegram->SendErrorMessage(
|
||||
"Lỗi nghiêm trọng khi xử lý order #{$this->order->order_num}: " . $e->getMessage(),
|
||||
'ProcessOrderPaymentJob - Critical Error'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Xử lý khi tạo VM thất bại
|
||||
*/
|
||||
private function handleVMCreationFailure($user, $invoice, $vmCreationResults, Telegram $telegram): void
|
||||
{
|
||||
$successfulVMs = array_filter($vmCreationResults, fn($vm) => $vm['success']);
|
||||
$failedVMs = array_filter($vmCreationResults, fn($vm) => !$vm['success']);
|
||||
|
||||
$errorDetails = [
|
||||
'order_id' => $this->order->id,
|
||||
'order_num' => $this->order->order_num,
|
||||
'vm_results' => $vmCreationResults,
|
||||
'total_vms' => count($vmCreationResults),
|
||||
'successful_vms' => count($successfulVMs),
|
||||
'failed_vms' => count($failedVMs),
|
||||
];
|
||||
|
||||
// Kiểm tra trường hợp đặc biệt: có VPS nhưng không có VM nào thành công
|
||||
if (count($vmCreationResults) > 0 && count($successfulVMs) === 0) {
|
||||
Log::error("Có sản phẩm VPS nhưng không có VM nào được tạo thành công", $errorDetails);
|
||||
$telegram->SendErrorMessage(array_merge($errorDetails, [
|
||||
'error' => 'Tất cả VM đều thất bại - không có VM nào thành công'
|
||||
]), 'ProcessOrderPaymentJob - All VMs Failed');
|
||||
|
||||
// REFUND CREDIT vì không tạo được VM nào
|
||||
try {
|
||||
DB::transaction(function () use ($user, $invoice) {
|
||||
$user->increment('credit', (int) $this->order->amount);
|
||||
$invoice->update(['status' => InvoiceStatus::UNPAID->value]);
|
||||
$this->order->update(['status' => OrderStatus::PENDING->value]);
|
||||
|
||||
Log::info("✓ Đã hoàn tiền cho order #{$this->order->order_num} do không tạo được VM");
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
Log::error("Lỗi khi hoàn tiền: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Thử lại nếu chưa vượt quá số lần thử và có VM fail
|
||||
if ($this->retryCount < self::MAX_RETRIES && count($failedVMs) > 0) {
|
||||
// Kiểm tra nếu lỗi là timeout, tăng delay time
|
||||
$isTimeoutError = $this->isTimeoutError($failedVMs);
|
||||
$delayMinutes = $isTimeoutError ? 10 : 5; // Timeout chờ 10 phút, lỗi khác 5 phút
|
||||
|
||||
Log::warning("Có lỗi khi tạo VM, thử lại lần " . ($this->retryCount + 1), array_merge($errorDetails, [
|
||||
'is_timeout_error' => $isTimeoutError,
|
||||
'delay_minutes' => $delayMinutes
|
||||
]));
|
||||
|
||||
// Thử lại với delay tùy theo loại lỗi
|
||||
self::dispatch($this->order, $this->retryCount + 1)
|
||||
->delay(now()->addMinutes($delayMinutes));
|
||||
} else if (count($failedVMs) > 0) {
|
||||
// Đã thử hết số lần, gửi thông báo lỗi
|
||||
Log::error("Đã thử tạo VM {$this->retryCount} lần nhưng vẫn thất bại", $errorDetails);
|
||||
|
||||
$telegram->SendErrorMessage(array_merge($errorDetails, [
|
||||
'error' => 'Không thể tạo VM sau nhiều lần thử',
|
||||
'retry_count' => $this->retryCount
|
||||
]), 'ProcessOrderPaymentJob - VM Creation Failed After Retries');
|
||||
|
||||
// Gửi thông báo lỗi cho khách hàng
|
||||
$this->sendFailureNotification($vmCreationResults);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tạo VM cho một order item
|
||||
*/
|
||||
private function createVirtualMachine(OrderItem $item): bool
|
||||
{
|
||||
try {
|
||||
$vCenter = app(\App\Http\Services\vCenter::class);
|
||||
$telegram = app(\App\Http\Services\Telegram::class);
|
||||
|
||||
$vmJob = new CreateVirtualMachineJob($item, $this->retryCount > 0);
|
||||
return $vmJob->handle($vCenter, $telegram);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
Log::error("Lỗi khi tạo VM cho order item #{$item->id}: " . $e->getMessage(), [
|
||||
'order_item_id' => $item->id,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send success notification to customer
|
||||
*/
|
||||
private function sendSuccessNotification(array $vmResults): void
|
||||
{
|
||||
try {
|
||||
$customer = $this->order->customer;
|
||||
|
||||
\App\Jobs\Mail\Customer\OrderPaymentSuccessJob::dispatch([
|
||||
'customer_email' => $customer->email,
|
||||
'customer_name' => trim($customer->firstname . ' ' . $customer->lastname),
|
||||
'order_id' => $this->order->id,
|
||||
'order_num' => $this->order->order_num,
|
||||
'amount' => $this->order->amount,
|
||||
'vm_results' => $vmResults,
|
||||
'addons_summary' => $this->resolveAddonsSummary(),
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Không thể gửi email thông báo thành công: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve addon display rows from all order items' addons_meta.
|
||||
* Returns array of [{name, qty, subtotal}] for email rendering.
|
||||
*
|
||||
* @return array<int, array{name:string, qty:int, subtotal:int}>
|
||||
*/
|
||||
private function resolveAddonsSummary(): array
|
||||
{
|
||||
$rows = [];
|
||||
$this->order->load('orderItems');
|
||||
|
||||
foreach ($this->order->orderItems as $item) {
|
||||
$meta = is_array($item->addons_meta) ? $item->addons_meta : [];
|
||||
foreach ($meta as $entry) {
|
||||
$addon = \App\Models\Addon::find($entry['addon_id'] ?? null);
|
||||
if (!$addon) {
|
||||
continue;
|
||||
}
|
||||
$qty = (int) ($entry['qty'] ?? 0);
|
||||
$rows[] = [
|
||||
'name' => $addon->name,
|
||||
'qty' => $qty,
|
||||
'subtotal' => (int) ($entry['subtotal'] ?? $addon->price * $qty),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the error is a timeout error
|
||||
*/
|
||||
private function isTimeoutError(array $failedVMs): bool
|
||||
{
|
||||
foreach ($failedVMs as $vmResult) {
|
||||
if (isset($vmResult['error'])) {
|
||||
$errorMessage = strtolower($vmResult['error']);
|
||||
|
||||
// Kiểm tra các dấu hiệu của timeout error
|
||||
if (strpos($errorMessage, 'timeout') !== false ||
|
||||
strpos($errorMessage, 'curl error 28') !== false ||
|
||||
strpos($errorMessage, 'operation timed out') !== false ||
|
||||
strpos($errorMessage, 'connection timeout') !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send failure notification to customer
|
||||
*/
|
||||
private function sendFailureNotification(array $vmResults): void
|
||||
{
|
||||
try {
|
||||
$customer = $this->order->customer;
|
||||
|
||||
// Gửi email thông báo lỗi
|
||||
\App\Jobs\Mail\Customer\VMCreationFailedJob::dispatch([
|
||||
'customer_email' => $customer->email,
|
||||
'customer_name' => trim($customer->firstname . ' ' . $customer->lastname),
|
||||
'product_name' => 'Multiple Products',
|
||||
'order_id' => $this->order->id,
|
||||
'order_num' => $this->order->order_num,
|
||||
'error_message' => 'Không thể khởi tạo máy chủ ảo sau nhiều lần thử. Vui lòng liên hệ admin để được hỗ trợ.',
|
||||
'vm_results' => $vmResults
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Không thể gửi email thông báo lỗi: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\Whmcs;
|
||||
|
||||
use App\Integrations\Whmcs\Transformers\ClientTransformer;
|
||||
use App\Integrations\Whmcs\Writers\ClientWriter;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class SyncClientJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* @param array $client Raw `tblclients` row from WHMCS.
|
||||
*/
|
||||
public function __construct(public array $client)
|
||||
{
|
||||
}
|
||||
|
||||
public function handle(ClientTransformer $transformer, ClientWriter $writer): void
|
||||
{
|
||||
$writer->write($transformer->transform($this->client));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\Whmcs;
|
||||
|
||||
use App\Integrations\Whmcs\Transformers\InvoiceTransformer;
|
||||
use App\Integrations\Whmcs\Writers\InvoiceWriter;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class SyncInvoiceJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* @param array $invoice Raw `tblinvoices` row from WHMCS.
|
||||
*/
|
||||
public function __construct(public array $invoice)
|
||||
{
|
||||
}
|
||||
|
||||
public function handle(InvoiceTransformer $transformer, InvoiceWriter $writer): void
|
||||
{
|
||||
$writer->write($transformer->transform($this->invoice));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\Whmcs;
|
||||
|
||||
use App\Integrations\Whmcs\Transformers\OrderTransformer;
|
||||
use App\Integrations\Whmcs\Writers\OrderWriter;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class SyncOrderJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* @param array $order Raw `tblorders` row from WHMCS.
|
||||
*/
|
||||
public function __construct(public array $order)
|
||||
{
|
||||
}
|
||||
|
||||
public function handle(OrderTransformer $transformer, OrderWriter $writer): void
|
||||
{
|
||||
$writer->write($transformer->transform($this->order));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user