feat: add domain registration form and email notifications

- Create a new Blade view for domain registration (`registrant-tochuc.blade.php`) to capture organization and manager details.
- Implement email templates for new domain orders for both admin (`domain-order-new.blade.php`) and customers (`domain-order-received.blade.php`).
- Add CSV exports for client and domain data.
- Introduce tests for customer mass assignment, password reset, login throttling, and permission gating.
- Ensure service renewals use current pricing and validate ownership for virtual machines.
- Validate mass assignment behavior in service model.
This commit is contained in:
baodev0
2026-08-22 17:30:55 +07:00
parent eec2c5906e
commit c377c7e195
58 changed files with 3063 additions and 418 deletions
+6 -24
View File
@@ -13,11 +13,11 @@ use App\Models\DnsDomain;
use App\Models\Invoice;
use App\Models\InvoiceItem;
use App\Models\Log as CustomerActivityLog;
use App\Models\DomainPricing;
use App\Models\Order;
use App\Models\OrderItem;
use App\Models\Product;
use App\Models\Service;
use App\Models\Setting;
use App\Models\Transaction;
use App\Repositories\Api\Domain\DomainOrderRepository;
use Illuminate\Support\Facades\DB;
@@ -53,6 +53,10 @@ class DomainOrderService
return DB::transaction(function () use ($customer, $product, $domainName, $price, $months, $years) {
$lockedCustomer = Customer::lockForUpdate()->find($customer->id);
if (!$lockedCustomer->front_identity_number || !$lockedCustomer->back_identity_number) {
throw new \InvalidArgumentException('Vui lòng bổ sung ảnh CMND/CCCD (2 mặt) trước khi đặt mua tên miền.');
}
if ((int) $lockedCustomer->total < $price) {
throw new \InvalidArgumentException('Số dư không đủ để đặt mua tên miền. Vui lòng nạp thêm tiền vào tài khoản.');
}
@@ -367,29 +371,7 @@ class DomainOrderService
*/
private function findDomainPricing(string $domain): array
{
$setting = Setting::where('key', 'domain_pricing')->first();
if (!$setting || empty($setting->value)) {
throw new \InvalidArgumentException('Không tìm thấy cấu hình giá tên miền.');
}
$pricingData = is_string($setting->value)
? json_decode($setting->value, true)
: (array) $setting->value;
$domains = $pricingData['domains'] ?? [];
// Longest-match: sort TLDs by length descending so ".com.vn" beats ".vn"
usort($domains, fn ($a, $b) => strlen($b['tld']) - strlen($a['tld']));
foreach ($domains as $entry) {
$tld = ltrim($entry['tld'], '.');
if (str_ends_with($domain, '.' . $tld)) {
return $entry;
}
}
throw new \InvalidArgumentException("Tên miền không được hỗ trợ. Vui lòng chọn đuôi tên miền có trong danh sách.");
return DomainPricing::findByDomain($domain)->toDomainArray();
}
/**