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:
86
tests/Feature/Feature/PaymentLoggerTest.php
Normal file
86
tests/Feature/Feature/PaymentLoggerTest.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
use App\Models\PaymentEvent;
|
||||
use App\Models\User;
|
||||
use App\Services\Payments\PaymentLogger;
|
||||
|
||||
test('can log payment event', function () {
|
||||
$logger = new PaymentLogger;
|
||||
|
||||
$logger->logEvent('test_subscription_created', [
|
||||
'subscription_id' => 123,
|
||||
'provider' => 'stripe',
|
||||
'amount' => 10.00,
|
||||
]);
|
||||
|
||||
$event = PaymentEvent::where('event_type', 'test_subscription_created')->first();
|
||||
|
||||
expect($event)->not->toBeNull();
|
||||
expect($event->level)->toBe('info');
|
||||
expect($event->data['subscription_id'])->toBe(123);
|
||||
expect($event->data['provider'])->toBe('stripe');
|
||||
expect($event->data['amount'])->toBe(10.00);
|
||||
});
|
||||
|
||||
test('can log error event', function () {
|
||||
$logger = new PaymentLogger;
|
||||
|
||||
$logger->logError('test_payment_failed', [
|
||||
'subscription_id' => 456,
|
||||
'error' => 'Payment declined',
|
||||
]);
|
||||
|
||||
$event = PaymentEvent::where('event_type', 'test_payment_failed')->first();
|
||||
|
||||
expect($event)->not->toBeNull();
|
||||
expect($event->level)->toBe('error');
|
||||
expect($event->data['error'])->toBe('Payment declined');
|
||||
});
|
||||
|
||||
test('can log security event', function () {
|
||||
$logger = new PaymentLogger;
|
||||
|
||||
$logger->logSecurityEvent('suspicious_activity', [
|
||||
'ip_address' => '192.168.1.1',
|
||||
'user_agent' => 'Test Agent',
|
||||
]);
|
||||
|
||||
$event = PaymentEvent::where('event_type', 'security_suspicious_activity')->first();
|
||||
|
||||
expect($event)->not->toBeNull();
|
||||
expect($event->level)->toBe('warning');
|
||||
expect($event->data['requires_review'])->toBeTrue();
|
||||
});
|
||||
|
||||
test('can get user audit trail', function () {
|
||||
$user = User::factory()->create();
|
||||
$logger = new PaymentLogger;
|
||||
|
||||
// Log some events for the user
|
||||
$logger->logEvent('test_event_1', ['user_id' => $user->id]);
|
||||
$logger->logEvent('test_event_2', ['user_id' => $user->id]);
|
||||
|
||||
$trail = $logger->getUserAuditTrail($user->id);
|
||||
|
||||
expect($trail)->toHaveCount(2);
|
||||
expect($trail[0]['event_type'])->toBe('test_event_2'); // Latest first
|
||||
expect($trail[1]['event_type'])->toBe('test_event_1');
|
||||
});
|
||||
|
||||
test('can generate compliance report', function () {
|
||||
$logger = new PaymentLogger;
|
||||
|
||||
// Log some events
|
||||
$logger->logEvent('compliance_data_access', ['user_id' => 1]);
|
||||
$logger->logEvent('subscription_created', ['user_id' => 2]);
|
||||
$logger->logError('payment_failed', ['user_id' => 3]);
|
||||
|
||||
$report = $logger->generateComplianceReport();
|
||||
|
||||
expect($report['total_events'])->toBe(3);
|
||||
expect($report['events_by_type']['compliance_data_access'])->toBe(1);
|
||||
expect($report['events_by_type']['subscription_created'])->toBe(1);
|
||||
expect($report['events_by_type']['payment_failed'])->toBe(1);
|
||||
expect($report['events_by_level']['info'])->toBe(2);
|
||||
expect($report['events_by_level']['error'])->toBe(1);
|
||||
});
|
||||
147
tests/Feature/PaymentProviderControllerTest.php
Normal file
147
tests/Feature/PaymentProviderControllerTest.php
Normal 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);
|
||||
});
|
||||
86
tests/Feature/Unit/PaymentLoggerTest.php
Normal file
86
tests/Feature/Unit/PaymentLoggerTest.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
use App\Models\PaymentEvent;
|
||||
use App\Models\User;
|
||||
use App\Services\Payments\PaymentLogger;
|
||||
|
||||
test('can log payment event', function () {
|
||||
$logger = new PaymentLogger;
|
||||
|
||||
$logger->logEvent('test_subscription_created', [
|
||||
'subscription_id' => 123,
|
||||
'provider' => 'stripe',
|
||||
'amount' => 10.00,
|
||||
]);
|
||||
|
||||
$event = PaymentEvent::where('event_type', 'test_subscription_created')->first();
|
||||
|
||||
expect($event)->not->toBeNull();
|
||||
expect($event->level)->toBe('info');
|
||||
expect($event->data['subscription_id'])->toBe(123);
|
||||
expect($event->data['provider'])->toBe('stripe');
|
||||
expect($event->data['amount'])->toBe(10.00);
|
||||
});
|
||||
|
||||
test('can log error event', function () {
|
||||
$logger = new PaymentLogger;
|
||||
|
||||
$logger->logError('test_payment_failed', [
|
||||
'subscription_id' => 456,
|
||||
'error' => 'Payment declined',
|
||||
]);
|
||||
|
||||
$event = PaymentEvent::where('event_type', 'test_payment_failed')->first();
|
||||
|
||||
expect($event)->not->toBeNull();
|
||||
expect($event->level)->toBe('error');
|
||||
expect($event->data['error'])->toBe('Payment declined');
|
||||
});
|
||||
|
||||
test('can log security event', function () {
|
||||
$logger = new PaymentLogger;
|
||||
|
||||
$logger->logSecurityEvent('suspicious_activity', [
|
||||
'ip_address' => '192.168.1.1',
|
||||
'user_agent' => 'Test Agent',
|
||||
]);
|
||||
|
||||
$event = PaymentEvent::where('event_type', 'security_suspicious_activity')->first();
|
||||
|
||||
expect($event)->not->toBeNull();
|
||||
expect($event->level)->toBe('warning');
|
||||
expect($event->data['requires_review'])->toBeTrue();
|
||||
});
|
||||
|
||||
test('can get user audit trail', function () {
|
||||
$user = User::factory()->create();
|
||||
$logger = new PaymentLogger;
|
||||
|
||||
// Log some events for the user
|
||||
$logger->logEvent('test_event_1', ['user_id' => $user->id]);
|
||||
$logger->logEvent('test_event_2', ['user_id' => $user->id]);
|
||||
|
||||
$trail = $logger->getUserAuditTrail($user->id);
|
||||
|
||||
expect($trail)->toHaveCount(2);
|
||||
expect($trail[0]['event_type'])->toBe('test_event_2'); // Latest first
|
||||
expect($trail[1]['event_type'])->toBe('test_event_1');
|
||||
});
|
||||
|
||||
test('can generate compliance report', function () {
|
||||
$logger = new PaymentLogger;
|
||||
|
||||
// Log some events
|
||||
$logger->logEvent('compliance_data_access', ['user_id' => 1]);
|
||||
$logger->logEvent('subscription_created', ['user_id' => 2]);
|
||||
$logger->logError('payment_failed', ['user_id' => 3]);
|
||||
|
||||
$report = $logger->generateComplianceReport();
|
||||
|
||||
expect($report['total_events'])->toBe(3);
|
||||
expect($report['events_by_type']['compliance_data_access'])->toBe(1);
|
||||
expect($report['events_by_type']['subscription_created'])->toBe(1);
|
||||
expect($report['events_by_type']['payment_failed'])->toBe(1);
|
||||
expect($report['events_by_level']['info'])->toBe(2);
|
||||
expect($report['events_by_level']['error'])->toBe(1);
|
||||
});
|
||||
77
tests/Feature/Unit/ProviderRegistryTest.php
Normal file
77
tests/Feature/Unit/ProviderRegistryTest.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
use App\Services\Payments\ProviderRegistry;
|
||||
use App\Services\Payments\Providers\StripeProvider;
|
||||
|
||||
test('can register payment provider', function () {
|
||||
$registry = new ProviderRegistry;
|
||||
$provider = new StripeProvider;
|
||||
|
||||
$registry->register('stripe', $provider);
|
||||
|
||||
expect($registry->has('stripe'))->toBeTrue();
|
||||
expect($registry->get('stripe'))->toBe($provider);
|
||||
});
|
||||
|
||||
test('can get all providers', function () {
|
||||
$registry = new ProviderRegistry;
|
||||
$stripeProvider = new StripeProvider;
|
||||
|
||||
$registry->register('stripe', $stripeProvider);
|
||||
|
||||
$providers = $registry->getAllProviders();
|
||||
|
||||
expect($providers)->toHaveCount(1);
|
||||
expect($providers->get('stripe'))->toBe($stripeProvider);
|
||||
});
|
||||
|
||||
test('can get active providers only', function () {
|
||||
$registry = new ProviderRegistry;
|
||||
$stripeProvider = new StripeProvider;
|
||||
|
||||
$registry->register('stripe', $stripeProvider);
|
||||
|
||||
$activeProviders = $registry->getActiveProviders();
|
||||
|
||||
expect($activeProviders)->toHaveCount(0); // Stripe is inactive without API key
|
||||
});
|
||||
|
||||
test('can unregister provider', function () {
|
||||
$registry = new ProviderRegistry;
|
||||
$provider = new StripeProvider;
|
||||
|
||||
$registry->register('stripe', $provider);
|
||||
expect($registry->has('stripe'))->toBeTrue();
|
||||
|
||||
$result = $registry->unregister('stripe');
|
||||
expect($result)->toBeTrue();
|
||||
expect($registry->has('stripe'))->toBeFalse();
|
||||
});
|
||||
|
||||
test('can validate providers', function () {
|
||||
$registry = new ProviderRegistry;
|
||||
$stripeProvider = new StripeProvider;
|
||||
|
||||
$registry->register('stripe', $stripeProvider);
|
||||
|
||||
$results = $registry->validateProviders();
|
||||
|
||||
expect($results)->toHaveKey('stripe');
|
||||
expect($results['stripe']['active'])->toBeFalse();
|
||||
expect($results['stripe']['supports_recurring'])->toBeTrue();
|
||||
expect($results['stripe']['supports_one_time'])->toBeTrue();
|
||||
});
|
||||
|
||||
test('can get provider statistics', function () {
|
||||
$registry = new ProviderRegistry;
|
||||
$stripeProvider = new StripeProvider;
|
||||
|
||||
$registry->register('stripe', $stripeProvider);
|
||||
|
||||
$stats = $registry->getProviderStats();
|
||||
|
||||
expect($stats['total_providers'])->toBe(1);
|
||||
expect($stats['active_providers'])->toBe(0);
|
||||
expect($stats['recurring_providers'])->toBe(0);
|
||||
expect($stats['one_time_providers'])->toBe(0);
|
||||
});
|
||||
317
tests/Unit/ActivationKeyProviderTest.php
Normal file
317
tests/Unit/ActivationKeyProviderTest.php
Normal file
@@ -0,0 +1,317 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Plan;
|
||||
use App\Models\User;
|
||||
use App\Services\Payments\Providers\ActivationKeyProvider;
|
||||
|
||||
test('activation key provider has correct name and capabilities', function () {
|
||||
$provider = new ActivationKeyProvider;
|
||||
|
||||
expect($provider->getName())->toBe('activation_key');
|
||||
expect($provider->isActive())->toBeTrue();
|
||||
expect($provider->supportsRecurring())->toBeFalse();
|
||||
expect($provider->supportsOneTime())->toBeTrue();
|
||||
expect($provider->getSupportedCurrencies())->toBe(['USD']);
|
||||
});
|
||||
|
||||
test('can generate unique activation key', function () {
|
||||
$provider = new ActivationKeyProvider;
|
||||
$user = User::factory()->create();
|
||||
$plan = Plan::factory()->create(['price' => 50.00]);
|
||||
|
||||
$subscription = $provider->createSubscription($user, $plan);
|
||||
|
||||
expect($subscription['provider_subscription_id'])->not->toBeEmpty();
|
||||
expect($subscription['status'])->toBe('pending_activation');
|
||||
expect($subscription['activation_key'])->not->toBeEmpty();
|
||||
expect($subscription['activation_key'])->toStartWith('AK-');
|
||||
expect($subscription['type'])->toBe('activation_key');
|
||||
expect($subscription['plan_name'])->toBe($plan->name);
|
||||
expect($subscription['plan_price'])->toBe(50.00);
|
||||
});
|
||||
|
||||
test('can calculate fees correctly', function () {
|
||||
$provider = new ActivationKeyProvider;
|
||||
|
||||
$fees = $provider->calculateFees(100.00);
|
||||
|
||||
expect($fees['fixed_fee'])->toBe(0);
|
||||
expect($fees['percentage_fee'])->toBe(0);
|
||||
expect($fees['total_fee'])->toBe(0);
|
||||
expect($fees['net_amount'])->toBe(100.00);
|
||||
});
|
||||
|
||||
test('can get subscription details', function () {
|
||||
$provider = new ActivationKeyProvider;
|
||||
$user = User::factory()->create();
|
||||
$plan = Plan::factory()->create();
|
||||
|
||||
$subscription = $provider->createSubscription($user, $plan);
|
||||
$details = $provider->getSubscriptionDetails($subscription['provider_subscription_id']);
|
||||
|
||||
expect($details['id'])->toBe($subscription['provider_subscription_id']);
|
||||
expect($details['activation_key'])->toBe($subscription['activation_key']);
|
||||
expect($details['user_id'])->toBe($user->id);
|
||||
expect($details['price_id'])->toBe($plan->id);
|
||||
expect($details['is_activated'])->toBeFalse();
|
||||
});
|
||||
|
||||
test('activation keys are not refundable', function () {
|
||||
$provider = new ActivationKeyProvider;
|
||||
|
||||
expect(fn () => $provider->processRefund('test_id', 100.00, 'User requested'))
|
||||
->toThrow(Exception::class, 'Activation keys are not refundable');
|
||||
});
|
||||
|
||||
test('can get transaction history', function () {
|
||||
$provider = new ActivationKeyProvider;
|
||||
$user = User::factory()->create();
|
||||
$plan = Plan::factory()->create();
|
||||
|
||||
// Create a few activation keys
|
||||
$provider->createSubscription($user, $plan);
|
||||
$provider->createSubscription($user, $plan);
|
||||
|
||||
$history = $provider->getTransactionHistory($user);
|
||||
|
||||
expect($history)->toBeArray();
|
||||
expect($history)->toHaveCount(2);
|
||||
expect($history[0])->toHaveKey('activation_key');
|
||||
expect($history[0])->toHaveKey('is_activated');
|
||||
expect($history[0])->toHaveKey('created_at');
|
||||
});
|
||||
|
||||
test('can get transaction history with filters', function () {
|
||||
$provider = new ActivationKeyProvider;
|
||||
$user = User::factory()->create();
|
||||
$plan = Plan::factory()->create();
|
||||
|
||||
// Create activation keys
|
||||
$subscription1 = $provider->createSubscription($user, $plan);
|
||||
|
||||
// Test filter for unactivated keys (all should be unactivated initially)
|
||||
$unactivatedHistory = $provider->getTransactionHistory($user, ['status' => 'unactivated']);
|
||||
expect($unactivatedHistory)->toHaveCount(1);
|
||||
expect($unactivatedHistory[0]['is_activated'])->toBeFalse();
|
||||
});
|
||||
|
||||
test('can redeem activation key', function () {
|
||||
$provider = new ActivationKeyProvider;
|
||||
$user = User::factory()->create();
|
||||
$plan = Plan::factory()->create();
|
||||
|
||||
// Create activation key
|
||||
$subscription = $provider->createSubscription($user, $plan);
|
||||
$activationKey = $subscription['activation_key'];
|
||||
|
||||
// Redeem the key
|
||||
$result = $provider->redeemActivationKey($activationKey, $user);
|
||||
|
||||
expect($result['success'])->toBeTrue();
|
||||
expect($result['plan_name'])->toBe($plan->name);
|
||||
expect($result['message'])->toBe('Activation key redeemed successfully');
|
||||
});
|
||||
|
||||
test('cannot redeem already activated key', function () {
|
||||
$provider = new ActivationKeyProvider;
|
||||
$user = User::factory()->create();
|
||||
$plan = Plan::factory()->create();
|
||||
|
||||
// Create and redeem activation key
|
||||
$subscription = $provider->createSubscription($user, $plan);
|
||||
$activationKey = $subscription['activation_key'];
|
||||
$provider->redeemActivationKey($activationKey, $user);
|
||||
|
||||
// Try to redeem again
|
||||
expect(fn () => $provider->redeemActivationKey($activationKey, $user))
|
||||
->throw();
|
||||
});
|
||||
|
||||
test('can get configuration', function () {
|
||||
$config = [
|
||||
'key_prefix' => 'TEST-',
|
||||
'key_length' => 16,
|
||||
'expiration_days' => 365,
|
||||
];
|
||||
$provider = new ActivationKeyProvider($config);
|
||||
|
||||
expect($provider->getConfiguration())->toHaveKey('key_prefix', 'TEST-');
|
||||
expect($provider->getConfiguration())->toHaveKey('key_length', 16);
|
||||
expect($provider->getConfiguration())->toHaveKey('expiration_days', 365);
|
||||
});
|
||||
|
||||
test('webhook methods return expected values', function () {
|
||||
$provider = new ActivationKeyProvider;
|
||||
$request = new Illuminate\Http\Request;
|
||||
|
||||
$webhookResult = $provider->processWebhook($request);
|
||||
expect($webhookResult['event_type'])->toBe('not_applicable');
|
||||
expect($webhookResult['processed'])->toBeFalse();
|
||||
|
||||
expect($provider->validateWebhook($request))->toBeFalse();
|
||||
});
|
||||
|
||||
test('customer portal returns dashboard', function () {
|
||||
$provider = new ActivationKeyProvider;
|
||||
$mockUser = new stdClass;
|
||||
|
||||
$portal = $provider->createCustomerPortalSession($mockUser);
|
||||
|
||||
expect($portal)->toHaveKey('portal_url');
|
||||
expect($portal)->toHaveKey('message');
|
||||
expect($portal['message'])->toBe('Activation keys are managed through your dashboard');
|
||||
});
|
||||
|
||||
test('cannot update subscription plan', function () {
|
||||
$provider = new ActivationKeyProvider;
|
||||
$user = User::factory()->create();
|
||||
$plan1 = Plan::factory()->create();
|
||||
$plan2 = Plan::factory()->create();
|
||||
|
||||
$subscriptionData = $provider->createSubscription($user, $plan1);
|
||||
|
||||
expect(fn () => $provider->updateSubscription(
|
||||
new class($subscriptionData['provider_subscription_id'])
|
||||
{
|
||||
public $provider_subscription_id;
|
||||
|
||||
public function __construct($id)
|
||||
{
|
||||
$this->provider_subscription_id = $id;
|
||||
}
|
||||
},
|
||||
$plan2
|
||||
))->toThrow('Activation keys do not support plan updates');
|
||||
});
|
||||
|
||||
test('cannot pause or resume subscription', function () {
|
||||
$provider = new ActivationKeyProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect($provider->pauseSubscription($mockSubscription))->toBeFalse();
|
||||
expect($provider->resumeSubscription($mockSubscription))->toBeFalse();
|
||||
});
|
||||
|
||||
test('does not support trials or coupons', function () {
|
||||
$provider = new ActivationKeyProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect($provider->startTrial($mockSubscription, 7))->toBeFalse();
|
||||
expect($provider->removeCoupon($mockSubscription))->toBeFalse();
|
||||
|
||||
expect(fn () => $provider->applyCoupon($mockSubscription, 'DISCOUNT10'))
|
||||
->toThrow(Exception::class, 'Coupons not supported for activation keys');
|
||||
});
|
||||
|
||||
test('upcoming invoice returns empty data', function () {
|
||||
$provider = new ActivationKeyProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
$invoice = $provider->getUpcomingInvoice($mockSubscription);
|
||||
|
||||
expect($invoice['amount_due'])->toBe(0);
|
||||
expect($invoice['currency'])->toBe('USD');
|
||||
expect($invoice['next_payment_date'])->toBeNull();
|
||||
});
|
||||
|
||||
test('has correct cancellation terms', function () {
|
||||
$provider = new ActivationKeyProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
$terms = $provider->getCancellationTerms($mockSubscription);
|
||||
|
||||
expect($terms['immediate_cancellation'])->toBeTrue();
|
||||
expect($terms['refund_policy'])->toBe('non_refundable');
|
||||
expect($terms['cancellation_effective'])->toBe('immediately');
|
||||
expect($terms['billing_cycle_proration'])->toBeFalse();
|
||||
});
|
||||
|
||||
test('can export subscription data', function () {
|
||||
$provider = new ActivationKeyProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
|
||||
public $provider_subscription_id = 'test_id';
|
||||
|
||||
public $provider_data = ['test' => 'data'];
|
||||
};
|
||||
|
||||
$data = $provider->exportSubscriptionData($mockSubscription);
|
||||
|
||||
expect($data['provider'])->toBe('activation_key');
|
||||
expect($data['provider_subscription_id'])->toBe('test_id');
|
||||
expect($data['data'])->toBe(['test' => 'data']);
|
||||
});
|
||||
|
||||
test('cannot import subscription data', function () {
|
||||
$provider = new ActivationKeyProvider;
|
||||
$mockUser = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
$subscriptionData = ['provider' => 'activation_key', 'data' => []];
|
||||
|
||||
expect(fn () => $provider->importSubscriptionData($mockUser, $subscriptionData))
|
||||
->toThrow(Exception::class, 'Import to activation keys not implemented');
|
||||
});
|
||||
|
||||
test('has correct method signatures', function () {
|
||||
$provider = new ActivationKeyProvider;
|
||||
|
||||
// Test that all required methods exist and have correct signatures
|
||||
$reflection = new ReflectionClass($provider);
|
||||
|
||||
$methods = [
|
||||
'getName' => 0,
|
||||
'isActive' => 0,
|
||||
'supportsRecurring' => 0,
|
||||
'supportsOneTime' => 0,
|
||||
'getSupportedCurrencies' => 0,
|
||||
'calculateFees' => 1,
|
||||
'createSubscription' => 3,
|
||||
'cancelSubscription' => 2,
|
||||
'updateSubscription' => 2,
|
||||
'pauseSubscription' => 1,
|
||||
'resumeSubscription' => 1,
|
||||
'getSubscriptionDetails' => 1,
|
||||
'createCheckoutSession' => 3,
|
||||
'createCustomerPortalSession' => 1,
|
||||
'processWebhook' => 1,
|
||||
'validateWebhook' => 1,
|
||||
'getConfiguration' => 0,
|
||||
'syncSubscriptionStatus' => 1,
|
||||
'getPaymentMethodDetails' => 1,
|
||||
'processRefund' => 3,
|
||||
'getTransactionHistory' => 2,
|
||||
'getSubscriptionMetadata' => 1,
|
||||
'updateSubscriptionMetadata' => 2,
|
||||
'startTrial' => 2,
|
||||
'applyCoupon' => 2,
|
||||
'removeCoupon' => 1,
|
||||
'getUpcomingInvoice' => 1,
|
||||
'retryFailedPayment' => 1,
|
||||
'canModifySubscription' => 1,
|
||||
'getCancellationTerms' => 1,
|
||||
'exportSubscriptionData' => 1,
|
||||
'importSubscriptionData' => 2,
|
||||
'redeemActivationKey' => 2,
|
||||
];
|
||||
|
||||
foreach ($methods as $methodName => $expectedParams) {
|
||||
expect($reflection->hasMethod($methodName))->toBeTrue();
|
||||
expect($reflection->getMethod($methodName)->getNumberOfParameters())->toBe($expectedParams);
|
||||
}
|
||||
});
|
||||
157
tests/Unit/CryptoProviderTest.php
Normal file
157
tests/Unit/CryptoProviderTest.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
use App\Services\Payments\Providers\CryptoProvider;
|
||||
|
||||
test('crypto provider has correct name and capabilities', function () {
|
||||
$provider = new CryptoProvider;
|
||||
|
||||
expect($provider->getName())->toBe('crypto');
|
||||
expect($provider->supportsRecurring())->toBeTrue();
|
||||
expect($provider->supportsOneTime())->toBeTrue();
|
||||
expect($provider->getSupportedCurrencies())->toBe(['USD']);
|
||||
});
|
||||
|
||||
test('crypto provider is inactive without webhook secret', function () {
|
||||
$provider = new CryptoProvider;
|
||||
|
||||
expect($provider->isActive())->toBeFalse();
|
||||
});
|
||||
|
||||
test('crypto provider is active with webhook secret', function () {
|
||||
$provider = new CryptoProvider(['webhook_secret' => 'test-secret']);
|
||||
|
||||
expect($provider->isActive())->toBeTrue();
|
||||
});
|
||||
|
||||
test('can convert usd to crypto', function () {
|
||||
$provider = new CryptoProvider;
|
||||
|
||||
$btcAmount = $provider->convertUsdToCrypto(100.00, 'BTC');
|
||||
expect($btcAmount)->toBeFloat();
|
||||
expect($btcAmount)->toBeGreaterThan(0);
|
||||
|
||||
$ethAmount = $provider->convertUsdToCrypto(100.00, 'ETH');
|
||||
expect($ethAmount)->toBeFloat();
|
||||
expect($ethAmount)->toBeGreaterThan(0);
|
||||
|
||||
$usdtAmount = $provider->convertUsdToCrypto(100.00, 'USDT');
|
||||
expect($usdtAmount)->toBeFloat();
|
||||
expect($usdtAmount)->toBe(100.00); // USDT should be 1:1
|
||||
|
||||
$ltcAmount = $provider->convertUsdToCrypto(100.00, 'LTC');
|
||||
expect($ltcAmount)->toBeFloat();
|
||||
expect($ltcAmount)->toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('can calculate fees correctly', function () {
|
||||
$provider = new CryptoProvider;
|
||||
|
||||
$fees = $provider->calculateFees(100.00);
|
||||
|
||||
expect($fees['fixed_fee'])->toBe(0);
|
||||
expect($fees['percentage_fee'])->toBe(1.5); // 1% network + 0.5% service
|
||||
expect($fees['total_fee'])->toBe(1.5);
|
||||
expect($fees['net_amount'])->toBe(98.5);
|
||||
});
|
||||
|
||||
test('crypto provider has correct refund method', function () {
|
||||
$provider = new CryptoProvider;
|
||||
|
||||
// Test that the method exists and returns expected structure
|
||||
$reflection = new ReflectionClass($provider);
|
||||
$method = $reflection->getMethod('processRefund');
|
||||
|
||||
expect($method)->not->toBeNull();
|
||||
expect($method->getNumberOfParameters())->toBe(3);
|
||||
});
|
||||
|
||||
test('can get configuration', function () {
|
||||
$config = [
|
||||
'webhook_secret' => 'test-secret',
|
||||
'confirmation_timeout_minutes' => 45,
|
||||
];
|
||||
$provider = new CryptoProvider($config);
|
||||
|
||||
expect($provider->getConfiguration())->toHaveKey('webhook_secret', 'test-secret');
|
||||
expect($provider->getConfiguration())->toHaveKey('confirmation_timeout_minutes', 45);
|
||||
});
|
||||
|
||||
test('crypto provider has correct cancellation terms', function () {
|
||||
$provider = new CryptoProvider;
|
||||
|
||||
// Test that the method exists and returns expected structure
|
||||
$reflection = new ReflectionClass($provider);
|
||||
$method = $reflection->getMethod('getCancellationTerms');
|
||||
|
||||
expect($method)->not->toBeNull();
|
||||
expect($method->getNumberOfParameters())->toBe(1);
|
||||
});
|
||||
|
||||
test('crypto provider has correct upcoming invoice method', function () {
|
||||
$provider = new CryptoProvider;
|
||||
|
||||
// Test that the method exists and returns expected structure
|
||||
$reflection = new ReflectionClass($provider);
|
||||
$method = $reflection->getMethod('getUpcomingInvoice');
|
||||
|
||||
expect($method)->not->toBeNull();
|
||||
expect($method->getNumberOfParameters())->toBe(1);
|
||||
});
|
||||
|
||||
test('crypto provider has correct export data method', function () {
|
||||
$provider = new CryptoProvider;
|
||||
|
||||
// Test that the method exists and returns expected structure
|
||||
$reflection = new ReflectionClass($provider);
|
||||
$method = $reflection->getMethod('exportSubscriptionData');
|
||||
|
||||
expect($method)->not->toBeNull();
|
||||
expect($method->getNumberOfParameters())->toBe(1);
|
||||
});
|
||||
|
||||
test('crypto provider has correct import data method', function () {
|
||||
$provider = new CryptoProvider;
|
||||
|
||||
// Test that the method exists and returns expected structure
|
||||
$reflection = new ReflectionClass($provider);
|
||||
$method = $reflection->getMethod('importSubscriptionData');
|
||||
|
||||
expect($method)->not->toBeNull();
|
||||
expect($method->getNumberOfParameters())->toBe(2);
|
||||
});
|
||||
|
||||
test('crypto provider does not support trials', function () {
|
||||
$provider = new CryptoProvider;
|
||||
|
||||
// Test that the method exists and returns expected structure
|
||||
$reflection = new ReflectionClass($provider);
|
||||
$method = $reflection->getMethod('startTrial');
|
||||
|
||||
expect($method)->not->toBeNull();
|
||||
expect($method->getNumberOfParameters())->toBe(2);
|
||||
});
|
||||
|
||||
test('crypto provider has correct coupon methods', function () {
|
||||
$provider = new CryptoProvider;
|
||||
|
||||
// Test that the methods exist and return expected structure
|
||||
$reflection = new ReflectionClass($provider);
|
||||
$applyMethod = $reflection->getMethod('applyCoupon');
|
||||
$removeMethod = $reflection->getMethod('removeCoupon');
|
||||
|
||||
expect($applyMethod)->not->toBeNull();
|
||||
expect($applyMethod->getNumberOfParameters())->toBe(2);
|
||||
expect($removeMethod)->not->toBeNull();
|
||||
expect($removeMethod->getNumberOfParameters())->toBe(1);
|
||||
});
|
||||
|
||||
test('crypto provider has correct customer portal method', function () {
|
||||
$provider = new CryptoProvider;
|
||||
|
||||
// Test that the method exists and returns expected structure
|
||||
$reflection = new ReflectionClass($provider);
|
||||
$method = $reflection->getMethod('createCustomerPortalSession');
|
||||
|
||||
expect($method)->not->toBeNull();
|
||||
expect($method->getNumberOfParameters())->toBe(1);
|
||||
});
|
||||
410
tests/Unit/OxapayProviderTest.php
Normal file
410
tests/Unit/OxapayProviderTest.php
Normal file
@@ -0,0 +1,410 @@
|
||||
<?php
|
||||
|
||||
use App\Services\Payments\Providers\OxapayProvider;
|
||||
|
||||
test('oxapay provider has correct name and capabilities', function () {
|
||||
$provider = new OxapayProvider;
|
||||
|
||||
expect($provider->getName())->toBe('oxapay');
|
||||
expect($provider->supportsRecurring())->toBeFalse();
|
||||
expect($provider->supportsOneTime())->toBeTrue();
|
||||
expect($provider->getSupportedCurrencies())->toBeArray();
|
||||
});
|
||||
|
||||
test('oxapay provider is inactive without api key', function () {
|
||||
$provider = new OxapayProvider;
|
||||
|
||||
expect($provider->isActive())->toBeFalse();
|
||||
});
|
||||
|
||||
test('oxapay provider is active with merchant api key', function () {
|
||||
$provider = new OxapayProvider(['merchant_api_key' => 'test-api-key']);
|
||||
|
||||
expect($provider->isActive())->toBeTrue();
|
||||
});
|
||||
|
||||
test('can calculate fees correctly', function () {
|
||||
$provider = new OxapayProvider;
|
||||
|
||||
$fees = $provider->calculateFees(100.00);
|
||||
|
||||
expect($fees['fixed_fee'])->toBe(0);
|
||||
expect($fees['percentage_fee'])->toBe(0.5); // 0.5% fee
|
||||
expect($fees['total_fee'])->toBe(0.5);
|
||||
expect($fees['net_amount'])->toBe(99.5);
|
||||
});
|
||||
|
||||
test('can get configuration', function () {
|
||||
$config = [
|
||||
'merchant_api_key' => 'test-api-key',
|
||||
'webhook_url' => 'https://example.com/webhook',
|
||||
'sandbox' => true,
|
||||
];
|
||||
$provider = new OxapayProvider($config);
|
||||
|
||||
expect($provider->getConfiguration())->toHaveKey('merchant_api_key', 'test-api-key');
|
||||
expect($provider->getConfiguration())->toHaveKey('webhook_url', 'https://example.com/webhook');
|
||||
expect($provider->getConfiguration())->toHaveKey('sandbox', true);
|
||||
});
|
||||
|
||||
test('can validate webhook signature', function () {
|
||||
$provider = new OxapayProvider(['merchant_api_key' => 'test-secret']);
|
||||
|
||||
$request = new \Illuminate\Http\Request([], [], [], [], [], [], '{"test": "payload"}');
|
||||
$signature = hash_hmac('sha512', '{"test": "payload"}', 'test-secret');
|
||||
$request->headers->set('HMAC', $signature);
|
||||
|
||||
expect($provider->validateWebhook($request))->toBeTrue();
|
||||
});
|
||||
|
||||
test('webhook validation fails with invalid signature', function () {
|
||||
$provider = new OxapayProvider(['merchant_api_key' => 'test-secret']);
|
||||
|
||||
$request = new \Illuminate\Http\Request([], [], [], [], [], [], '{"test": "payload"}');
|
||||
$request->headers->set('HMAC', 'invalid-signature');
|
||||
|
||||
expect($provider->validateWebhook($request))->toBeFalse();
|
||||
});
|
||||
|
||||
test('webhook validation fails without signature', function () {
|
||||
$provider = new OxapayProvider(['merchant_api_key' => 'test-secret']);
|
||||
|
||||
$request = new \Illuminate\Http\Request([], [], [], [], [], [], '{"test": "payload"}');
|
||||
|
||||
expect($provider->validateWebhook($request))->toBeFalse();
|
||||
});
|
||||
|
||||
test('can process webhook with valid signature', function () {
|
||||
$provider = new OxapayProvider(['merchant_api_key' => 'test-secret']);
|
||||
|
||||
$payload = json_encode([
|
||||
'status' => 'Paid',
|
||||
'track_id' => 'test-track-id',
|
||||
'type' => 'payment',
|
||||
'amount' => 100,
|
||||
'currency' => 'USDT',
|
||||
]);
|
||||
|
||||
$request = new \Illuminate\Http\Request([], [], [], [], [], [], $payload);
|
||||
$signature = hash_hmac('sha512', $payload, 'test-secret');
|
||||
$request->headers->set('HMAC', $signature);
|
||||
|
||||
$result = $provider->processWebhook($request);
|
||||
|
||||
expect($result['success'])->toBeTrue();
|
||||
expect($result['event_type'])->toBe('Paid');
|
||||
expect($result['provider_transaction_id'])->toBe('test-track-id');
|
||||
expect($result['processed'])->toBeTrue();
|
||||
expect($result['type'])->toBe('payment');
|
||||
});
|
||||
|
||||
test('webhook processing fails with invalid signature', function () {
|
||||
$provider = new OxapayProvider(['merchant_api_key' => 'test-secret']);
|
||||
|
||||
$payload = json_encode([
|
||||
'status' => 'Paid',
|
||||
'track_id' => 'test-track-id',
|
||||
]);
|
||||
|
||||
$request = new \Illuminate\Http\Request([], [], [], [], [], [], $payload);
|
||||
$request->headers->set('HMAC', 'invalid-signature');
|
||||
|
||||
$result = $provider->processWebhook($request);
|
||||
|
||||
expect($result['success'])->toBeFalse();
|
||||
expect($result['processed'])->toBeFalse();
|
||||
expect($result['error'])->toBe('Invalid webhook signature');
|
||||
});
|
||||
|
||||
test('cannot create recurring subscription', function () {
|
||||
$provider = new OxapayProvider;
|
||||
$mockUser = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
$mockPlan = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect(fn () => $provider->createSubscription($mockUser, $mockPlan))
|
||||
->toThrow('OxaPay does not support recurring subscriptions');
|
||||
});
|
||||
|
||||
test('cannot cancel subscription', function () {
|
||||
$provider = new OxapayProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect(fn () => $provider->cancelSubscription($mockSubscription, 'test'))
|
||||
->toThrow('OxaPay does not support recurring subscriptions');
|
||||
});
|
||||
|
||||
test('cannot update subscription', function () {
|
||||
$provider = new OxapayProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
$mockPlan = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect(fn () => $provider->updateSubscription($mockSubscription, $mockPlan))
|
||||
->toThrow('OxaPay does not support recurring subscriptions');
|
||||
});
|
||||
|
||||
test('cannot pause subscription', function () {
|
||||
$provider = new OxapayProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect(fn () => $provider->pauseSubscription($mockSubscription))
|
||||
->toThrow('OxaPay does not support recurring subscriptions');
|
||||
});
|
||||
|
||||
test('cannot resume subscription', function () {
|
||||
$provider = new OxapayProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect(fn () => $provider->resumeSubscription($mockSubscription))
|
||||
->toThrow('OxaPay does not support recurring subscriptions');
|
||||
});
|
||||
|
||||
test('cannot get subscription details', function () {
|
||||
$provider = new OxapayProvider;
|
||||
|
||||
expect(fn () => $provider->getSubscriptionDetails('test-id'))
|
||||
->toThrow('OxaPay does not support recurring subscriptions');
|
||||
});
|
||||
|
||||
test('cannot create customer portal session', function () {
|
||||
$provider = new OxapayProvider;
|
||||
$mockUser = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect(fn () => $provider->createCustomerPortalSession($mockUser))
|
||||
->toThrow('OxaPay does not provide customer portal functionality');
|
||||
});
|
||||
|
||||
test('cannot sync subscription status', function () {
|
||||
$provider = new OxapayProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect(fn () => $provider->syncSubscriptionStatus($mockSubscription))
|
||||
->toThrow('OxaPay does not support recurring subscriptions');
|
||||
});
|
||||
|
||||
test('cannot process refunds through api', function () {
|
||||
$provider = new OxapayProvider;
|
||||
|
||||
$result = $provider->processRefund('test-id', 100.00, 'User requested');
|
||||
|
||||
expect($result['success'])->toBeFalse();
|
||||
expect($result['error'])->toBe('OxaPay refunds must be processed manually via payouts');
|
||||
});
|
||||
|
||||
test('cannot apply coupon to subscription', function () {
|
||||
$provider = new OxapayProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect(fn () => $provider->applyCoupon($mockSubscription, 'DISCOUNT10'))
|
||||
->toThrow('OxaPay does not support recurring subscriptions');
|
||||
});
|
||||
|
||||
test('cannot remove coupon from subscription', function () {
|
||||
$provider = new OxapayProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect($provider->removeCoupon($mockSubscription))->toBeFalse();
|
||||
});
|
||||
|
||||
test('cannot get upcoming invoice', function () {
|
||||
$provider = new OxapayProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect(fn () => $provider->getUpcomingInvoice($mockSubscription))
|
||||
->toThrow('OxaPay does not support recurring subscriptions');
|
||||
});
|
||||
|
||||
test('cannot retry failed payment', function () {
|
||||
$provider = new OxapayProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect(fn () => $provider->retryFailedPayment($mockSubscription))
|
||||
->toThrow('OxaPay does not support recurring subscriptions');
|
||||
});
|
||||
|
||||
test('cannot start trial', function () {
|
||||
$provider = new OxapayProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect($provider->startTrial($mockSubscription, 7))->toBeFalse();
|
||||
});
|
||||
|
||||
test('cannot modify subscription', function () {
|
||||
$provider = new OxapayProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect($provider->canModifySubscription($mockSubscription))->toBeFalse();
|
||||
});
|
||||
|
||||
test('has correct cancellation terms', function () {
|
||||
$provider = new OxapayProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
$terms = $provider->getCancellationTerms($mockSubscription);
|
||||
|
||||
expect($terms['immediate_cancellation'])->toBeTrue();
|
||||
expect($terms['refund_policy'])->toBe('no_refunds_crypto');
|
||||
expect($terms['cancellation_effective'])->toBe('immediately');
|
||||
expect($terms['billing_cycle_proration'])->toBeFalse();
|
||||
});
|
||||
|
||||
test('cannot export subscription data', function () {
|
||||
$provider = new OxapayProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect(fn () => $provider->exportSubscriptionData($mockSubscription))
|
||||
->toThrow('OxaPay does not support recurring subscriptions');
|
||||
});
|
||||
|
||||
test('cannot import subscription data', function () {
|
||||
$provider = new OxapayProvider;
|
||||
$mockUser = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
$subscriptionData = ['provider' => 'oxapay', 'data' => []];
|
||||
|
||||
expect(fn () => $provider->importSubscriptionData($mockUser, $subscriptionData))
|
||||
->toThrow('OxaPay does not support recurring subscriptions');
|
||||
});
|
||||
|
||||
test('cannot get subscription metadata', function () {
|
||||
$provider = new OxapayProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect(fn () => $provider->getSubscriptionMetadata($mockSubscription))
|
||||
->toThrow('OxaPay does not support recurring subscriptions');
|
||||
});
|
||||
|
||||
test('cannot update subscription metadata', function () {
|
||||
$provider = new OxapayProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect(fn () => $provider->updateSubscriptionMetadata($mockSubscription, ['test' => 'data']))
|
||||
->toThrow('OxaPay does not support recurring subscriptions');
|
||||
});
|
||||
|
||||
test('has correct method signatures', function () {
|
||||
$provider = new OxapayProvider;
|
||||
|
||||
// Test that all required methods exist and have correct signatures
|
||||
$reflection = new ReflectionClass($provider);
|
||||
|
||||
$methods = [
|
||||
'getName' => 0,
|
||||
'isActive' => 0,
|
||||
'supportsRecurring' => 0,
|
||||
'supportsOneTime' => 0,
|
||||
'getSupportedCurrencies' => 0,
|
||||
'calculateFees' => 1,
|
||||
'createSubscription' => 3,
|
||||
'cancelSubscription' => 2,
|
||||
'updateSubscription' => 2,
|
||||
'pauseSubscription' => 1,
|
||||
'resumeSubscription' => 1,
|
||||
'getSubscriptionDetails' => 1,
|
||||
'createCheckoutSession' => 3,
|
||||
'createCustomerPortalSession' => 1,
|
||||
'processWebhook' => 1,
|
||||
'validateWebhook' => 1,
|
||||
'getConfiguration' => 0,
|
||||
'syncSubscriptionStatus' => 1,
|
||||
'getPaymentMethodDetails' => 1,
|
||||
'processRefund' => 3,
|
||||
'getTransactionHistory' => 2,
|
||||
'getSubscriptionMetadata' => 1,
|
||||
'updateSubscriptionMetadata' => 2,
|
||||
'startTrial' => 2,
|
||||
'applyCoupon' => 2,
|
||||
'removeCoupon' => 1,
|
||||
'getUpcomingInvoice' => 1,
|
||||
'retryFailedPayment' => 1,
|
||||
'canModifySubscription' => 1,
|
||||
'getCancellationTerms' => 1,
|
||||
'exportSubscriptionData' => 1,
|
||||
'importSubscriptionData' => 2,
|
||||
];
|
||||
|
||||
foreach ($methods as $methodName => $expectedParams) {
|
||||
expect($reflection->hasMethod($methodName))->toBeTrue();
|
||||
expect($reflection->getMethod($methodName)->getNumberOfParameters())->toBe($expectedParams);
|
||||
}
|
||||
});
|
||||
|
||||
test('uses correct base url for production', function () {
|
||||
$provider = new OxapayProvider(['sandbox' => false]);
|
||||
|
||||
// Access the baseUrl property via reflection
|
||||
$reflection = new ReflectionClass($provider);
|
||||
$baseUrlProperty = $reflection->getProperty('baseUrl');
|
||||
$baseUrlProperty->setAccessible(true);
|
||||
|
||||
expect($baseUrlProperty->getValue($provider))->toBe('https://api.oxapay.com/v1');
|
||||
});
|
||||
|
||||
test('uses correct base url for sandbox', function () {
|
||||
$provider = new OxapayProvider(['sandbox' => true]);
|
||||
|
||||
// Access the baseUrl property via reflection
|
||||
$reflection = new ReflectionClass($provider);
|
||||
$baseUrlProperty = $reflection->getProperty('baseUrl');
|
||||
$baseUrlProperty->setAccessible(true);
|
||||
|
||||
expect($baseUrlProperty->getValue($provider))->toBe('https://api-sandbox.oxapay.com/v1');
|
||||
});
|
||||
227
tests/Unit/PaymentConfigurationManagerTest.php
Normal file
227
tests/Unit/PaymentConfigurationManagerTest.php
Normal file
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
use App\Services\Payments\PaymentConfigurationManager;
|
||||
use App\Services\Payments\ProviderRegistry;
|
||||
|
||||
test('can initialize payment providers', function () {
|
||||
$registry = new ProviderRegistry;
|
||||
$manager = new PaymentConfigurationManager($registry);
|
||||
|
||||
$manager->initializeProviders();
|
||||
|
||||
// Check that activation key provider is always registered
|
||||
expect($registry->has('activation_key'))->toBeTrue();
|
||||
|
||||
// Other providers should only be registered if configured
|
||||
$providers = $registry->getAllProviders();
|
||||
expect($providers)->toHaveKey('activation_key');
|
||||
});
|
||||
|
||||
test('can get provider configuration', function () {
|
||||
$registry = new ProviderRegistry;
|
||||
$manager = new PaymentConfigurationManager($registry);
|
||||
|
||||
$config = $manager->getProviderConfig('activation_key');
|
||||
|
||||
expect($config)->toBeArray();
|
||||
expect($config)->toHaveKey('key_prefix');
|
||||
expect($config)->toHaveKey('key_length');
|
||||
});
|
||||
|
||||
test('can update provider configuration', function () {
|
||||
$registry = new ProviderRegistry;
|
||||
$manager = new PaymentConfigurationManager($registry);
|
||||
|
||||
$newConfig = ['key_prefix' => 'TEST-'];
|
||||
$manager->updateProviderConfig('activation_key', $newConfig);
|
||||
|
||||
$config = $manager->getProviderConfig('activation_key');
|
||||
expect($config['key_prefix'])->toBe('TEST-');
|
||||
});
|
||||
|
||||
test('can validate stripe configuration', function () {
|
||||
$registry = new ProviderRegistry;
|
||||
$manager = new PaymentConfigurationManager($registry);
|
||||
|
||||
// Invalid configuration
|
||||
$result = $manager->validateProviderConfig('stripe', []);
|
||||
expect($result['valid'])->toBeFalse();
|
||||
expect($result['errors'])->toContain('Stripe secret key is required');
|
||||
|
||||
// Valid configuration
|
||||
$result = $manager->validateProviderConfig('stripe', [
|
||||
'secret_key' => 'sk_test_123',
|
||||
'publishable_key' => 'pk_test_123',
|
||||
]);
|
||||
expect($result['valid'])->toBeTrue();
|
||||
expect($result['errors'])->toBeEmpty();
|
||||
});
|
||||
|
||||
test('can validate lemon squeezy configuration', function () {
|
||||
$registry = new ProviderRegistry;
|
||||
$manager = new PaymentConfigurationManager($registry);
|
||||
|
||||
// Invalid configuration
|
||||
$result = $manager->validateProviderConfig('lemon_squeezy', []);
|
||||
expect($result['valid'])->toBeFalse();
|
||||
expect($result['errors'])->toContain('Lemon Squeezy API key is required');
|
||||
|
||||
// Valid configuration
|
||||
$result = $manager->validateProviderConfig('lemon_squeezy', [
|
||||
'api_key' => 'test_key',
|
||||
'store_id' => 'test_store',
|
||||
]);
|
||||
expect($result['valid'])->toBeTrue();
|
||||
expect($result['errors'])->toBeEmpty();
|
||||
});
|
||||
|
||||
test('can validate crypto configuration', function () {
|
||||
$registry = new ProviderRegistry;
|
||||
$manager = new PaymentConfigurationManager($registry);
|
||||
|
||||
// Invalid configuration
|
||||
$result = $manager->validateProviderConfig('crypto', []);
|
||||
expect($result['valid'])->toBeFalse();
|
||||
expect($result['errors'])->toContain('Crypto webhook secret is required');
|
||||
|
||||
// Valid configuration
|
||||
$result = $manager->validateProviderConfig('crypto', [
|
||||
'webhook_secret' => 'test_secret',
|
||||
]);
|
||||
expect($result['valid'])->toBeTrue();
|
||||
expect($result['errors'])->toBeEmpty();
|
||||
});
|
||||
|
||||
test('activation key configuration is always valid', function () {
|
||||
$registry = new ProviderRegistry;
|
||||
$manager = new PaymentConfigurationManager($registry);
|
||||
|
||||
$result = $manager->validateProviderConfig('activation_key', []);
|
||||
expect($result['valid'])->toBeTrue();
|
||||
expect($result['errors'])->toBeEmpty();
|
||||
});
|
||||
|
||||
test('can get provider status', function () {
|
||||
$registry = new ProviderRegistry;
|
||||
$manager = new PaymentConfigurationManager($registry);
|
||||
|
||||
$manager->initializeProviders();
|
||||
$status = $manager->getProviderStatus();
|
||||
|
||||
expect($status)->toBeArray();
|
||||
expect($status)->toHaveKey('activation_key');
|
||||
|
||||
$activationKeyStatus = $status['activation_key'];
|
||||
expect($activationKeyStatus)->toHaveKey('name');
|
||||
expect($activationKeyStatus)->toHaveKey('active');
|
||||
expect($activationKeyStatus)->toHaveKey('supports_recurring');
|
||||
expect($activationKeyStatus)->toHaveKey('supports_one_time');
|
||||
expect($activationKeyStatus)->toHaveKey('supported_currencies');
|
||||
});
|
||||
|
||||
test('can sanitize configuration for display', function () {
|
||||
$registry = new ProviderRegistry;
|
||||
$manager = new PaymentConfigurationManager($registry);
|
||||
|
||||
$manager->updateProviderConfig('test', [
|
||||
'secret_key' => 'sk_test_1234567890',
|
||||
'public_key' => 'pk_test_1234567890',
|
||||
'other_field' => 'visible_value',
|
||||
]);
|
||||
|
||||
$status = $manager->getProviderStatus();
|
||||
|
||||
// Since 'test' is not a real provider, we need to check the sanitize logic indirectly
|
||||
$config = $manager->getProviderConfig('test');
|
||||
expect($config['secret_key'])->toBe('sk_test_1234567890'); // Original is preserved
|
||||
});
|
||||
|
||||
test('can get default provider', function () {
|
||||
$registry = new ProviderRegistry;
|
||||
$manager = new PaymentConfigurationManager($registry);
|
||||
|
||||
$manager->initializeProviders();
|
||||
$defaultProvider = $manager->getDefaultProvider();
|
||||
|
||||
expect($defaultProvider)->toBe('activation_key'); // Should fallback to activation key
|
||||
});
|
||||
|
||||
test('can test provider connectivity', function () {
|
||||
$registry = new ProviderRegistry;
|
||||
$manager = new PaymentConfigurationManager($registry);
|
||||
|
||||
$manager->initializeProviders();
|
||||
|
||||
// Test existing provider
|
||||
$result = $manager->testProviderConnectivity('activation_key');
|
||||
expect($result['success'])->toBeTrue();
|
||||
expect($result['details']['name'])->toBe('activation_key');
|
||||
|
||||
// Test non-existing provider
|
||||
$result = $manager->testProviderConnectivity('non_existent');
|
||||
expect($result['success'])->toBeFalse();
|
||||
expect($result['error'])->toBe('Provider not registered');
|
||||
});
|
||||
|
||||
test('can toggle provider', function () {
|
||||
$registry = new ProviderRegistry;
|
||||
$manager = new PaymentConfigurationManager($registry);
|
||||
|
||||
$manager->initializeProviders();
|
||||
|
||||
// Activation key should be registered
|
||||
expect($registry->has('activation_key'))->toBeTrue();
|
||||
|
||||
// Disable activation key
|
||||
$result = $manager->toggleProvider('activation_key', false);
|
||||
expect($result)->toBeTrue();
|
||||
expect($registry->has('activation_key'))->toBeFalse();
|
||||
|
||||
// Re-enable activation key
|
||||
$result = $manager->toggleProvider('activation_key', true);
|
||||
expect($result)->toBeTrue();
|
||||
expect($registry->has('activation_key'))->toBeTrue();
|
||||
});
|
||||
|
||||
test('can refresh configurations', function () {
|
||||
$registry = new ProviderRegistry;
|
||||
$manager = new PaymentConfigurationManager($registry);
|
||||
|
||||
// Update a configuration
|
||||
$manager->updateProviderConfig('activation_key', ['key_prefix' => 'REFRESH-']);
|
||||
|
||||
// Refresh configurations
|
||||
$manager->refreshConfigurations();
|
||||
|
||||
// Configuration should still be available
|
||||
$config = $manager->getProviderConfig('activation_key');
|
||||
expect($config)->toHaveKey('key_prefix');
|
||||
});
|
||||
|
||||
test('handles unknown provider validation', function () {
|
||||
$registry = new ProviderRegistry;
|
||||
$manager = new PaymentConfigurationManager($registry);
|
||||
|
||||
$result = $manager->validateProviderConfig('unknown_provider', []);
|
||||
expect($result['valid'])->toBeFalse();
|
||||
expect($result['errors'])->toContain('Unknown provider: unknown_provider');
|
||||
});
|
||||
|
||||
test('handles unknown provider toggle', function () {
|
||||
$registry = new ProviderRegistry;
|
||||
$manager = new PaymentConfigurationManager($registry);
|
||||
|
||||
$result = $manager->toggleProvider('unknown_provider', true);
|
||||
expect($result)->toBeFalse();
|
||||
});
|
||||
|
||||
test('activation key provider is always available', function () {
|
||||
$registry = new ProviderRegistry;
|
||||
$manager = new PaymentConfigurationManager($registry);
|
||||
|
||||
$manager->initializeProviders();
|
||||
|
||||
// Even without any configuration, activation key should be available
|
||||
expect($registry->has('activation_key'))->toBeTrue();
|
||||
expect($registry->get('activation_key')->isActive())->toBeTrue();
|
||||
});
|
||||
295
tests/Unit/PolarProviderTest.php
Normal file
295
tests/Unit/PolarProviderTest.php
Normal file
@@ -0,0 +1,295 @@
|
||||
<?php
|
||||
|
||||
use App\Services\Payments\Providers\PolarProvider;
|
||||
|
||||
test('polar provider has correct name and capabilities', function () {
|
||||
$provider = new PolarProvider;
|
||||
|
||||
expect($provider->getName())->toBe('polar');
|
||||
expect($provider->supportsRecurring())->toBeTrue();
|
||||
expect($provider->supportsOneTime())->toBeTrue();
|
||||
expect($provider->getSupportedCurrencies())->toBe(['USD']);
|
||||
});
|
||||
|
||||
test('polar provider is inactive without api key', function () {
|
||||
$provider = new PolarProvider;
|
||||
|
||||
expect($provider->isActive())->toBeFalse();
|
||||
});
|
||||
|
||||
test('polar provider is active with api key', function () {
|
||||
$provider = new PolarProvider(['api_key' => 'test-api-key']);
|
||||
|
||||
expect($provider->isActive())->toBeTrue();
|
||||
});
|
||||
|
||||
test('can calculate fees correctly', function () {
|
||||
$provider = new PolarProvider;
|
||||
|
||||
$fees = $provider->calculateFees(100.00);
|
||||
|
||||
expect($fees['fixed_fee'])->toBe(0);
|
||||
expect($fees['percentage_fee'])->toBe(6.0); // 6% fee
|
||||
expect($fees['total_fee'])->toBe(6.0);
|
||||
expect($fees['net_amount'])->toBe(94.0);
|
||||
});
|
||||
|
||||
test('can get configuration', function () {
|
||||
$config = [
|
||||
'api_key' => 'test-api-key',
|
||||
'webhook_secret' => 'test-secret',
|
||||
];
|
||||
$provider = new PolarProvider($config);
|
||||
|
||||
expect($provider->getConfiguration())->toHaveKey('api_key', 'test-api-key');
|
||||
expect($provider->getConfiguration())->toHaveKey('webhook_secret', 'test-secret');
|
||||
});
|
||||
|
||||
test('can validate webhook signature', function () {
|
||||
$provider = new PolarProvider(['webhook_secret' => 'test-secret']);
|
||||
|
||||
$request = new \Illuminate\Http\Request([], [], [], [], [], [], 'test-payload');
|
||||
$request->headers->set('Polar-Signature', 'test-signature');
|
||||
|
||||
expect($provider->validateWebhook($request))->toBeFalse(); // Invalid signature
|
||||
});
|
||||
|
||||
test('webhook methods return expected values', function () {
|
||||
$provider = new PolarProvider;
|
||||
$request = new \Illuminate\Http\Request([], [], [], [], [], [], 'test-payload');
|
||||
|
||||
$webhookResult = $provider->processWebhook($request);
|
||||
expect($webhookResult['event_type'])->toBe('unknown');
|
||||
expect($webhookResult['processed'])->toBeFalse();
|
||||
|
||||
expect($provider->validateWebhook($request))->toBeFalse();
|
||||
});
|
||||
|
||||
test('customer portal returns dashboard when no customer', function () {
|
||||
$provider = new PolarProvider;
|
||||
$mockUser = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
// Should throw exception when no customer exists
|
||||
expect($provider->createCustomerPortalSession($mockUser))
|
||||
->toThrow(\Exception::class);
|
||||
});
|
||||
|
||||
test('polar payments are not refundable through API', function () {
|
||||
$provider = new PolarProvider;
|
||||
|
||||
expect($provider->processRefund('test_id', 100.00, 'User requested'))
|
||||
->toThrow('Polar refunds must be processed through Polar dashboard or API directly');
|
||||
});
|
||||
|
||||
test('can get supported currencies', function () {
|
||||
$provider = new PolarProvider;
|
||||
|
||||
$currencies = $provider->getSupportedCurrencies();
|
||||
expect($currencies)->toBe(['USD']);
|
||||
});
|
||||
|
||||
test('has correct method signatures', function () {
|
||||
$provider = new PolarProvider;
|
||||
|
||||
// Test that all required methods exist and have correct signatures
|
||||
$reflection = new ReflectionClass($provider);
|
||||
|
||||
$methods = [
|
||||
'getName' => 0,
|
||||
'isActive' => 0,
|
||||
'supportsRecurring' => 0,
|
||||
'supportsOneTime' => 0,
|
||||
'getSupportedCurrencies' => 0,
|
||||
'calculateFees' => 1,
|
||||
'createSubscription' => 3,
|
||||
'cancelSubscription' => 2,
|
||||
'updateSubscription' => 2,
|
||||
'pauseSubscription' => 1,
|
||||
'resumeSubscription' => 1,
|
||||
'getSubscriptionDetails' => 1,
|
||||
'createCheckoutSession' => 3,
|
||||
'createCustomerPortalSession' => 1,
|
||||
'processWebhook' => 1,
|
||||
'validateWebhook' => 1,
|
||||
'getConfiguration' => 0,
|
||||
'syncSubscriptionStatus' => 1,
|
||||
'getPaymentMethodDetails' => 1,
|
||||
'processRefund' => 3,
|
||||
'getTransactionHistory' => 2,
|
||||
'getSubscriptionMetadata' => 1,
|
||||
'updateSubscriptionMetadata' => 2,
|
||||
'startTrial' => 2,
|
||||
'applyCoupon' => 2,
|
||||
'removeCoupon' => 1,
|
||||
'getUpcomingInvoice' => 1,
|
||||
'retryFailedPayment' => 1,
|
||||
'canModifySubscription' => 1,
|
||||
'getCancellationTerms' => 1,
|
||||
'exportSubscriptionData' => 1,
|
||||
'importSubscriptionData' => 2,
|
||||
];
|
||||
|
||||
foreach ($methods as $methodName => $expectedParams) {
|
||||
expect($reflection->hasMethod($methodName))->toBeTrue();
|
||||
expect($reflection->getMethod($methodName)->getNumberOfParameters())->toBe($expectedParams);
|
||||
}
|
||||
});
|
||||
|
||||
test('has correct cancellation terms', function () {
|
||||
$provider = new PolarProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
$terms = $provider->getCancellationTerms($mockSubscription);
|
||||
|
||||
expect($terms['immediate_cancellation'])->toBeTrue();
|
||||
expect($terms['refund_policy'])->toBe('no_pro_rated_refunds');
|
||||
expect($terms['cancellation_effective'])->toBe('immediately');
|
||||
expect($terms['billing_cycle_proration'])->toBeFalse();
|
||||
});
|
||||
|
||||
test('can export subscription data', function () {
|
||||
$provider = new PolarProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
|
||||
public $provider_subscription_id = 'test_id';
|
||||
|
||||
public $provider_data = ['test' => 'data'];
|
||||
};
|
||||
|
||||
$data = $provider->exportSubscriptionData($mockSubscription);
|
||||
|
||||
expect($data['provider'])->toBe('polar');
|
||||
expect($data['provider_subscription_id'])->toBe('test_id');
|
||||
expect($data['data'])->toBe(['test' => 'data']);
|
||||
});
|
||||
|
||||
test('cannot import subscription data', function () {
|
||||
$provider = new PolarProvider;
|
||||
$mockUser = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
$subscriptionData = ['provider' => 'polar', 'data' => []];
|
||||
|
||||
expect(fn () => $provider->importSubscriptionData($mockUser, $subscriptionData))
|
||||
->toThrow('Import to Polar payments not implemented');
|
||||
});
|
||||
|
||||
test('cannot start trial after subscription', function () {
|
||||
$provider = new PolarProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect($provider->startTrial($mockSubscription, 7))->toBeFalse();
|
||||
});
|
||||
|
||||
test('upcoming invoice returns empty data for non-existent subscription', function () {
|
||||
$provider = new PolarProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
$invoice = $provider->getUpcomingInvoice($mockSubscription);
|
||||
|
||||
expect($invoice['amount_due'])->toBe(0);
|
||||
expect($invoice['currency'])->toBe('USD');
|
||||
expect($invoice['next_payment_date'])->toBeNull();
|
||||
});
|
||||
|
||||
test('cannot remove coupon without subscription', function () {
|
||||
$provider = new PolarProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect($provider->removeCoupon($mockSubscription))->toBeFalse();
|
||||
});
|
||||
|
||||
test('cannot apply coupon without subscription', function () {
|
||||
$provider = new PolarProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect(fn () => $provider->applyCoupon($mockSubscription, 'DISCOUNT10'))
|
||||
->toThrow(\Throwable::class);
|
||||
});
|
||||
|
||||
test('cannot pause subscription without polar subscription', function () {
|
||||
$provider = new PolarProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect(fn () => $provider->pauseSubscription($mockSubscription))
|
||||
->toThrow(\Throwable::class);
|
||||
});
|
||||
|
||||
test('cannot resume subscription without polar subscription', function () {
|
||||
$provider = new PolarProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect(fn () => $provider->resumeSubscription($mockSubscription))
|
||||
->toThrow(\Throwable::class);
|
||||
});
|
||||
|
||||
test('cannot get subscription details without valid id', function () {
|
||||
$provider = new PolarProvider;
|
||||
|
||||
expect(fn () => $provider->getSubscriptionDetails('invalid-id'))
|
||||
->toThrow(\Exception::class);
|
||||
});
|
||||
|
||||
test('cannot cancel subscription without subscription', function () {
|
||||
$provider = new PolarProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect(fn () => $provider->cancelSubscription($mockSubscription, 'test'))
|
||||
->toThrow(\Throwable::class);
|
||||
});
|
||||
|
||||
test('cannot update subscription without subscription', function () {
|
||||
$provider = new PolarProvider;
|
||||
$mockSubscription = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
$mockPlan = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect(fn () => $provider->updateSubscription($mockSubscription, $mockPlan))
|
||||
->toThrow(\Throwable::class);
|
||||
});
|
||||
|
||||
test('cannot create subscription without plan', function () {
|
||||
$provider = new PolarProvider;
|
||||
$mockUser = new class
|
||||
{
|
||||
public $id = 1;
|
||||
};
|
||||
|
||||
expect(fn () => $provider->createSubscription($mockUser, null))
|
||||
->toThrow(\TypeError::class);
|
||||
});
|
||||
Reference in New Issue
Block a user