update api register
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Voucher;
|
||||
|
||||
use App\Enums\UserAccessLevel;
|
||||
use App\Models\HotspotVoucher;
|
||||
use App\Models\NasMikrotik;
|
||||
use App\Models\NasPackageProfile;
|
||||
use App\Models\PaymentGatewayAccount;
|
||||
use App\Models\TenantPaymentSetting;
|
||||
use App\Models\User;
|
||||
use App\Models\VoucherAgent;
|
||||
use App\Models\VoucherLoginPageSetting;
|
||||
use App\Models\VoucherSale;
|
||||
use App\Services\Wallet\WalletService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class VoucherService
|
||||
{
|
||||
public function __construct(protected WalletService $wallets) {}
|
||||
|
||||
public function sales(array $filters, User $actor, ?int $tenantId)
|
||||
{
|
||||
return VoucherSale::with(['agent.user:id,name,username', 'packageProfile:id,name,external_profile_name,session_timeout', 'creator:id,name'])
|
||||
->where('tenant_id', $tenantId)
|
||||
->when($actor->access_level === UserAccessLevel::CUSTOMER, fn ($q) => $q->whereHas('agent', fn ($x) => $x->where('user_id', $actor->id)))
|
||||
->when($filters['search'] ?? null, fn ($q, $search) => $q->where('sale_number', 'ilike', "%{$search}%"))
|
||||
->when($filters['payment_status'] ?? null, fn ($q, $value) => $q->where('payment_status', $value))
|
||||
->latest('id')->paginate($filters['per_page'] ?? 10);
|
||||
}
|
||||
|
||||
public function saleDetail(VoucherSale $sale, User $actor, ?int $tenantId): VoucherSale
|
||||
{
|
||||
$this->authorizeTenant($sale->tenant_id, $tenantId, $actor);
|
||||
if ($actor->access_level === UserAccessLevel::CUSTOMER && $sale->agent?->user_id !== $actor->id) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$sale->load(['agent.user:id,name,username', 'packageProfile', 'vouchers.mikrotik:id,name']);
|
||||
$sale->vouchers->each(fn (HotspotVoucher $voucher) => $voucher->setAttribute('access_password', $voucher->password));
|
||||
|
||||
return $sale;
|
||||
}
|
||||
|
||||
public function createSale(array $data, User $actor, ?int $tenantId): VoucherSale
|
||||
{
|
||||
if (! $tenantId) {
|
||||
throw ValidationException::withMessages(['tenant_id' => 'Tenant aktif wajib tersedia.']);
|
||||
}
|
||||
$package = NasPackageProfile::whereKey($data['nas_package_profile_id'])->where('tenant_id', $tenantId)
|
||||
->where('service_type', 'hotspot')->where('status', 'active')->firstOrFail();
|
||||
if (! empty($data['nas_mikrotik_id']) && ! NasMikrotik::whereKey($data['nas_mikrotik_id'])->where('tenant_id', $tenantId)->exists()) {
|
||||
throw ValidationException::withMessages(['nas_mikrotik_id' => 'Mikrotik tidak valid untuk tenant aktif.']);
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($data, $actor, $tenantId, $package) {
|
||||
$agent = $actor->access_level === UserAccessLevel::CUSTOMER
|
||||
? VoucherAgent::with('user')->where('tenant_id', $tenantId)->where('user_id', $actor->id)->where('status', 'active')->lockForUpdate()->first()
|
||||
: null;
|
||||
if ($actor->access_level === UserAccessLevel::CUSTOMER && ! $agent) {
|
||||
throw ValidationException::withMessages(['agent' => 'Akun belum terdaftar sebagai agen voucher aktif.']);
|
||||
}
|
||||
|
||||
$quantity = (int) $data['quantity'];
|
||||
$gross = (float) $package->price * $quantity;
|
||||
$benefitPercent = $agent?->price_mode === 'commission'
|
||||
? (float) $agent->commission_percent
|
||||
: (float) ($agent?->discount_percent ?? 0);
|
||||
$discount = round($gross * $benefitPercent / 100, 2);
|
||||
$total = $gross - $discount;
|
||||
$saleUuid = (string) Str::uuid();
|
||||
|
||||
if ($agent?->payment_type === 'prepaid') {
|
||||
$this->wallets->chargeUser($actor, $tenantId, $total, "voucher-sale:{$saleUuid}", "Pembelian {$quantity} voucher {$package->name}");
|
||||
}
|
||||
if ($agent?->payment_type === 'postpaid') {
|
||||
if ((float) $agent->outstanding_balance + $total > (float) $agent->credit_limit) {
|
||||
throw ValidationException::withMessages(['credit_limit' => 'Limit kredit agen tidak mencukupi.']);
|
||||
}
|
||||
$agent->increment('outstanding_balance', $total);
|
||||
}
|
||||
|
||||
$sale = VoucherSale::create([
|
||||
'uuid' => $saleUuid, 'sale_number' => $this->nextSaleNumber($tenantId), 'tenant_id' => $tenantId,
|
||||
'voucher_agent_id' => $agent?->id, 'nas_package_profile_id' => $package->id,
|
||||
'created_by' => $actor->id, 'source' => 'dashboard', 'quantity' => $quantity,
|
||||
'unit_price' => $package->price, 'discount_amount' => $discount, 'total_amount' => $total,
|
||||
'payment_status' => $agent?->payment_type === 'postpaid' ? 'credit' : 'paid', 'status' => 'completed',
|
||||
'pricing_snapshot' => [
|
||||
'agent_type' => $agent?->payment_type, 'price_mode' => $agent?->price_mode,
|
||||
'discount_percent' => $agent?->discount_percent, 'commission_percent' => $agent?->commission_percent,
|
||||
'agent_benefit_amount' => $discount,
|
||||
],
|
||||
'notes' => $data['notes'] ?? null,
|
||||
]);
|
||||
|
||||
for ($index = 0; $index < $quantity; $index++) {
|
||||
$username = $this->uniqueVoucherCode($tenantId);
|
||||
$password = Str::upper(Str::random(6));
|
||||
$voucher = HotspotVoucher::create([
|
||||
'uuid' => Str::uuid(), 'tenant_id' => $tenantId, 'voucher_sale_id' => $sale->id,
|
||||
'nas_package_profile_id' => $package->id, 'nas_mikrotik_id' => $data['nas_mikrotik_id'] ?? null,
|
||||
'username' => $username, 'password' => $password, 'status' => 'generated', 'sync_status' => 'pending',
|
||||
'profile_snapshot' => ['name' => $package->name, 'external_profile_name' => $package->external_profile_name, 'session_timeout' => $package->session_timeout],
|
||||
]);
|
||||
$voucher->setAttribute('access_password', $password);
|
||||
}
|
||||
|
||||
return $this->saleDetail($sale, $actor, $tenantId);
|
||||
});
|
||||
}
|
||||
|
||||
public function agents(array $filters, User $actor, ?int $tenantId)
|
||||
{
|
||||
return VoucherAgent::with(['user:id,name,username,email,whatsapp_number'])
|
||||
->when(! $actor->isMasterAdmin(), fn ($q) => $q->where('tenant_id', $tenantId))
|
||||
->when($actor->isMasterAdmin() && $tenantId, fn ($q) => $q->where('tenant_id', $tenantId))
|
||||
->when($filters['search'] ?? null, fn ($q, $search) => $q->where(fn ($x) => $x->where('agent_code', 'ilike', "%{$search}%")->orWhereHas('user', fn ($u) => $u->where('name', 'ilike', "%{$search}%"))))
|
||||
->latest('id')->paginate($filters['per_page'] ?? 10);
|
||||
}
|
||||
|
||||
public function saveAgent(array $data, User $actor, ?int $tenantId, ?VoucherAgent $agent = null): VoucherAgent
|
||||
{
|
||||
$effectiveTenant = $actor->isMasterAdmin() ? ($data['tenant_id'] ?? $tenantId ?? $agent?->tenant_id) : $tenantId;
|
||||
if (! $effectiveTenant) {
|
||||
throw ValidationException::withMessages(['tenant_id' => 'Tenant wajib dipilih.']);
|
||||
}
|
||||
if ($agent) {
|
||||
$this->authorizeTenant($agent->tenant_id, $effectiveTenant, $actor);
|
||||
}
|
||||
$belongs = DB::table('user_tenants')->where('tenant_id', $effectiveTenant)->where('user_id', $data['user_id'])->exists();
|
||||
if (! $belongs) {
|
||||
throw ValidationException::withMessages(['user_id' => 'User tidak terdaftar pada tenant tersebut.']);
|
||||
}
|
||||
|
||||
$payload = [...$data, 'tenant_id' => $effectiveTenant, 'uuid' => $agent?->uuid ?? Str::uuid()];
|
||||
$payload['agent_code'] = filled($data['agent_code'] ?? null) ? Str::upper($data['agent_code']) : $this->nextAgentCode($effectiveTenant);
|
||||
if ($data['payment_type'] === 'prepaid') {
|
||||
$payload['credit_limit'] = 0;
|
||||
}
|
||||
$model = $agent ?: new VoucherAgent;
|
||||
$model->fill($payload)->save();
|
||||
|
||||
return $model->load('user:id,name,username,email,whatsapp_number');
|
||||
}
|
||||
|
||||
public function options(string $scope, User $actor, ?int $tenantId): array
|
||||
{
|
||||
$data = [];
|
||||
if (in_array($scope, ['sales', 'login-page'], true)) {
|
||||
$data['packages'] = NasPackageProfile::where('tenant_id', $tenantId)->where('service_type', 'hotspot')->where('status', 'active')->orderBy('name')->get();
|
||||
}
|
||||
if ($scope === 'sales') {
|
||||
$data['mikrotiks'] = NasMikrotik::where('tenant_id', $tenantId)->where('status', 'active')->orderBy('name')->get(['id', 'name', 'connection_type']);
|
||||
}
|
||||
if ($scope === 'agents') {
|
||||
$data['users'] = User::whereHas('userTenants', fn ($q) => $q->where('tenant_id', $tenantId))->orderBy('name')->get(['id', 'name', 'username', 'email']);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function loginPage(User $actor, ?int $tenantId): array
|
||||
{
|
||||
if (! $tenantId) {
|
||||
throw ValidationException::withMessages(['tenant_id' => 'Tenant aktif wajib tersedia.']);
|
||||
}
|
||||
$setting = VoucherLoginPageSetting::firstOrCreate(['tenant_id' => $tenantId], [
|
||||
'uuid' => Str::uuid(), 'public_key' => Str::random(64), 'enabled' => false,
|
||||
]);
|
||||
|
||||
return ['setting' => $setting->load('gatewayAccount.provider:id,code,name'), 'embed_script' => $this->embedScript($setting), 'dummy_sales' => $setting->enabled ? $this->dummySales() : []];
|
||||
}
|
||||
|
||||
public function saveLoginPage(array $data, User $actor, ?int $tenantId): array
|
||||
{
|
||||
$setting = VoucherLoginPageSetting::firstOrCreate(['tenant_id' => $tenantId], ['uuid' => Str::uuid(), 'public_key' => Str::random(64)]);
|
||||
$requestedPackages = collect($data['allowed_package_profile_ids'] ?? [])->map(fn ($id) => (int) $id)->unique();
|
||||
$validPackageCount = NasPackageProfile::where('tenant_id', $tenantId)->where('service_type', 'hotspot')->whereIn('id', $requestedPackages)->count();
|
||||
if ($validPackageCount !== $requestedPackages->count()) {
|
||||
throw ValidationException::withMessages(['allowed_package_profile_ids' => 'Terdapat profile paket yang bukan milik tenant aktif.']);
|
||||
}
|
||||
if ($data['enabled']) {
|
||||
$paymentSetting = TenantPaymentSetting::where('tenant_id', $tenantId)->whereIn('payment_mode', ['tenant_gateway', 'platform_gateway'])->first();
|
||||
$accountId = $paymentSetting?->payment_mode === 'tenant_gateway' ? $paymentSetting?->tenant_gateway_account_id : $paymentSetting?->platform_gateway_account_id;
|
||||
$gateway = PaymentGatewayAccount::whereKey($accountId)->where('enabled', true)->first();
|
||||
if (! $gateway) {
|
||||
throw ValidationException::withMessages(['enabled' => 'LoginPage Voucher hanya dapat diaktifkan jika tenant memiliki payment gateway aktif.']);
|
||||
}
|
||||
$data['payment_gateway_account_id'] = $gateway->id;
|
||||
} else {
|
||||
$data['payment_gateway_account_id'] = null;
|
||||
}
|
||||
$setting->update([...$data, 'enabled_at' => $data['enabled'] ? ($setting->enabled_at ?? now()) : null]);
|
||||
|
||||
return $this->loginPage($actor, $tenantId);
|
||||
}
|
||||
|
||||
private function embedScript(VoucherLoginPageSetting $setting): string
|
||||
{
|
||||
return '<div id="voucher-container"></div>'."\n".'<script src="https://manjapro.net/voucherpay/cl-style.js?i='.$setting->public_key.'"></script>';
|
||||
}
|
||||
|
||||
private function dummySales(): array
|
||||
{
|
||||
return collect(range(1, 5))->map(fn ($i) => [
|
||||
'id' => $i, 'reference' => 'DEMO-'.now()->format('Ymd').'-'.str_pad((string) $i, 3, '0', STR_PAD_LEFT),
|
||||
'package' => ['Hotspot 2 Jam', 'Hotspot Harian', 'Hotspot Mingguan'][$i % 3],
|
||||
'buyer' => 'Customer Demo '.$i, 'amount' => 5000 * $i,
|
||||
'status' => $i === 4 ? 'pending' : 'paid', 'created_at' => now()->subMinutes($i * 17)->toIso8601String(),
|
||||
])->all();
|
||||
}
|
||||
|
||||
private function nextSaleNumber(int $tenantId): string
|
||||
{
|
||||
return 'VCR-'.$tenantId.'-'.now()->format('YmdHis').'-'.Str::upper(Str::random(4));
|
||||
}
|
||||
|
||||
private function nextAgentCode(int $tenantId): string
|
||||
{
|
||||
return 'AGT-'.$tenantId.'-'.str_pad((string) (VoucherAgent::where('tenant_id', $tenantId)->count() + 1), 4, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
private function uniqueVoucherCode(int $tenantId): string
|
||||
{
|
||||
do {
|
||||
$code = 'V'.Str::upper(Str::random(9));
|
||||
} while (HotspotVoucher::where('tenant_id', $tenantId)->where('username', $code)->exists());
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
private function authorizeTenant(int $modelTenantId, ?int $tenantId, User $actor): void
|
||||
{
|
||||
if (! $actor->isMasterAdmin()) {
|
||||
abort_unless($tenantId && $modelTenantId === $tenantId, 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user