Implement Phase 2: Billing & Subscriptions
Add complete billing system with Stripe and PayPal gateway support, checkout flow with coupon validation, subscription management (cancel/resume/swap), payment method management, invoice and transaction history, webhook handlers, dunning/suspension system with scheduled processing, and 29 new tests (53 total passing). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
129
website/app/Http/Controllers/Account/BillingController.php
Normal file
129
website/app/Http/Controllers/Account/BillingController.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Account;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Invoice;
|
||||
use App\Services\Billing\BillingServiceFactory;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
|
||||
class BillingController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private BillingServiceFactory $billingFactory,
|
||||
) {}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$user = $request->user();
|
||||
$stripeService = $this->billingFactory->make('stripe');
|
||||
|
||||
return Inertia::render('Billing/Index', [
|
||||
'paymentMethods' => $stripeService->getPaymentMethods($user),
|
||||
'invoices' => $user->invoices()
|
||||
->latest()
|
||||
->take(20)
|
||||
->get(),
|
||||
'transactions' => $user->paymentTransactions()
|
||||
->latest()
|
||||
->take(20)
|
||||
->get(),
|
||||
'intent' => $user->hasStripeId() ? $user->createSetupIntent() : null,
|
||||
'stripeKey' => config('cashier.key'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function addPaymentMethod(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'payment_method_id' => ['required', 'string'],
|
||||
]);
|
||||
|
||||
$service = $this->billingFactory->make('stripe');
|
||||
$success = $service->addPaymentMethod($request->user(), $request->input('payment_method_id'));
|
||||
|
||||
if (! $success) {
|
||||
return back()->with('error', 'Failed to add payment method.');
|
||||
}
|
||||
|
||||
return back()->with('success', 'Payment method added.');
|
||||
}
|
||||
|
||||
public function removePaymentMethod(Request $request, string $paymentMethodId): RedirectResponse
|
||||
{
|
||||
$service = $this->billingFactory->make('stripe');
|
||||
$service->removePaymentMethod($request->user(), $paymentMethodId);
|
||||
|
||||
return back()->with('success', 'Payment method removed.');
|
||||
}
|
||||
|
||||
public function setDefaultPaymentMethod(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'payment_method_id' => ['required', 'string'],
|
||||
]);
|
||||
|
||||
$service = $this->billingFactory->make('stripe');
|
||||
$service->setDefaultPaymentMethod($request->user(), $request->input('payment_method_id'));
|
||||
|
||||
return back()->with('success', 'Default payment method updated.');
|
||||
}
|
||||
|
||||
public function invoices(Request $request): Response
|
||||
{
|
||||
$invoices = $request->user()
|
||||
->invoices()
|
||||
->latest()
|
||||
->paginate(20);
|
||||
|
||||
return Inertia::render('Billing/Invoices', [
|
||||
'invoices' => $invoices,
|
||||
]);
|
||||
}
|
||||
|
||||
public function downloadInvoice(Request $request, Invoice $invoice): \Symfony\Component\HttpFoundation\Response
|
||||
{
|
||||
if ($invoice->user_id !== $request->user()->id) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
if ($invoice->invoice_pdf) {
|
||||
return redirect($invoice->invoice_pdf);
|
||||
}
|
||||
|
||||
// Generate a basic invoice download as JSON for now
|
||||
// PDF generation will be added with a dedicated package
|
||||
return response()->json($invoice->load('items'));
|
||||
}
|
||||
|
||||
public function transactions(Request $request): Response
|
||||
{
|
||||
$transactions = $request->user()
|
||||
->paymentTransactions()
|
||||
->latest()
|
||||
->paginate(20);
|
||||
|
||||
return Inertia::render('Billing/Transactions', [
|
||||
'transactions' => $transactions,
|
||||
]);
|
||||
}
|
||||
|
||||
public function setupIntent(Request $request): JsonResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
if (! $user->hasStripeId()) {
|
||||
$user->createAsStripeCustomer();
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'client_secret' => $user->createSetupIntent()->client_secret,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user