Files
zemailnator/tests/Unit/ActivationKeyProviderTest.php
idevakk 27ac13948c 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
2025-11-19 09:37:00 -08:00

318 lines
11 KiB
PHP

<?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);
}
});