feat(services): partial/unpaid payment on admin create, payment history per invoice, zip attachments
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
02daefe4c2
commit
fe68171aa7
@@ -204,38 +204,49 @@ class CustomerServiceProvisioningService
|
||||
$billingCycle = $data['billing_cycle'] ?? 'monthly';
|
||||
$months = OrderHelper::cycleToMonths($billingCycle);
|
||||
|
||||
// Số tiền đã thanh toán: admin nhập tay để khớp với thực tế (có thể lệch giá niêm yết do khuyến mãi/thoả thuận)
|
||||
$listPrice = $this->orderRecorder->listPrice($product, $billingCycle, $data['server_name'] ?? null);
|
||||
abort_if(
|
||||
!isset($data['amount_paid']) && $listPrice === null && $product->product_type === 'domain',
|
||||
!isset($data['price']) && $listPrice === null && $product->product_type === 'domain',
|
||||
422,
|
||||
'Tên miền không có giá trong bảng giá tên miền, vui lòng nhập giá.',
|
||||
);
|
||||
$basePrice = isset($data['amount_paid']) ? (int) $data['amount_paid'] : (int) $listPrice;
|
||||
$basePrice = (int) round((float) ($data['price'] ?? $listPrice));
|
||||
|
||||
$paymentMethod = $data['payment_method'];
|
||||
|
||||
// Đối với balance: kiểm tra số dư trước khi vào transaction
|
||||
$paymentStatus = $data['payment_status'] ?? 'paid';
|
||||
$paidAmount = match ($paymentStatus) {
|
||||
'unpaid' => 0,
|
||||
'partial' => (int) round((float) $data['amount_paid']),
|
||||
default => $basePrice,
|
||||
};
|
||||
abort_if(
|
||||
$paymentMethod === 'balance' && (int) $customer->total < $basePrice,
|
||||
$paymentStatus === 'partial' && $paidAmount >= $basePrice,
|
||||
422,
|
||||
'Số tiền trả trước phải nhỏ hơn giá dịch vụ. Khách trả đủ thì chọn "Đã thanh toán".',
|
||||
);
|
||||
|
||||
$paymentMethod = $paymentStatus === 'unpaid' ? null : $data['payment_method'];
|
||||
|
||||
abort_if(
|
||||
$paymentMethod === 'balance' && (int) $customer->total < $paidAmount,
|
||||
422,
|
||||
sprintf(
|
||||
'Số dư khách không đủ. Hiện có: %s VNĐ, cần: %s VNĐ.',
|
||||
number_format((int) $customer->total),
|
||||
number_format($basePrice),
|
||||
number_format($paidAmount),
|
||||
),
|
||||
);
|
||||
|
||||
return DB::transaction(function () use ($customer, $product, $data, $billingCycle, $months, $basePrice, $paymentMethod, $clientIp) {
|
||||
return DB::transaction(function () use ($customer, $product, $data, $billingCycle, $months, $basePrice, $paidAmount, $paymentStatus, $paymentMethod, $clientIp) {
|
||||
['order' => $order, 'orderItem' => $orderItem, 'invoice' => $invoice] = $this->orderRecorder->create(
|
||||
$customer->id,
|
||||
$product,
|
||||
$billingCycle,
|
||||
$basePrice,
|
||||
$data['server_name'] ?? null,
|
||||
paid: true,
|
||||
paid: $paymentStatus === 'paid',
|
||||
paymentMethod: $paymentMethod,
|
||||
clientIp: $clientIp,
|
||||
amountPaid: $paidAmount,
|
||||
);
|
||||
|
||||
// Ngày bắt đầu/hết hạn dùng chung cho cả module_vmware (next_due_date/next_invoice) và services.
|
||||
@@ -246,28 +257,32 @@ class CustomerServiceProvisioningService
|
||||
// resolve VM qua service->orderItem->order->vmWare, không qua services.vm_id.
|
||||
$vmId = $this->createModuleVmwareIfNeeded($data, $customer, $order, $basePrice, $expiresAt);
|
||||
|
||||
// 4) Payment processing
|
||||
if ($paymentMethod === 'balance') {
|
||||
$customer->total = (int) $customer->total - $basePrice;
|
||||
$customer->total = (int) $customer->total - $paidAmount;
|
||||
$customer->save();
|
||||
}
|
||||
|
||||
$proofPaths = $data['_proof_paths'] ?? [];
|
||||
|
||||
Transaction::create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'public_code' => Transaction::newPublicCode('SVC'),
|
||||
'customer_id' => $customer->id,
|
||||
'user_id' => Auth::id(),
|
||||
'trans' => $order->order_num,
|
||||
'amount' => $basePrice,
|
||||
'type' => $paymentMethod === 'transfer' ? 'transfer' : 'balance',
|
||||
'status' => 1, // paid
|
||||
'note' => $paymentMethod === 'transfer'
|
||||
if ($paidAmount > 0) {
|
||||
$proofPaths = $data['_proof_paths'] ?? [];
|
||||
$paymentNote = $paymentMethod === 'transfer'
|
||||
? 'Admin tạo dịch vụ - thanh toán chuyển khoản'
|
||||
: 'Admin tạo dịch vụ - trừ số dư khách hàng',
|
||||
'proof_image' => !empty($proofPaths) ? json_encode($proofPaths) : null,
|
||||
]);
|
||||
: 'Admin tạo dịch vụ - trừ số dư khách hàng';
|
||||
|
||||
Transaction::create([
|
||||
'uuid' => (string) Str::uuid(),
|
||||
'public_code' => Transaction::newPublicCode('SVC'),
|
||||
'customer_id' => $customer->id,
|
||||
'user_id' => Auth::id(),
|
||||
'trans' => $order->order_num,
|
||||
'amount' => $paidAmount,
|
||||
'type' => $paymentMethod === 'transfer' ? 'transfer' : 'balance',
|
||||
'status' => 1,
|
||||
'note' => $paymentStatus === 'partial'
|
||||
? sprintf('%s (trả trước %s/%s VNĐ)', $paymentNote, number_format($paidAmount), number_format($basePrice))
|
||||
: $paymentNote,
|
||||
'proof_image' => !empty($proofPaths) ? json_encode($proofPaths) : null,
|
||||
]);
|
||||
}
|
||||
|
||||
// 5) Service — updateOrCreate để tránh trùng khi cùng VM được gán lại
|
||||
$serviceLookup = $vmId
|
||||
|
||||
Reference in New Issue
Block a user