initial: bootstrap from BukidBountyApp base

This commit is contained in:
Jonathan Sykes
2026-06-06 18:43:00 +08:00
commit eb4a5731fb
5674 changed files with 160857 additions and 0 deletions

View File

@@ -0,0 +1,201 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Subscription;
use Hypervel\Http\Request;
use Hypervel\Support\Facades\Auth;
use Hypervel\Support\Facades\Response;
use App\Models\User;
use App\Models\Subscription\SubscriptionPlan;
use App\Models\Subscription\Subscription;
use App\Models\Subscription\SubscriptionInvoice;
use App\Enums\UserTypes;
use Carbon\Carbon;
class SubscriptionController
{
// ── User: list available plans (active only) ───────────────────────────
public function listAvailablePlans()
{
$plans = SubscriptionPlan::where('active', true)
->orderBy('price')
->get()
->map(fn($p) => [
'hashkey' => $p->hashkey,
'name' => $p->name,
'description' => $p->description,
'price' => $p->price,
'duration_days' => $p->duration_days,
'expiry_action' => $p->expiry_action,
]);
return Response::json($plans);
}
// ── User: get my current subscription ─────────────────────────────────
public function mySubscription()
{
$user = User::findOrFail(Auth::id());
$sub = self::getActiveSubscription($user->id);
if (!$sub) {
return Response::json([
'has_subscription' => false,
'balance' => $user->total_balance,
]);
}
return Response::json([
'has_subscription' => true,
'subscription' => self::formatUserSubscription($sub),
'balance' => $user->total_balance,
]);
}
// ── User: pay for a subscription via wallet ────────────────────────────
public function payWithWallet(Request $request)
{
$planHashkey = $request->input('plan_hashkey');
if (!$planHashkey) {
return Response::json('Plan is required.', 422);
}
$plan = SubscriptionPlan::where('hashkey', $planHashkey)
->where('active', true)
->first();
if (!$plan) {
return Response::json('Plan not found or no longer available.', 404);
}
$user = User::findOrFail(Auth::id());
if ($user->total_balance < $plan->price) {
return Response::json('Insufficient wallet balance.', 402);
}
$admin = User::where('acct_type', UserTypes::ULTIMATE->value)->first();
if (!$admin) {
return Response::json('Payment recipient not configured.', 500);
}
try {
// Deduct from user, credit admin
$user->total_balance -= $plan->price;
$user->save();
$admin->total_balance += $plan->price;
$admin->save();
// Create or extend subscription
$now = Carbon::now();
$expiry = $now->copy()->addDays($plan->duration_days);
$existing = self::getActiveSubscription($user->id);
if ($existing) {
// Extend from current expiry if still active, otherwise from now
$base = $existing->expires_at && $existing->expires_at->isFuture()
? $existing->expires_at
: $now;
$expiry = $base->copy()->addDays($plan->duration_days);
$existing->expires_at = $expiry;
$existing->status = 'active';
$existing->payment_method = 'wallet';
$existing->save();
$subscription = $existing;
} else {
$subscription = Subscription::create([
'user_id' => $user->id,
'plan_id' => $plan->id,
'status' => 'active',
'starts_at' => $now,
'expires_at' => $expiry,
'payment_method' => 'wallet',
]);
}
// Record invoice
SubscriptionInvoice::create([
'subscription_id' => $subscription->id,
'user_id' => $user->id,
'amount' => $plan->price,
'status' => 'paid',
'paid_at' => $now,
'payment_method' => 'wallet',
'payment_reference' => null,
'additional_details' => [
'plan_name' => $plan->name,
'plan_hashkey' => $plan->hashkey,
'admin_id' => $admin->id,
],
]);
return Response::json([
'success' => true,
'expires_at' => $expiry,
'balance' => $user->total_balance,
]);
} catch (\Throwable $th) {
return Response::json($th->getMessage(), 500);
}
}
// ── User: my invoice history ───────────────────────────────────────────
public function myInvoices()
{
$invoices = SubscriptionInvoice::where('user_id', Auth::id())
->orderByDesc('created_at')
->get()
->map(fn($inv) => [
'hashkey' => $inv->hashkey,
'amount' => $inv->amount,
'status' => $inv->status,
'payment_method' => $inv->payment_method,
'payment_reference' => $inv->payment_reference,
'paid_at' => $inv->paid_at,
'plan_name' => $inv->additional_details['plan_name'] ?? '',
'created_at' => $inv->created_at,
]);
return Response::json($invoices);
}
// ── Helper: get user's latest active subscription ─────────────────────
private static function getActiveSubscription(int $userId): ?Subscription
{
return Subscription::where('user_id', $userId)
->where('status', 'active')
->where('expires_at', '>', Carbon::now())
->with('plan')
->orderByDesc('expires_at')
->first();
}
private static function formatUserSubscription(Subscription $sub): array
{
$plan = $sub->plan;
$expiresAt = $sub->expires_at;
$daysRemaining = $expiresAt ? (int) now()->diffInDays($expiresAt, false) : 0;
return [
'hashkey' => $sub->hashkey,
'status' => $sub->status,
'starts_at' => $sub->starts_at,
'expires_at' => $expiresAt,
'days_remaining' => max(0, $daysRemaining),
'payment_method' => $sub->payment_method,
'plan' => $plan ? [
'hashkey' => $plan->hashkey,
'name' => $plan->name,
'price' => $plan->price,
'duration_days' => $plan->duration_days,
'expiry_action' => $plan->expiry_action,
] : null,
];
}
}