- Add unified payment provider architecture with contract-based design - Implement 6 payment providers: Stripe, Lemon Squeezy, Polar, Oxapay, Crypto, Activation Keys - Create subscription management with lifecycle handling (create, cancel, pause, resume, update) - Add coupon system with usage tracking and trial extensions - Build Filament admin resources for payment providers, subscriptions, coupons, and trials - Implement payment orchestration service with provider registry and configuration management - Add comprehensive payment logging and webhook handling for all providers - Create customer analytics dashboard with revenue, churn, and lifetime value metrics - Add subscription migration service for provider switching - Include extensive test coverage for all payment functionality
241 lines
7.5 KiB
PHP
241 lines
7.5 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\Plan;
|
|
use App\Models\User;
|
|
use App\Services\Payments\PaymentOrchestrator;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
class PaymentController extends Controller
|
|
{
|
|
public function __construct(
|
|
private PaymentOrchestrator $orchestrator
|
|
) {}
|
|
|
|
/**
|
|
* Create a checkout session for a plan
|
|
*/
|
|
public function createCheckout(Request $request): JsonResponse
|
|
{
|
|
try {
|
|
$validated = $request->validate([
|
|
'plan_id' => 'required|exists:plans,id',
|
|
'provider' => 'nullable|string|in:stripe,lemon_squeezy,polar,oxapay,crypto,activation_key',
|
|
'options' => 'nullable|array',
|
|
]);
|
|
|
|
$user = $request->user();
|
|
$plan = Plan::findOrFail($validated['plan_id']);
|
|
$provider = $validated['provider'] ?? null;
|
|
$options = $validated['options'] ?? [];
|
|
|
|
$result = $this->orchestrator->createCheckoutSession($user, $plan, $provider, $options);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $result,
|
|
]);
|
|
|
|
} catch (ValidationException $e) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'errors' => $e->errors(),
|
|
], 422);
|
|
} catch (\Exception $e) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'error' => $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a new subscription
|
|
*/
|
|
public function createSubscription(Request $request): JsonResponse
|
|
{
|
|
try {
|
|
$validated = $request->validate([
|
|
'plan_id' => 'required|exists:plans,id',
|
|
'provider' => 'nullable|string|in:stripe,lemon_squeezy,polar',
|
|
'options' => 'nullable|array',
|
|
]);
|
|
|
|
$user = $request->user();
|
|
$plan = Plan::findOrFail($validated['plan_id']);
|
|
$provider = $validated['provider'] ?? null;
|
|
$options = $validated['options'] ?? [];
|
|
|
|
// Only recurring providers can create subscriptions
|
|
if (! $plan->monthly_billing) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'error' => 'This plan does not support recurring subscriptions. Use checkout instead.',
|
|
], 400);
|
|
}
|
|
|
|
$result = $this->orchestrator->createSubscription($user, $plan, $provider, $options);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $result,
|
|
]);
|
|
|
|
} catch (ValidationException $e) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'errors' => $e->errors(),
|
|
], 422);
|
|
} catch (\Exception $e) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'error' => $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get available payment methods for a plan
|
|
*/
|
|
public function getPaymentMethods(Request $request): JsonResponse
|
|
{
|
|
try {
|
|
$validated = $request->validate([
|
|
'plan_id' => 'required|exists:plans,id',
|
|
]);
|
|
|
|
$plan = Plan::findOrFail($validated['plan_id']);
|
|
$providers = $this->orchestrator->getActiveProvidersForPlan($plan);
|
|
|
|
$methods = $providers->map(function ($provider) use ($plan) {
|
|
return [
|
|
'provider' => $provider->getName(),
|
|
'name' => $provider->getName(),
|
|
'supports_recurring' => $provider->supportsRecurring(),
|
|
'supports_one_time' => $provider->supportsOneTime(),
|
|
'supported_currencies' => $provider->getSupportedCurrencies(),
|
|
'fees' => $provider->calculateFees($plan->price),
|
|
'active' => $provider->isActive(),
|
|
];
|
|
})->values()->toArray();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => [
|
|
'plan' => [
|
|
'id' => $plan->id,
|
|
'name' => $plan->name,
|
|
'price' => $plan->price,
|
|
'monthly_billing' => $plan->monthly_billing,
|
|
],
|
|
'payment_methods' => $methods,
|
|
],
|
|
]);
|
|
|
|
} catch (ValidationException $e) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'errors' => $e->errors(),
|
|
], 422);
|
|
} catch (\Exception $e) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'error' => $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get user's payment/subscription history
|
|
*/
|
|
public function getHistory(Request $request): JsonResponse
|
|
{
|
|
try {
|
|
$validated = $request->validate([
|
|
'limit' => 'nullable|integer|min:1|max:100',
|
|
'offset' => 'nullable|integer|min:0',
|
|
'filters' => 'nullable|array',
|
|
]);
|
|
|
|
$user = $request->user();
|
|
$limit = $validated['limit'] ?? 20;
|
|
$filters = $validated['filters'] ?? [];
|
|
|
|
$history = $this->orchestrator->getTransactionHistory($user, $filters);
|
|
|
|
// Apply pagination
|
|
$offset = $validated['offset'] ?? 0;
|
|
$paginatedHistory = array_slice($history, $offset, $limit);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => [
|
|
'transactions' => $paginatedHistory,
|
|
'pagination' => [
|
|
'total' => count($history),
|
|
'limit' => $limit,
|
|
'offset' => $offset,
|
|
'has_more' => $offset + $limit < count($history),
|
|
],
|
|
],
|
|
]);
|
|
|
|
} catch (ValidationException $e) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'errors' => $e->errors(),
|
|
], 422);
|
|
} catch (\Exception $e) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'error' => $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle successful payment redirect
|
|
*/
|
|
public function success(Request $request): JsonResponse
|
|
{
|
|
return response()->json([
|
|
'status' => 'success',
|
|
'message' => 'Payment completed successfully',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Handle cancelled payment redirect
|
|
*/
|
|
public function cancel(Request $request): JsonResponse
|
|
{
|
|
return response()->json([
|
|
'status' => 'cancelled',
|
|
'message' => 'Payment was cancelled',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Handle payment provider webhooks
|
|
*/
|
|
public function webhook(Request $request, string $provider): JsonResponse
|
|
{
|
|
try {
|
|
$result = $this->orchestrator->processWebhook($provider, $request);
|
|
|
|
return response()->json([
|
|
'status' => 'processed',
|
|
'result' => $result,
|
|
]);
|
|
} catch (\Exception $e) {
|
|
return response()->json([
|
|
'status' => 'error',
|
|
'message' => $e->getMessage(),
|
|
], 400);
|
|
}
|
|
}
|
|
}
|