feat: implement comprehensive multi-provider payment processing system

- 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
This commit is contained in:
idevakk
2025-11-19 09:37:00 -08:00
parent 0560016f33
commit 27ac13948c
83 changed files with 15613 additions and 103 deletions

View File

@@ -0,0 +1,240 @@
<?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);
}
}
}