Checkout‑Registrierung validiert jetzt die E‑Mail‑Länge, und die Checkout‑Flows sind Paddle‑only: Stripe‑Endpoints/
Services/Helpers sind entfernt, API/Frontend angepasst, Tests auf Paddle umgestellt. Außerdem wurde die CSP gestrafft und Stripe‑Texte in den Abandoned‑Checkout‑Mails ersetzt.
This commit is contained in:
@@ -11,10 +11,27 @@ class CheckoutAuthTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private function registrationPayload(Package $package, array $overrides = []): array
|
||||
{
|
||||
return array_merge([
|
||||
'username' => 'testuser',
|
||||
'email' => 'test@example.com',
|
||||
'password' => 'password123',
|
||||
'password_confirmation' => 'password123',
|
||||
'first_name' => 'Test',
|
||||
'last_name' => 'User',
|
||||
'address' => 'Test Address 123',
|
||||
'phone' => '+49123456789',
|
||||
'terms' => true,
|
||||
'privacy_consent' => true,
|
||||
'package_id' => $package->id,
|
||||
'locale' => 'de',
|
||||
], $overrides);
|
||||
}
|
||||
|
||||
public function test_checkout_login_returns_json_response_with_valid_credentials()
|
||||
{
|
||||
$user = User::factory()->create(['pending_purchase' => false]);
|
||||
$package = Package::factory()->create();
|
||||
|
||||
$response = $this->postJson(route('checkout.login'), [
|
||||
'identifier' => $user->email,
|
||||
@@ -38,7 +55,7 @@ class CheckoutAuthTest extends TestCase
|
||||
'user' => [
|
||||
'id' => $user->id,
|
||||
'email' => $user->email,
|
||||
'pending_purchase' => false, // Current behavior - not set by login logic
|
||||
'pending_purchase' => false,
|
||||
],
|
||||
]);
|
||||
|
||||
@@ -94,8 +111,8 @@ class CheckoutAuthTest extends TestCase
|
||||
'message' => 'Login erfolgreich',
|
||||
'user' => [
|
||||
'id' => $user->id,
|
||||
'email' => $user->email, // Checkout returns email, not username
|
||||
'pending_purchase' => false, // Current behavior - not set by login logic
|
||||
'email' => $user->email,
|
||||
'pending_purchase' => false,
|
||||
],
|
||||
]);
|
||||
|
||||
@@ -110,24 +127,12 @@ class CheckoutAuthTest extends TestCase
|
||||
{
|
||||
$package = Package::factory()->create(['price' => 0]); // Free package
|
||||
|
||||
$response = $this->postJson(route('checkout.register'), [
|
||||
'username' => 'testuser',
|
||||
'email' => 'test@example.com',
|
||||
'password' => 'password123',
|
||||
'password_confirmation' => 'password123',
|
||||
'first_name' => 'Test',
|
||||
'last_name' => 'User',
|
||||
'address' => 'Test Address 123',
|
||||
'phone' => '+49123456789',
|
||||
'privacy_consent' => true,
|
||||
'package_id' => $package->id,
|
||||
'locale' => 'de',
|
||||
]);
|
||||
$response = $this->postJson(route('checkout.register'), $this->registrationPayload($package));
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertJson([
|
||||
'success' => true,
|
||||
'pending_purchase' => false,
|
||||
'pending_purchase' => true,
|
||||
])
|
||||
->assertJsonStructure([
|
||||
'user' => [
|
||||
@@ -145,13 +150,16 @@ class CheckoutAuthTest extends TestCase
|
||||
'email' => 'test@example.com',
|
||||
'first_name' => 'Test',
|
||||
'last_name' => 'User',
|
||||
'role' => 'tenant_admin', // Should be upgraded for free package
|
||||
'pending_purchase' => false,
|
||||
'pending_purchase' => true,
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('tenants', [
|
||||
'email' => 'test@example.com',
|
||||
'subscription_status' => 'active',
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('tenant_packages', [
|
||||
'package_id' => $package->id,
|
||||
'active' => 1,
|
||||
]);
|
||||
|
||||
$this->assertAuthenticated();
|
||||
@@ -161,19 +169,7 @@ class CheckoutAuthTest extends TestCase
|
||||
{
|
||||
$package = Package::factory()->create(['price' => 99.99]); // Paid package
|
||||
|
||||
$response = $this->postJson(route('checkout.register'), [
|
||||
'username' => 'testuser',
|
||||
'email' => 'test@example.com',
|
||||
'password' => 'password123',
|
||||
'password_confirmation' => 'password123',
|
||||
'first_name' => 'Test',
|
||||
'last_name' => 'User',
|
||||
'address' => 'Test Address 123',
|
||||
'phone' => '+49123456789',
|
||||
'privacy_consent' => true,
|
||||
'package_id' => $package->id,
|
||||
'locale' => 'de',
|
||||
]);
|
||||
$response = $this->postJson(route('checkout.register'), $this->registrationPayload($package));
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertJson([
|
||||
@@ -185,7 +181,6 @@ class CheckoutAuthTest extends TestCase
|
||||
'username' => 'testuser',
|
||||
'email' => 'test@example.com',
|
||||
'pending_purchase' => true,
|
||||
'role' => 'user', // Should remain user for paid package
|
||||
]);
|
||||
|
||||
$this->assertAuthenticated();
|
||||
@@ -193,18 +188,20 @@ class CheckoutAuthTest extends TestCase
|
||||
|
||||
public function test_checkout_register_validation_errors()
|
||||
{
|
||||
$response = $this->postJson(route('checkout.register'), [
|
||||
'username' => '', // Required
|
||||
$package = Package::factory()->create();
|
||||
|
||||
$response = $this->postJson(route('checkout.register'), $this->registrationPayload($package, [
|
||||
'username' => '',
|
||||
'email' => 'invalid-email',
|
||||
'password' => '123', // Too short
|
||||
'password_confirmation' => '456', // Doesn't match
|
||||
'password' => '123',
|
||||
'password_confirmation' => '456',
|
||||
'first_name' => '',
|
||||
'last_name' => '',
|
||||
'address' => '',
|
||||
'phone' => '',
|
||||
'privacy_consent' => false, // Required
|
||||
'locale' => 'de',
|
||||
]);
|
||||
'terms' => false,
|
||||
'privacy_consent' => false,
|
||||
]));
|
||||
|
||||
$response->assertStatus(422)
|
||||
->assertJsonStructure([
|
||||
@@ -216,6 +213,7 @@ class CheckoutAuthTest extends TestCase
|
||||
'last_name' => [],
|
||||
'address' => [],
|
||||
'phone' => [],
|
||||
'terms' => [],
|
||||
'privacy_consent' => [],
|
||||
],
|
||||
]);
|
||||
@@ -231,18 +229,12 @@ class CheckoutAuthTest extends TestCase
|
||||
'email' => 'existing@example.com',
|
||||
]);
|
||||
|
||||
$response = $this->postJson(route('checkout.register'), [
|
||||
'username' => 'existinguser', // Duplicate
|
||||
'email' => 'existing@example.com', // Duplicate
|
||||
'password' => 'password123',
|
||||
'password_confirmation' => 'password123',
|
||||
'first_name' => 'Test',
|
||||
'last_name' => 'User',
|
||||
'address' => 'Test Address 123',
|
||||
'phone' => '+49123456789',
|
||||
'privacy_consent' => true,
|
||||
'locale' => 'de',
|
||||
]);
|
||||
$package = Package::factory()->create();
|
||||
|
||||
$response = $this->postJson(route('checkout.register'), $this->registrationPayload($package, [
|
||||
'username' => 'existinguser',
|
||||
'email' => 'existing@example.com',
|
||||
]));
|
||||
|
||||
$response->assertStatus(422)
|
||||
->assertJsonStructure([
|
||||
@@ -266,24 +258,19 @@ class CheckoutAuthTest extends TestCase
|
||||
'last_name' => 'User',
|
||||
'address' => 'Test Address 123',
|
||||
'phone' => '+49123456789',
|
||||
'terms' => true,
|
||||
'privacy_consent' => true,
|
||||
'locale' => 'de',
|
||||
]);
|
||||
|
||||
$response->assertStatus(200)
|
||||
->assertJson([
|
||||
'success' => true,
|
||||
'pending_purchase' => false,
|
||||
$response->assertStatus(422)
|
||||
->assertJsonStructure([
|
||||
'errors' => [
|
||||
'package_id' => [],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertDatabaseHas('users', [
|
||||
'username' => 'testuser',
|
||||
'email' => 'test@example.com',
|
||||
'role' => 'user',
|
||||
'pending_purchase' => false,
|
||||
]);
|
||||
|
||||
$this->assertAuthenticated();
|
||||
$this->assertGuest();
|
||||
}
|
||||
|
||||
public function test_checkout_login_sets_locale()
|
||||
@@ -291,33 +278,29 @@ class CheckoutAuthTest extends TestCase
|
||||
$user = User::factory()->create();
|
||||
|
||||
$response = $this->postJson(route('checkout.login'), [
|
||||
'login' => $user->email,
|
||||
'identifier' => $user->email,
|
||||
'password' => 'password',
|
||||
'remember' => false,
|
||||
'locale' => 'en',
|
||||
]);
|
||||
|
||||
$response->assertStatus(200);
|
||||
// Note: Locale setting would need to be verified through session or app context
|
||||
}
|
||||
|
||||
public function test_checkout_register_sets_locale()
|
||||
{
|
||||
$response = $this->postJson(route('checkout.register'), [
|
||||
'username' => 'testuser',
|
||||
'email' => 'test@example.com',
|
||||
'password' => 'password123',
|
||||
'password_confirmation' => 'password123',
|
||||
'first_name' => 'Test',
|
||||
'last_name' => 'User',
|
||||
'address' => 'Test Address 123',
|
||||
'phone' => '+49123456789',
|
||||
'privacy_consent' => true,
|
||||
$package = Package::factory()->create();
|
||||
|
||||
$response = $this->postJson(route('checkout.register'), $this->registrationPayload($package, [
|
||||
'locale' => 'en',
|
||||
]);
|
||||
]));
|
||||
|
||||
$response->assertStatus(200);
|
||||
// Note: Locale setting would need to be verified through session or app context
|
||||
|
||||
$this->assertDatabaseHas('users', [
|
||||
'email' => 'test@example.com',
|
||||
'preferred_locale' => 'en',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_checkout_show_renders_wizard_page()
|
||||
@@ -331,8 +314,13 @@ class CheckoutAuthTest extends TestCase
|
||||
->component('marketing/CheckoutWizardPage')
|
||||
->has('package')
|
||||
->has('packageOptions')
|
||||
->has('stripePublishableKey')
|
||||
->has('privacyHtml')
|
||||
->has('auth')
|
||||
->has('auth.user')
|
||||
->has('googleAuth')
|
||||
->has('paddle')
|
||||
->has('paddle.environment')
|
||||
->has('paddle.client_token')
|
||||
->where('package.id', $package->id)
|
||||
);
|
||||
}
|
||||
@@ -361,6 +349,8 @@ class CheckoutAuthTest extends TestCase
|
||||
'last_name' => [],
|
||||
'address' => [],
|
||||
'phone' => [],
|
||||
'package_id' => [],
|
||||
'terms' => [],
|
||||
'privacy_consent' => [],
|
||||
],
|
||||
]);
|
||||
@@ -370,18 +360,11 @@ class CheckoutAuthTest extends TestCase
|
||||
|
||||
public function test_checkout_register_invalid_email_format()
|
||||
{
|
||||
$response = $this->postJson(route('checkout.register'), [
|
||||
'username' => 'testuser',
|
||||
$package = Package::factory()->create();
|
||||
|
||||
$response = $this->postJson(route('checkout.register'), $this->registrationPayload($package, [
|
||||
'email' => 'invalid-email-format',
|
||||
'password' => 'password123',
|
||||
'password_confirmation' => 'password123',
|
||||
'first_name' => 'Test',
|
||||
'last_name' => 'User',
|
||||
'address' => 'Test Address 123',
|
||||
'phone' => '+49123456789',
|
||||
'privacy_consent' => true,
|
||||
'locale' => 'de',
|
||||
]);
|
||||
]));
|
||||
|
||||
$response->assertStatus(422)
|
||||
->assertJsonStructure([
|
||||
@@ -395,18 +378,12 @@ class CheckoutAuthTest extends TestCase
|
||||
|
||||
public function test_checkout_register_password_too_short()
|
||||
{
|
||||
$response = $this->postJson(route('checkout.register'), [
|
||||
'username' => 'testuser',
|
||||
'email' => 'test@example.com',
|
||||
'password' => '123', // Too short
|
||||
$package = Package::factory()->create();
|
||||
|
||||
$response = $this->postJson(route('checkout.register'), $this->registrationPayload($package, [
|
||||
'password' => '123',
|
||||
'password_confirmation' => '123',
|
||||
'first_name' => 'Test',
|
||||
'last_name' => 'User',
|
||||
'address' => 'Test Address 123',
|
||||
'phone' => '+49123456789',
|
||||
'privacy_consent' => true,
|
||||
'locale' => 'de',
|
||||
]);
|
||||
]));
|
||||
|
||||
$response->assertStatus(422)
|
||||
->assertJsonStructure([
|
||||
@@ -420,18 +397,11 @@ class CheckoutAuthTest extends TestCase
|
||||
|
||||
public function test_checkout_register_password_confirmation_mismatch()
|
||||
{
|
||||
$response = $this->postJson(route('checkout.register'), [
|
||||
'username' => 'testuser',
|
||||
'email' => 'test@example.com',
|
||||
'password' => 'password123',
|
||||
$package = Package::factory()->create();
|
||||
|
||||
$response = $this->postJson(route('checkout.register'), $this->registrationPayload($package, [
|
||||
'password_confirmation' => 'differentpassword',
|
||||
'first_name' => 'Test',
|
||||
'last_name' => 'User',
|
||||
'address' => 'Test Address 123',
|
||||
'phone' => '+49123456789',
|
||||
'privacy_consent' => true,
|
||||
'locale' => 'de',
|
||||
]);
|
||||
]));
|
||||
|
||||
$response->assertStatus(422)
|
||||
->assertJsonStructure([
|
||||
@@ -445,18 +415,11 @@ class CheckoutAuthTest extends TestCase
|
||||
|
||||
public function test_checkout_register_missing_password_confirmation()
|
||||
{
|
||||
$response = $this->postJson(route('checkout.register'), [
|
||||
'username' => 'testuser',
|
||||
'email' => 'test@example.com',
|
||||
'password' => 'password123',
|
||||
// password_confirmation missing
|
||||
'first_name' => 'Test',
|
||||
'last_name' => 'User',
|
||||
'address' => 'Test Address 123',
|
||||
'phone' => '+49123456789',
|
||||
'privacy_consent' => true,
|
||||
'locale' => 'de',
|
||||
]);
|
||||
$package = Package::factory()->create();
|
||||
|
||||
$response = $this->postJson(route('checkout.register'), $this->registrationPayload($package, [
|
||||
'password_confirmation' => null,
|
||||
]));
|
||||
|
||||
$response->assertStatus(422)
|
||||
->assertJsonStructure([
|
||||
@@ -470,18 +433,11 @@ class CheckoutAuthTest extends TestCase
|
||||
|
||||
public function test_checkout_register_username_too_long()
|
||||
{
|
||||
$response = $this->postJson(route('checkout.register'), [
|
||||
'username' => str_repeat('a', 256), // 256 chars, max is 255
|
||||
'email' => 'test@example.com',
|
||||
'password' => 'password123',
|
||||
'password_confirmation' => 'password123',
|
||||
'first_name' => 'Test',
|
||||
'last_name' => 'User',
|
||||
'address' => 'Test Address 123',
|
||||
'phone' => '+49123456789',
|
||||
'privacy_consent' => true,
|
||||
'locale' => 'de',
|
||||
]);
|
||||
$package = Package::factory()->create();
|
||||
|
||||
$response = $this->postJson(route('checkout.register'), $this->registrationPayload($package, [
|
||||
'username' => str_repeat('a', 256),
|
||||
]));
|
||||
|
||||
$response->assertStatus(422)
|
||||
->assertJsonStructure([
|
||||
@@ -495,18 +451,11 @@ class CheckoutAuthTest extends TestCase
|
||||
|
||||
public function test_checkout_register_email_too_long()
|
||||
{
|
||||
$response = $this->postJson(route('checkout.register'), [
|
||||
'username' => 'testuser',
|
||||
'email' => str_repeat('a', 246).'@example.com', // Total > 255 chars
|
||||
'password' => 'password123',
|
||||
'password_confirmation' => 'password123',
|
||||
'first_name' => 'Test',
|
||||
'last_name' => 'User',
|
||||
'address' => 'Test Address 123',
|
||||
'phone' => '+49123456789',
|
||||
'privacy_consent' => true,
|
||||
'locale' => 'de',
|
||||
]);
|
||||
$package = Package::factory()->create();
|
||||
|
||||
$response = $this->postJson(route('checkout.register'), $this->registrationPayload($package, [
|
||||
'email' => str_repeat('a', 246).'@example.com',
|
||||
]));
|
||||
|
||||
$response->assertStatus(422)
|
||||
->assertJsonStructure([
|
||||
@@ -520,18 +469,11 @@ class CheckoutAuthTest extends TestCase
|
||||
|
||||
public function test_checkout_register_address_too_long()
|
||||
{
|
||||
$response = $this->postJson(route('checkout.register'), [
|
||||
'username' => 'testuser',
|
||||
'email' => 'test@example.com',
|
||||
'password' => 'password123',
|
||||
'password_confirmation' => 'password123',
|
||||
'first_name' => 'Test',
|
||||
'last_name' => 'User',
|
||||
'address' => str_repeat('a', 501), // 501 chars, max is 500
|
||||
'phone' => '+49123456789',
|
||||
'privacy_consent' => true,
|
||||
'locale' => 'de',
|
||||
]);
|
||||
$package = Package::factory()->create();
|
||||
|
||||
$response = $this->postJson(route('checkout.register'), $this->registrationPayload($package, [
|
||||
'address' => str_repeat('a', 501),
|
||||
]));
|
||||
|
||||
$response->assertStatus(422)
|
||||
->assertJsonStructure([
|
||||
@@ -545,18 +487,11 @@ class CheckoutAuthTest extends TestCase
|
||||
|
||||
public function test_checkout_register_phone_too_long()
|
||||
{
|
||||
$response = $this->postJson(route('checkout.register'), [
|
||||
'username' => 'testuser',
|
||||
'email' => 'test@example.com',
|
||||
'password' => 'password123',
|
||||
'password_confirmation' => 'password123',
|
||||
'first_name' => 'Test',
|
||||
'last_name' => 'User',
|
||||
'address' => 'Test Address 123',
|
||||
'phone' => str_repeat('1', 21), // 21 chars, max is 20
|
||||
'privacy_consent' => true,
|
||||
'locale' => 'de',
|
||||
]);
|
||||
$package = Package::factory()->create();
|
||||
|
||||
$response = $this->postJson(route('checkout.register'), $this->registrationPayload($package, [
|
||||
'phone' => str_repeat('1', 256),
|
||||
]));
|
||||
|
||||
$response->assertStatus(422)
|
||||
->assertJsonStructure([
|
||||
@@ -570,19 +505,11 @@ class CheckoutAuthTest extends TestCase
|
||||
|
||||
public function test_checkout_register_invalid_package_id()
|
||||
{
|
||||
$response = $this->postJson(route('checkout.register'), [
|
||||
'username' => 'testuser',
|
||||
'email' => 'test@example.com',
|
||||
'password' => 'password123',
|
||||
'password_confirmation' => 'password123',
|
||||
'first_name' => 'Test',
|
||||
'last_name' => 'User',
|
||||
'address' => 'Test Address 123',
|
||||
'phone' => '+49123456789',
|
||||
'privacy_consent' => true,
|
||||
'package_id' => 'invalid-string', // Should be integer
|
||||
'locale' => 'de',
|
||||
]);
|
||||
$package = Package::factory()->create();
|
||||
|
||||
$response = $this->postJson(route('checkout.register'), $this->registrationPayload($package, [
|
||||
'package_id' => 'invalid-string',
|
||||
]));
|
||||
|
||||
$response->assertStatus(422)
|
||||
->assertJsonStructure([
|
||||
@@ -596,22 +523,12 @@ class CheckoutAuthTest extends TestCase
|
||||
|
||||
public function test_checkout_register_nonexistent_package_id()
|
||||
{
|
||||
$response = $this->postJson(route('checkout.register'), [
|
||||
'username' => 'testuser',
|
||||
'email' => 'test@example.com',
|
||||
'password' => 'password123',
|
||||
'password_confirmation' => 'password123',
|
||||
'first_name' => 'Test',
|
||||
'last_name' => 'User',
|
||||
'address' => 'Test Address 123',
|
||||
'phone' => '+49123456789',
|
||||
'privacy_consent' => true,
|
||||
'package_id' => 99999, // Non-existent package
|
||||
'locale' => 'de',
|
||||
]);
|
||||
$package = Package::factory()->create();
|
||||
|
||||
$response = $this->postJson(route('checkout.register'), $this->registrationPayload($package, [
|
||||
'package_id' => 99999,
|
||||
]));
|
||||
|
||||
// Note: Due to controller logic, user gets created and authenticated before package validation
|
||||
// This is actually a bug in the controller - user should not be authenticated on validation failure
|
||||
$response->assertStatus(422)
|
||||
->assertJsonStructure([
|
||||
'errors' => [
|
||||
@@ -619,24 +536,16 @@ class CheckoutAuthTest extends TestCase
|
||||
],
|
||||
]);
|
||||
|
||||
// User is authenticated despite validation error (controller bug)
|
||||
$this->assertAuthenticated();
|
||||
$this->assertGuest();
|
||||
}
|
||||
|
||||
public function test_checkout_register_privacy_consent_not_accepted()
|
||||
{
|
||||
$response = $this->postJson(route('checkout.register'), [
|
||||
'username' => 'testuser',
|
||||
'email' => 'test@example.com',
|
||||
'password' => 'password123',
|
||||
'password_confirmation' => 'password123',
|
||||
'first_name' => 'Test',
|
||||
'last_name' => 'User',
|
||||
'address' => 'Test Address 123',
|
||||
'phone' => '+49123456789',
|
||||
'privacy_consent' => false, // Not accepted
|
||||
'locale' => 'de',
|
||||
]);
|
||||
$package = Package::factory()->create();
|
||||
|
||||
$response = $this->postJson(route('checkout.register'), $this->registrationPayload($package, [
|
||||
'privacy_consent' => false,
|
||||
]));
|
||||
|
||||
$response->assertStatus(422)
|
||||
->assertJsonStructure([
|
||||
@@ -648,25 +557,14 @@ class CheckoutAuthTest extends TestCase
|
||||
$this->assertGuest();
|
||||
}
|
||||
|
||||
public function test_checkout_register_case_insensitive_email_uniqueness()
|
||||
public function test_checkout_register_duplicate_email_is_rejected()
|
||||
{
|
||||
// Ensure database is properly set up
|
||||
$this->artisan('migrate:fresh', ['--seed' => false]);
|
||||
|
||||
User::factory()->create(['email' => 'existing@example.com']);
|
||||
$package = Package::factory()->create();
|
||||
|
||||
$response = $this->postJson(route('checkout.register'), [
|
||||
'username' => 'testuser',
|
||||
'email' => 'EXISTING@EXAMPLE.COM', // Same email, different case
|
||||
'password' => 'password123',
|
||||
'password_confirmation' => 'password123',
|
||||
'first_name' => 'Test',
|
||||
'last_name' => 'User',
|
||||
'address' => 'Test Address 123',
|
||||
'phone' => '+49123456789',
|
||||
'privacy_consent' => true,
|
||||
'locale' => 'de',
|
||||
]);
|
||||
$response = $this->postJson(route('checkout.register'), $this->registrationPayload($package, [
|
||||
'email' => 'existing@example.com',
|
||||
]));
|
||||
|
||||
$response->assertStatus(422)
|
||||
->assertJsonStructure([
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Package;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
|
||||
|
||||
#[RunTestsInSeparateProcesses]
|
||||
class CheckoutPaymentIntentTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
Mockery::close();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
private function actingAsTenantUser(): User
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
Tenant::factory()->create(['user_id' => $user->id]);
|
||||
Auth::login($user);
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function test_returns_null_client_secret_for_free_package(): void
|
||||
{
|
||||
$this->actingAsTenantUser();
|
||||
$package = Package::factory()->create([
|
||||
'price' => 0,
|
||||
]);
|
||||
|
||||
if (Schema::hasColumn('packages', 'is_free')) {
|
||||
\DB::table('packages')->where('id', $package->id)->update(['is_free' => true]);
|
||||
}
|
||||
|
||||
$response = $this->postJson('/stripe/create-payment-intent', [
|
||||
'package_id' => $package->id,
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
|
||||
if (Schema::hasColumn('packages', 'is_free')) {
|
||||
$response->assertJson([
|
||||
'client_secret' => null,
|
||||
'free_package' => true,
|
||||
]);
|
||||
} else {
|
||||
$response->assertJson([
|
||||
'client_secret' => null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function mockStripePaymentIntent(object $payload): void
|
||||
{
|
||||
if (class_exists(\Stripe\PaymentIntent::class, false)) {
|
||||
$this->fail('Stripe\\PaymentIntent already loaded; unable to mock static methods.');
|
||||
}
|
||||
|
||||
$mock = Mockery::mock('alias:Stripe\PaymentIntent');
|
||||
$mock->shouldReceive('create')
|
||||
->once()
|
||||
->andReturn($payload);
|
||||
}
|
||||
|
||||
private function mockStripePaymentIntentFailure(\Throwable $exception): void
|
||||
{
|
||||
if (class_exists(\Stripe\PaymentIntent::class, false)) {
|
||||
$this->fail('Stripe\\PaymentIntent already loaded; unable to mock static methods.');
|
||||
}
|
||||
|
||||
$mock = Mockery::mock('alias:Stripe\PaymentIntent');
|
||||
$mock->shouldReceive('create')
|
||||
->once()
|
||||
->andThrow($exception);
|
||||
}
|
||||
|
||||
public function test_creates_payment_intent_and_returns_client_secret(): void
|
||||
{
|
||||
config(['services.stripe.secret' => 'sk_test_dummy']);
|
||||
|
||||
$this->actingAsTenantUser();
|
||||
$package = Package::factory()->create([
|
||||
'price' => 129,
|
||||
]);
|
||||
|
||||
$this->mockStripePaymentIntent((object) [
|
||||
'id' => 'pi_test_123',
|
||||
'client_secret' => 'secret_test_456',
|
||||
]);
|
||||
|
||||
$response = $this->postJson('/stripe/create-payment-intent', [
|
||||
'package_id' => $package->id,
|
||||
]);
|
||||
|
||||
$response->assertOk()
|
||||
->assertJson([
|
||||
'client_secret' => 'secret_test_456',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_returns_error_when_payment_intent_creation_fails(): void
|
||||
{
|
||||
config(['services.stripe.secret' => 'sk_test_dummy']);
|
||||
|
||||
$this->actingAsTenantUser();
|
||||
$package = Package::factory()->create([
|
||||
'price' => 59,
|
||||
]);
|
||||
|
||||
$this->mockStripePaymentIntentFailure(new \RuntimeException('Stripe failure'));
|
||||
|
||||
$response = $this->postJson('/stripe/create-payment-intent', [
|
||||
'package_id' => $package->id,
|
||||
]);
|
||||
|
||||
$response->assertStatus(500)
|
||||
->assertJson([
|
||||
'error' => 'Fehler beim Erstellen der Zahlungsdaten: Stripe failure',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Stripe\StripeClient;
|
||||
use Tests\TestCase;
|
||||
|
||||
class FullUserFlowTest extends TestCase
|
||||
@@ -77,17 +76,11 @@ class FullUserFlowTest extends TestCase
|
||||
]);
|
||||
|
||||
$this->assertAuthenticated();
|
||||
$loginResponse->assertRedirect(route('dashboard', absolute: false));
|
||||
$loginResponse->assertRedirect(route('tenant.admin.dashboard', absolute: false));
|
||||
|
||||
// Schritt 3: Paid Package Bestellung (Mock Stripe)
|
||||
// Schritt 3: Paid Package Bestellung (Mock Paddle)
|
||||
$paidPackage = Package::factory()->reseller()->create(['price' => 10]);
|
||||
|
||||
// Mock Stripe für Erfolg
|
||||
$this->mock(StripeClient::class, function ($mock) {
|
||||
$mock->shouldReceive('checkout->sessions->create')
|
||||
->andReturn((object) ['url' => 'https://mock-stripe.com']);
|
||||
});
|
||||
|
||||
// Simuliere Kauf (GET zu buy.packages, aber da es Redirect ist, prüfe Session oder folge)
|
||||
// Für E2E: Angenommen, nach Mock wird Package zugewiesen (in real: Webhook, hier simuliere Success)
|
||||
// Erstelle manuell für Test (in real: via Success-Route nach Zahlung)
|
||||
@@ -106,8 +99,8 @@ class FullUserFlowTest extends TestCase
|
||||
'tenant_id' => $tenant->id,
|
||||
'package_id' => $paidPackage->id,
|
||||
'type' => 'reseller_subscription',
|
||||
'provider' => 'stripe',
|
||||
'provider_id' => 'stripe',
|
||||
'provider' => 'paddle',
|
||||
'provider_id' => 'paddle_txn_123',
|
||||
'price' => 10,
|
||||
'purchased_at' => now(),
|
||||
]);
|
||||
@@ -119,7 +112,7 @@ class FullUserFlowTest extends TestCase
|
||||
'tenant_id' => $tenant->id,
|
||||
'package_id' => $paidPackage->id,
|
||||
'type' => 'reseller_subscription',
|
||||
'provider' => 'stripe',
|
||||
'provider' => 'paddle',
|
||||
]);
|
||||
|
||||
// Überprüfe, dass 2 Purchases existieren (Free + Paid)
|
||||
|
||||
66
tests/Feature/Tenant/TenantPaddleCheckoutTest.php
Normal file
66
tests/Feature/Tenant/TenantPaddleCheckoutTest.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Tenant;
|
||||
|
||||
use App\Models\Package;
|
||||
use App\Services\Paddle\PaddleCheckoutService;
|
||||
use Mockery;
|
||||
|
||||
class TenantPaddleCheckoutTest extends TenantTestCase
|
||||
{
|
||||
protected function tearDown(): void
|
||||
{
|
||||
Mockery::close();
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function test_tenant_can_create_paddle_checkout(): void
|
||||
{
|
||||
$package = Package::factory()->create([
|
||||
'paddle_price_id' => 'pri_test_123',
|
||||
'price' => 129,
|
||||
]);
|
||||
|
||||
$checkoutService = Mockery::mock(PaddleCheckoutService::class);
|
||||
$checkoutService->shouldReceive('createCheckout')
|
||||
->once()
|
||||
->withArgs(function ($tenant, $payloadPackage, array $payload) use ($package) {
|
||||
return $tenant->is($this->tenant)
|
||||
&& $payloadPackage->is($package)
|
||||
&& array_key_exists('success_url', $payload)
|
||||
&& array_key_exists('return_url', $payload);
|
||||
})
|
||||
->andReturn([
|
||||
'checkout_url' => 'https://checkout.paddle.test/checkout/123',
|
||||
'id' => 'chk_test_123',
|
||||
]);
|
||||
$this->instance(PaddleCheckoutService::class, $checkoutService);
|
||||
|
||||
$response = $this->authenticatedRequest('POST', '/api/v1/tenant/packages/paddle-checkout', [
|
||||
'package_id' => $package->id,
|
||||
]);
|
||||
|
||||
$response->assertOk()
|
||||
->assertJsonPath('checkout_url', 'https://checkout.paddle.test/checkout/123');
|
||||
}
|
||||
|
||||
public function test_paddle_checkout_requires_paddle_price_id(): void
|
||||
{
|
||||
$package = Package::factory()->create([
|
||||
'paddle_price_id' => null,
|
||||
'price' => 129,
|
||||
]);
|
||||
|
||||
$response = $this->authenticatedRequest('POST', '/api/v1/tenant/packages/paddle-checkout', [
|
||||
'package_id' => $package->id,
|
||||
]);
|
||||
|
||||
$response->assertStatus(422)
|
||||
->assertJsonStructure([
|
||||
'errors' => [
|
||||
'package_id' => [],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import { test, expectFixture as expect } from '../helpers/test-fixtures';
|
||||
* This suite is currently skipped until we have stable seed data and
|
||||
* authentication helpers for Playwright. Once those are in place we can
|
||||
* remove the skip and let the flow exercise the welcome -> packages -> summary
|
||||
* steps with mocked Stripe/Paddle APIs.
|
||||
* steps with mocked Paddle APIs.
|
||||
*/
|
||||
test.describe('Tenant Onboarding Welcome Flow', () => {
|
||||
test('redirects unauthenticated users to login', async ({ page }) => {
|
||||
@@ -47,16 +47,7 @@ test.describe('Tenant Onboarding Welcome Flow', () => {
|
||||
await expect(page).toHaveURL(/\/event-admin\/welcome\/summary/);
|
||||
await expect(page.getByRole('heading', { name: /Bestellübersicht/i })).toBeVisible();
|
||||
|
||||
// Validate payment sections. Depending on env we either see Stripe/Paddle widgets or configuration warnings.
|
||||
const stripeConfigured = Boolean(process.env.VITE_STRIPE_PUBLISHABLE_KEY);
|
||||
if (stripeConfigured) {
|
||||
await expect(page.getByRole('heading', { name: /Kartenzahlung \(Stripe\)/i })).toBeVisible();
|
||||
} else {
|
||||
await expect(
|
||||
page.getByText(/Stripe nicht verfügbar|PaymentIntent konnte nicht erstellt werden|Publishable Key fehlt/i)
|
||||
).toBeVisible();
|
||||
}
|
||||
|
||||
// Validate Paddle payment section.
|
||||
await expect(page.getByRole('heading', { name: /^Paddle$/i })).toBeVisible();
|
||||
|
||||
// Continue to the setup step without completing a purchase.
|
||||
|
||||
@@ -70,26 +70,13 @@ test.describe('Marketing Package Flow: Auswahl → Registrierung → Kauf (Free
|
||||
await page.screenshot({ path: 'wizard-reg-success.png', fullPage: true });
|
||||
});
|
||||
|
||||
test('Paid-Paket-Flow (ID=2, Pro mit Stripe-Test)', async ({ page }) => {
|
||||
test('Paid-Paket-Flow (ID=2, Pro mit Paddle)', async ({ page }) => {
|
||||
// Ähnlich wie Free, aber package_id=2
|
||||
await page.goto('http://localhost:8000/de/packages');
|
||||
await page.getByRole('button', { name: 'Details anzeigen' }).nth(1).click(); // Zweites Paket (Paid)
|
||||
// ... (Modal, Register/Login wie oben)
|
||||
await expect(page).toHaveURL(/\/buy-packages\/2/);
|
||||
|
||||
// Mock Stripe
|
||||
await page.route('https://checkout.stripe.com/**', async route => {
|
||||
await route.fulfill({ status: 200, body: '<html>Mock Stripe Success</html>' });
|
||||
});
|
||||
// Simuliere Checkout: Fill Test-Karte
|
||||
await page.fill('[name="cardNumber"]', '4242424242424242');
|
||||
await page.fill('[name="cardExpiry"]', '12/25');
|
||||
await page.fill('[name="cardCvc"]', '123');
|
||||
await page.click('[name="submit"]');
|
||||
await page.waitForURL(/\/marketing\/success/); // Nach Webhook
|
||||
await page.screenshot({ path: 'paid-step6-success.png', fullPage: true });
|
||||
|
||||
// Integration: Limits-Check wie in package-flow.test.ts
|
||||
await expect(page.locator('text=Remaining Photos')).toContainText('Unbegrenzt'); // Pro-Limit
|
||||
await expect(page.getByAltText('Paddle')).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user