- 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
298 lines
9.3 KiB
PHP
298 lines
9.3 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\ActivationKey;
|
|
use App\Services\Payments\PaymentConfigurationManager;
|
|
use App\Services\Payments\PaymentOrchestrator;
|
|
use App\Services\Payments\Providers\ActivationKeyProvider;
|
|
use App\Services\Payments\Providers\CryptoProvider;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
class PaymentProviderController extends Controller
|
|
{
|
|
public function __construct(
|
|
private PaymentConfigurationManager $configManager,
|
|
private PaymentOrchestrator $orchestrator
|
|
) {}
|
|
|
|
/**
|
|
* Get all payment providers and their status
|
|
*/
|
|
public function index(): JsonResponse
|
|
{
|
|
try {
|
|
$status = $this->configManager->getProviderStatus();
|
|
$stats = $this->orchestrator->getRegistry()->getProviderStats();
|
|
|
|
return response()->json([
|
|
'providers' => $status,
|
|
'statistics' => $stats,
|
|
]);
|
|
} catch (\Exception $e) {
|
|
return response()->json([
|
|
'error' => 'Failed to retrieve provider status',
|
|
'message' => $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get specific provider details
|
|
*/
|
|
public function show(string $provider): JsonResponse
|
|
{
|
|
try {
|
|
$providerInstance = $this->orchestrator->getRegistry()->get($provider);
|
|
$config = $this->configManager->getProviderConfig($provider);
|
|
|
|
return response()->json([
|
|
'provider' => $provider,
|
|
'name' => $providerInstance->getName(),
|
|
'active' => $providerInstance->isActive(),
|
|
'configuration' => $this->configManager->sanitizeConfig($config),
|
|
'capabilities' => [
|
|
'supports_recurring' => $providerInstance->supportsRecurring(),
|
|
'supports_one_time' => $providerInstance->supportsOneTime(),
|
|
'supported_currencies' => $providerInstance->getSupportedCurrencies(),
|
|
],
|
|
]);
|
|
} catch (\Exception $e) {
|
|
return response()->json([
|
|
'error' => 'Provider not found',
|
|
'message' => $e->getMessage(),
|
|
], 404);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test provider connectivity
|
|
*/
|
|
public function test(string $provider): JsonResponse
|
|
{
|
|
try {
|
|
$result = $this->configManager->testProviderConnectivity($provider);
|
|
|
|
return response()->json($result);
|
|
} catch (\Exception $e) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'error' => 'Failed to test provider',
|
|
'message' => $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enable/disable a provider
|
|
*/
|
|
public function toggle(Request $request, string $provider): JsonResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'enabled' => 'required|boolean',
|
|
]);
|
|
|
|
try {
|
|
$result = $this->configManager->toggleProvider($provider, $validated['enabled']);
|
|
|
|
if ($result) {
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => "Provider {$provider} has been ".
|
|
($validated['enabled'] ? 'enabled' : 'disabled'),
|
|
]);
|
|
}
|
|
|
|
return response()->json([
|
|
'success' => false,
|
|
'message' => "Failed to toggle provider {$provider}",
|
|
], 400);
|
|
} catch (\Exception $e) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'error' => 'Failed to toggle provider',
|
|
'message' => $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update provider configuration
|
|
*/
|
|
public function updateConfig(Request $request, string $provider): JsonResponse
|
|
{
|
|
$config = $request->all();
|
|
|
|
// Validate configuration
|
|
$validation = $this->configManager->validateProviderConfig($provider, $config);
|
|
if (! $validation['valid']) {
|
|
throw ValidationException::withMessages([
|
|
'config' => $validation['errors'],
|
|
]);
|
|
}
|
|
|
|
try {
|
|
$this->configManager->updateProviderConfig($provider, $config);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => "Configuration updated for provider {$provider}",
|
|
]);
|
|
} catch (\Exception $e) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'error' => 'Failed to update configuration',
|
|
'message' => $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Refresh provider configurations
|
|
*/
|
|
public function refresh(): JsonResponse
|
|
{
|
|
try {
|
|
$this->configManager->refreshConfigurations();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Provider configurations refreshed',
|
|
]);
|
|
} catch (\Exception $e) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'error' => 'Failed to refresh configurations',
|
|
'message' => $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Redeem an activation key
|
|
*/
|
|
public function redeemActivationKey(Request $request): JsonResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'activation_key' => 'required|string',
|
|
]);
|
|
|
|
try {
|
|
$user = $request->user();
|
|
if (! $user) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'error' => 'Authentication required',
|
|
], 401);
|
|
}
|
|
|
|
$provider = new ActivationKeyProvider;
|
|
$result = $provider->redeemActivationKey($validated['activation_key'], $user);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $result,
|
|
]);
|
|
} catch (\Exception $e) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'error' => 'Failed to redeem activation key',
|
|
'message' => $e->getMessage(),
|
|
], 400);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate an activation key
|
|
*/
|
|
public function validateActivationKey(string $key): JsonResponse
|
|
{
|
|
try {
|
|
$activationKey = ActivationKey::where('activation_key', $key)->first();
|
|
|
|
if (! $activationKey) {
|
|
return response()->json([
|
|
'valid' => false,
|
|
'reason' => 'Activation key not found',
|
|
]);
|
|
}
|
|
|
|
return response()->json([
|
|
'valid' => true,
|
|
'is_activated' => $activationKey->is_activated,
|
|
'created_at' => $activationKey->created_at,
|
|
'plan_id' => $activationKey->price_id,
|
|
]);
|
|
} catch (\Exception $e) {
|
|
return response()->json([
|
|
'valid' => false,
|
|
'error' => 'Failed to validate activation key',
|
|
'message' => $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get cryptocurrency exchange rate
|
|
*/
|
|
public function getCryptoRate(string $crypto): JsonResponse
|
|
{
|
|
try {
|
|
$provider = new CryptoProvider;
|
|
|
|
// Test conversion with $1 USD to get rate
|
|
$amount = $provider->convertUsdToCrypto(1.00, strtoupper($crypto));
|
|
$rate = 1 / $amount; // Invert to get USD per crypto
|
|
|
|
return response()->json([
|
|
'crypto' => strtoupper($crypto),
|
|
'rate_usd_per_crypto' => $rate,
|
|
'rate_crypto_per_usd' => $amount,
|
|
'updated_at' => now()->toISOString(),
|
|
]);
|
|
} catch (\Exception $e) {
|
|
return response()->json([
|
|
'error' => 'Failed to get crypto rate',
|
|
'message' => $e->getMessage(),
|
|
], 400);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert USD to cryptocurrency
|
|
*/
|
|
public function convertUsdToCrypto(Request $request): JsonResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'usd_amount' => 'required|numeric|min:0.01',
|
|
'crypto' => 'required|string|in:BTC,ETH,USDT,USDC,LTC',
|
|
]);
|
|
|
|
try {
|
|
$provider = new CryptoProvider;
|
|
$cryptoAmount = $provider->convertUsdToCrypto(
|
|
$validated['usd_amount'],
|
|
strtoupper($validated['crypto'])
|
|
);
|
|
|
|
$fees = $provider->calculateFees($validated['usd_amount']);
|
|
|
|
return response()->json([
|
|
'usd_amount' => $validated['usd_amount'],
|
|
'crypto' => strtoupper($validated['crypto']),
|
|
'crypto_amount' => $cryptoAmount,
|
|
'fees' => $fees,
|
|
'net_crypto_amount' => $cryptoAmount, // Crypto providers typically don't deduct fees from amount
|
|
'updated_at' => now()->toISOString(),
|
|
]);
|
|
} catch (\Exception $e) {
|
|
return response()->json([
|
|
'error' => 'Failed to convert USD to crypto',
|
|
'message' => $e->getMessage(),
|
|
], 400);
|
|
}
|
|
}
|
|
}
|