feat(billing): renewals, coupons, VAT numbering, billing cycles and invoice payment tracking
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
ea9a53f742
commit
25f23d541e
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\Admin;
|
||||
|
||||
use App\Enums\Product\ProductTypeEnums;
|
||||
use App\Models\Coupon;
|
||||
use App\Support\BillingCycleCatalog;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class CouponRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
$id = $this->route('id');
|
||||
$isPercent = $this->input('type') === Coupon::TYPE_PERCENT;
|
||||
|
||||
return [
|
||||
'code' => ['required', 'string', 'max:50', 'regex:/^[A-Za-z0-9_-]+$/', Rule::unique('coupons', 'code')->ignore($id)],
|
||||
'name' => 'nullable|string|max:150',
|
||||
'type' => ['required', Rule::in(Coupon::TYPES)],
|
||||
'value' => ['required', 'integer', 'min:1', $isPercent ? 'max:99' : 'max:1000000000'],
|
||||
'max_discount' => 'nullable|integer|min:1',
|
||||
'min_order' => 'nullable|integer|min:0',
|
||||
'applies_to' => ['required', Rule::in(Coupon::APPLIES_TO)],
|
||||
'product_types' => 'nullable|array',
|
||||
'product_types.*' => ['string', Rule::in(array_column(ProductTypeEnums::cases(), 'value'))],
|
||||
'product_ids' => 'nullable|array',
|
||||
'product_ids.*' => ['integer', Rule::exists('products', 'id')],
|
||||
'billing_cycles' => 'nullable|array',
|
||||
'billing_cycles.*' => ['string', Rule::in([...BillingCycleCatalog::codes(), 'onetime'])],
|
||||
'usage_limit' => 'nullable|integer|min:1',
|
||||
'per_customer_limit' => 'nullable|integer|min:1',
|
||||
'starts_at' => 'nullable|date',
|
||||
'ends_at' => 'nullable|date|after_or_equal:starts_at',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'code.unique' => 'Mã giảm giá đã tồn tại.',
|
||||
'code.regex' => 'Mã chỉ gồm chữ, số, gạch ngang và gạch dưới.',
|
||||
'value.max' => 'Giảm theo % tối đa 99.',
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return ['ends_at' => 'ngày kết thúc'];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class BillingProfileRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'invoice_company' => 'nullable|string|max:255|required_with:tax_code',
|
||||
'tax_code' => ['nullable', 'string', 'regex:/^\d{10}(-\d{3})?$/'],
|
||||
'invoice_address' => 'nullable|string|max:500',
|
||||
'invoice_email' => 'nullable|email|max:255',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'tax_code.regex' => 'Mã số thuế phải gồm 10 số hoặc 13 số (dạng 0123456789-001)',
|
||||
'invoice_company.required_with' => 'Vui lòng nhập tên đơn vị khi có mã số thuế',
|
||||
'invoice_email.email' => 'Email nhận hóa đơn không hợp lệ',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\Client;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class InvoiceLookupRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'invoice_number' => 'required|string|max:50',
|
||||
'lookup_code' => 'required|string|max:16',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\Order;
|
||||
|
||||
use App\Support\BillingCycleCatalog;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class CouponPreviewRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'coupon_code' => ['required', 'string', 'max:50'],
|
||||
'product_id' => ['required', Rule::exists('products', 'uuid')->whereNull('deleted_at')],
|
||||
'billing_cycle' => ['required', Rule::in([...BillingCycleCatalog::codes(), 'onetime'])],
|
||||
'addons' => 'array',
|
||||
'addons.*.addon_id' => 'nullable|exists:product_addon,id',
|
||||
'addons.*.qty' => 'nullable|integer|min:0|max:1000000',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ namespace App\Http\Requests\Api\Order;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
use App\Enums\Product\ProductBillingCycle;
|
||||
use App\Support\BillingCycleCatalog;
|
||||
use App\Enums\Product\ProductTypeEnums;
|
||||
use App\Models\Product;
|
||||
|
||||
@@ -35,11 +35,12 @@ class StoreOrderRequest extends FormRequest
|
||||
{
|
||||
return [
|
||||
'product_id' => ['required', Rule::exists('products', 'uuid')->whereNull('deleted_at')],
|
||||
'billing_cycle'=> ['required', Rule::enum(ProductBillingCycle::class)],
|
||||
'billing_cycle'=> ['required', Rule::in([...BillingCycleCatalog::codes(), 'onetime'])],
|
||||
'server_name' => $this->isHostingOrder()
|
||||
? ['required', 'string', 'max:253', 'regex:'.self::DOMAIN_PATTERN]
|
||||
: 'nullable|string|max:255',
|
||||
'os_id' => 'nullable|integer',
|
||||
'coupon_code' => 'nullable|string|max:50',
|
||||
'addons' => 'array',
|
||||
'addons.*.addon_id' => 'nullable|exists:product_addon,id',
|
||||
'addons.*.qty' => 'nullable|integer|min:0|max:1000000',
|
||||
|
||||
Reference in New Issue
Block a user