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,147 @@
<?php
use App\Models\ActivationKey;
use App\Models\User;
use App\Services\Payments\PaymentConfigurationManager;
use App\Services\Payments\PaymentOrchestrator;
use App\Services\Payments\ProviderRegistry;
test('can redeem activation key', function () {
$user = User::factory()->create();
// Create an activation key
$activationKey = ActivationKey::factory()->create([
'activation_key' => 'TEST-KEY-123',
'is_activated' => false,
]);
$response = actingAs($user)
->postJson('/api/activation-keys/redeem', [
'activation_key' => 'TEST-KEY-123',
]);
expect($response->status())->toBe(200);
$data = $response->json();
expect($data['success'])->toBeTrue();
expect($data['data'])->toHaveKey('subscription_id');
// Verify the key is now activated
$activationKey->refresh();
expect($activationKey->is_activated)->toBeTrue();
});
test('requires authentication to redeem activation key', function () {
$response = postJson('/api/activation-keys/redeem', [
'activation_key' => 'TEST-KEY-123',
]);
expect($response->status())->toBe(401);
});
test('can validate activation key', function () {
// Create an activation key
$activationKey = ActivationKey::factory()->create([
'activation_key' => 'VALIDATE-KEY-123',
'is_activated' => false,
]);
$response = getJson('/api/activation-keys/validate/VALIDATE-KEY-123');
expect($response->status())->toBe(200);
$data = $response->json();
expect($data['valid'])->toBeTrue();
expect($data['is_activated'])->toBeFalse();
});
test('returns invalid for non-existent activation key', function () {
$response = getJson('/api/activation-keys/validate/NON-EXISTENT');
expect($response->status())->toBe(200);
$data = $response->json();
expect($data['valid'])->toBeFalse();
expect($data['reason'])->toBe('Activation key not found');
});
test('can get crypto exchange rate', function () {
$response = getJson('/api/crypto/rates/btc');
expect($response->status())->toBe(200);
$response->assertJsonStructure([
'crypto',
'rate_usd_per_crypto',
'rate_crypto_per_usd',
'updated_at',
]);
$data = $response->json();
expect($data['crypto'])->toBe('BTC');
expect($data['rate_usd_per_crypto'])->toBeNumeric();
expect($data['rate_crypto_per_usd'])->toBeNumeric();
});
test('can convert usd to crypto', function () {
$response = getJson('/api/crypto/convert?usd_amount=100&crypto=btc');
expect($response->status())->toBe(200);
$response->assertJsonStructure([
'usd_amount',
'crypto',
'crypto_amount',
'fees',
'net_crypto_amount',
'updated_at',
]);
$data = $response->json();
expect($data['usd_amount'])->toBe(100.0);
expect($data['crypto'])->toBe('BTC');
expect($data['crypto_amount'])->toBeNumeric();
expect($data['fees'])->toHaveKey('total_fee');
});
test('validates crypto conversion parameters', function () {
// Invalid crypto type
$response = getJson('/api/crypto/convert?usd_amount=100&crypto=invalid');
expect($response->status())->toBe(422);
// Invalid amount
$response = getJson('/api/crypto/convert?usd_amount=0&crypto=btc');
expect($response->status())->toBe(422);
});
test('payment routes exist', function () {
// Test that basic payment routes exist
$response = getJson('/api/payment/success');
expect($response->status())->toBe(200);
$response = getJson('/api/payment/cancel');
expect($response->status())->toBe(200);
});
test('can create simple provider registry', function () {
$registry = new ProviderRegistry;
expect($registry)->toBeInstanceOf(ProviderRegistry::class);
expect($registry->getAllProviders())->toHaveCount(0);
});
test('can create configuration manager', function () {
$registry = new ProviderRegistry;
$configManager = new PaymentConfigurationManager($registry);
expect($configManager)->toBeInstanceOf(PaymentConfigurationManager::class);
// Test that activation key is always available
$configManager->initializeProviders();
expect($registry->has('activation_key'))->toBeTrue();
});
test('can create payment orchestrator', function () {
$registry = new ProviderRegistry;
$configManager = new PaymentConfigurationManager($registry);
$orchestrator = new PaymentOrchestrator($registry, $configManager);
expect($orchestrator)->toBeInstanceOf(PaymentOrchestrator::class);
expect($orchestrator->getRegistry())->toBe($registry);
});