Files
zemailnator/database/factories/SubscriptionFactory.php
idevakk 289baa1286 feat(payments): implement standard webhooks validation system
Add comprehensive webhook validation and processing system with Polar.sh integration:

  - Create built-in Standard Webhooks package following official specification
  - Implement HMAC-SHA256 signature validation with base64 encoding
  - Add webhook factory for multi-provider support (Polar, Stripe, generic)
  - Replace custom Polar webhook validation with Standard Webhooks implementation
  - Add proper exception handling with custom WebhookVerificationException
  - Support sandbox mode bypass for development environments
  - Update Polar provider to use database-driven configuration
  - Enhance webhook test suite with proper Standard Webhooks format
  - Add PaymentProvider model HasFactory trait for testing
  - Implement timestamp tolerance checking (±5 minutes) for replay protection
  - Support multiple signature versions and proper header validation

  This provides a secure, reusable webhook validation system that can be extended
  to other payment providers while maintaining full compliance with Standard
  Webhooks specification.

  BREAKING CHANGE: Polar webhook validation now uses Standard Webhooks format
  with headers 'webhook-id', 'webhook-timestamp', 'webhook-signature' instead of
  previous Polar-specific headers.
2025-12-06 22:49:54 -08:00

105 lines
2.8 KiB
PHP

<?php
namespace Database\Factories;
use App\Models\Plan;
use App\Models\Subscription;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<Subscription>
*/
class SubscriptionFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'user_id' => User::factory(),
'plan_id' => Plan::factory(),
'type' => 'default',
'stripe_id' => fake()->uuid(),
'stripe_status' => 'active',
'stripe_price' => fake()->randomNumber(4),
'quantity' => 1,
'trial_ends_at' => null,
'ends_at' => fake()->dateTimeBetween('+1 month', '+1 year'),
'provider' => 'polar',
'provider_subscription_id' => fake()->uuid(),
'provider_checkout_id' => fake()->uuid(),
'unified_status' => 'active',
'cancelled_at' => null,
'cancellation_reason' => null,
'paused_at' => null,
'resumed_at' => null,
'migration_batch_id' => null,
'is_migrated' => false,
'legacy_data' => null,
'synced_at' => now(),
'provider_data' => '[]',
'last_provider_sync' => now(),
'starts_at' => now(),
'status' => 'active',
];
}
/**
* Indicate that the subscription is cancelled.
*/
public function cancelled(): static
{
return $this->state(fn (array $attributes) => [
'status' => 'cancelled',
'cancelled_at' => now(),
'cancellation_reason' => 'customer_request',
]);
}
/**
* Indicate that the subscription is paused.
*/
public function paused(): static
{
return $this->state(fn (array $attributes) => [
'status' => 'paused',
'paused_at' => now(),
]);
}
/**
* Indicate that the subscription is on trial.
*/
public function trialing(): static
{
return $this->state(fn (array $attributes) => [
'status' => 'trialing',
'trial_ends_at' => now()->addDays(14),
]);
}
/**
* Indicate that the subscription uses a specific provider.
*/
public function provider(string $provider): static
{
return $this->state(fn (array $attributes) => [
'provider' => $provider,
]);
}
/**
* Indicate that the subscription has no provider subscription ID (for testing fallback logic).
*/
public function withoutProviderId(): static
{
return $this->state(fn (array $attributes) => [
'provider_subscription_id' => null,
]);
}
}