verschieben des sofortigen verzichts auf das Widerrrufsrecht zum Anlegen des Events
This commit is contained in:
@@ -8,10 +8,12 @@ use App\Http\Requests\Tenant\EventStoreRequest;
|
||||
use App\Http\Resources\Tenant\EventJoinTokenResource;
|
||||
use App\Http\Resources\Tenant\EventResource;
|
||||
use App\Http\Resources\Tenant\PhotoResource;
|
||||
use App\Models\CheckoutSession;
|
||||
use App\Models\Event;
|
||||
use App\Models\EventPackage;
|
||||
use App\Models\GuestNotification;
|
||||
use App\Models\Package;
|
||||
use App\Models\PackagePurchase;
|
||||
use App\Models\Photo;
|
||||
use App\Models\Tenant;
|
||||
use App\Services\EventJoinTokenService;
|
||||
@@ -116,6 +118,17 @@ class EventController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
$requiresWaiver = $package->isEndcustomer();
|
||||
$latestPurchase = $requiresWaiver ? $this->resolveLatestPackagePurchase($tenant, $package) : null;
|
||||
$existingWaiver = $latestPurchase ? data_get($latestPurchase->metadata, 'consents.digital_content_waiver_at') : null;
|
||||
$needsWaiver = $requiresWaiver && ! $existingWaiver;
|
||||
|
||||
if ($needsWaiver && ! $request->boolean('accepted_waiver')) {
|
||||
throw ValidationException::withMessages([
|
||||
'accepted_waiver' => 'Ein sofortiger Beginn der digitalen Dienstleistung erfordert Ihre ausdrückliche Zustimmung.',
|
||||
]);
|
||||
}
|
||||
|
||||
$eventData = array_merge($validated, [
|
||||
'tenant_id' => $tenantId,
|
||||
'status' => $validated['status'] ?? 'draft',
|
||||
@@ -180,15 +193,21 @@ class EventController extends Controller
|
||||
'gallery_expires_at' => $package->gallery_days ? now()->addDays($package->gallery_days) : null,
|
||||
]);
|
||||
|
||||
$note = sprintf('Event #%d created (%s)', $event->id, $event->name);
|
||||
if ($package->isReseller()) {
|
||||
$note = sprintf('Event #%d created (%s)', $event->id, $event->name);
|
||||
|
||||
if (! $tenant->consumeEventAllowance(1, 'event.create', $note)) {
|
||||
throw new HttpException(402, 'Insufficient package allowance.');
|
||||
if (! $tenant->consumeEventAllowance(1, 'event.create', $note)) {
|
||||
throw new HttpException(402, 'Insufficient package allowance.');
|
||||
}
|
||||
}
|
||||
|
||||
return $event;
|
||||
});
|
||||
|
||||
if ($needsWaiver) {
|
||||
$this->recordEventStartWaiver($tenant, $package, $latestPurchase);
|
||||
}
|
||||
|
||||
$tenant->refresh();
|
||||
$event->load(['eventType', 'tenant', 'eventPackages.package', 'eventPackages.addons']);
|
||||
|
||||
@@ -200,6 +219,45 @@ class EventController extends Controller
|
||||
], 201);
|
||||
}
|
||||
|
||||
private function resolveLatestPackagePurchase(Tenant $tenant, Package $package): ?PackagePurchase
|
||||
{
|
||||
return PackagePurchase::query()
|
||||
->where('tenant_id', $tenant->id)
|
||||
->where('package_id', $package->id)
|
||||
->orderByDesc('purchased_at')
|
||||
->orderByDesc('id')
|
||||
->first();
|
||||
}
|
||||
|
||||
private function recordEventStartWaiver(Tenant $tenant, Package $package, ?PackagePurchase $purchase): void
|
||||
{
|
||||
$timestamp = now();
|
||||
$legalVersion = config('app.legal_version', $timestamp->toDateString());
|
||||
|
||||
if ($purchase) {
|
||||
$metadata = $purchase->metadata ?? [];
|
||||
$consents = is_array($metadata['consents'] ?? null) ? $metadata['consents'] : [];
|
||||
$consents['digital_content_waiver_at'] = $timestamp->toIso8601String();
|
||||
$consents['legal_version'] = $consents['legal_version'] ?? $legalVersion;
|
||||
$metadata['consents'] = $consents;
|
||||
$purchase->metadata = $metadata;
|
||||
$purchase->save();
|
||||
}
|
||||
|
||||
$session = CheckoutSession::query()
|
||||
->where('tenant_id', $tenant->id)
|
||||
->where('package_id', $package->id)
|
||||
->where('status', CheckoutSession::STATUS_COMPLETED)
|
||||
->orderByDesc('completed_at')
|
||||
->first();
|
||||
|
||||
if ($session && ! $session->digital_content_waiver_at) {
|
||||
$session->digital_content_waiver_at = $timestamp;
|
||||
$session->legal_version = $session->legal_version ?? $legalVersion;
|
||||
$session->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function show(Request $request, Event $event): JsonResponse
|
||||
{
|
||||
$tenantId = $request->attributes->get('tenant_id');
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\EventPackageAddon;
|
||||
use App\Services\Paddle\PaddleCustomerPortalService;
|
||||
use App\Services\Paddle\PaddleCustomerService;
|
||||
use App\Services\Paddle\PaddleTransactionService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -12,7 +14,11 @@ use Illuminate\Support\Facades\Log;
|
||||
|
||||
class TenantBillingController extends Controller
|
||||
{
|
||||
public function __construct(private readonly PaddleTransactionService $paddleTransactions) {}
|
||||
public function __construct(
|
||||
private readonly PaddleTransactionService $paddleTransactions,
|
||||
private readonly PaddleCustomerService $paddleCustomers,
|
||||
private readonly PaddleCustomerPortalService $portalSessions,
|
||||
) {}
|
||||
|
||||
public function transactions(Request $request): JsonResponse
|
||||
{
|
||||
@@ -116,4 +122,44 @@ class TenantBillingController extends Controller
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function portal(Request $request): JsonResponse
|
||||
{
|
||||
$tenant = $request->attributes->get('tenant');
|
||||
|
||||
if (! $tenant) {
|
||||
return response()->json([
|
||||
'message' => 'Tenant not found.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
try {
|
||||
$customerId = $this->paddleCustomers->ensureCustomerId($tenant);
|
||||
$session = $this->portalSessions->createSession($customerId);
|
||||
} catch (\Throwable $exception) {
|
||||
Log::warning('Failed to create Paddle customer portal session', [
|
||||
'tenant_id' => $tenant->id,
|
||||
'error' => $exception->getMessage(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Failed to create Paddle customer portal session.',
|
||||
], 502);
|
||||
}
|
||||
|
||||
$url = Arr::get($session, 'data.urls.general.overview')
|
||||
?? Arr::get($session, 'data.urls.general')
|
||||
?? Arr::get($session, 'urls.general.overview')
|
||||
?? Arr::get($session, 'urls.general');
|
||||
|
||||
if (! $url) {
|
||||
return response()->json([
|
||||
'message' => 'Paddle customer portal session missing URL.',
|
||||
], 502);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'url' => $url,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,16 +219,6 @@ class CheckoutController extends Controller
|
||||
], 422);
|
||||
}
|
||||
|
||||
$requiresWaiver = (bool) ($package->activates_immediately ?? true);
|
||||
|
||||
if ($requiresWaiver && ! $request->boolean('accepted_waiver')) {
|
||||
return response()->json([
|
||||
'errors' => [
|
||||
'accepted_waiver' => ['Ein sofortiger Beginn der digitalen Dienstleistung erfordert Ihre ausdrückliche Zustimmung.'],
|
||||
],
|
||||
], 422);
|
||||
}
|
||||
|
||||
$session = $sessions->createOrResume($user, $package, [
|
||||
'tenant' => $user->tenant,
|
||||
'locale' => $validated['locale'] ?? null,
|
||||
@@ -241,7 +231,7 @@ class CheckoutController extends Controller
|
||||
'accepted_terms_at' => $now,
|
||||
'accepted_privacy_at' => $now,
|
||||
'accepted_withdrawal_notice_at' => $now,
|
||||
'digital_content_waiver_at' => $requiresWaiver && $request->boolean('accepted_waiver') ? $now : null,
|
||||
'digital_content_waiver_at' => null,
|
||||
'legal_version' => config('app.legal_version', $now->toDateString()),
|
||||
])->save();
|
||||
|
||||
|
||||
@@ -38,14 +38,6 @@ class PaddleCheckoutController extends Controller
|
||||
throw ValidationException::withMessages(['package_id' => 'Package is not linked to a Paddle price.']);
|
||||
}
|
||||
|
||||
$requiresWaiver = (bool) ($package->activates_immediately ?? true);
|
||||
|
||||
if ($requiresWaiver && ! $request->boolean('accepted_waiver')) {
|
||||
throw ValidationException::withMessages([
|
||||
'accepted_waiver' => 'Ein sofortiger Beginn der digitalen Dienstleistung erfordert Ihre ausdrückliche Zustimmung.',
|
||||
]);
|
||||
}
|
||||
|
||||
$session = $this->sessions->createOrResume($user, $package, [
|
||||
'tenant' => $tenant,
|
||||
]);
|
||||
@@ -58,7 +50,7 @@ class PaddleCheckoutController extends Controller
|
||||
'accepted_terms_at' => $now,
|
||||
'accepted_privacy_at' => $now,
|
||||
'accepted_withdrawal_notice_at' => $now,
|
||||
'digital_content_waiver_at' => $requiresWaiver ? $now : null,
|
||||
'digital_content_waiver_at' => null,
|
||||
'legal_version' => $this->resolveLegalVersion(),
|
||||
])->save();
|
||||
|
||||
@@ -95,7 +87,6 @@ class PaddleCheckoutController extends Controller
|
||||
'checkout_session_id' => (string) $session->id,
|
||||
'legal_version' => $session->legal_version,
|
||||
'accepted_terms' => '1',
|
||||
'accepted_waiver' => $requiresWaiver && $request->boolean('accepted_waiver') ? '1' : '0',
|
||||
],
|
||||
'customer' => array_filter([
|
||||
'email' => $user->email,
|
||||
@@ -112,7 +103,6 @@ class PaddleCheckoutController extends Controller
|
||||
'coupon_code' => $couponCode ?: null,
|
||||
'legal_version' => $session->legal_version,
|
||||
'accepted_terms' => true,
|
||||
'accepted_waiver' => $requiresWaiver && $request->boolean('accepted_waiver'),
|
||||
],
|
||||
'discount_id' => $discountId,
|
||||
]);
|
||||
|
||||
@@ -24,7 +24,6 @@ class CheckoutFreeActivationRequest extends FormRequest
|
||||
return [
|
||||
'package_id' => ['required', 'exists:packages,id'],
|
||||
'accepted_terms' => ['required', 'boolean', 'accepted'],
|
||||
'accepted_waiver' => ['nullable', 'boolean'],
|
||||
'locale' => ['nullable', 'string', 'max:10'],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ class PaddleCheckoutRequest extends FormRequest
|
||||
'inline' => ['sometimes', 'boolean'],
|
||||
'coupon_code' => ['nullable', 'string', 'max:64'],
|
||||
'accepted_terms' => ['required', 'boolean', 'accepted'],
|
||||
'accepted_waiver' => ['sometimes', 'boolean'],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ class EventStoreRequest extends FormRequest
|
||||
'settings.watermark.offset_x' => ['nullable', 'integer', 'min:-500', 'max:500'],
|
||||
'settings.watermark.offset_y' => ['nullable', 'integer', 'min:-500', 'max:500'],
|
||||
'settings.watermark_serve_originals' => ['nullable', 'boolean'],
|
||||
'accepted_waiver' => ['nullable', 'boolean'],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,15 @@ class PackageLimitEvaluator
|
||||
{
|
||||
public function assessEventCreation(Tenant $tenant): ?array
|
||||
{
|
||||
$hasEndcustomerPackage = $tenant->tenantPackages()
|
||||
->where('active', true)
|
||||
->whereHas('package', fn ($query) => $query->withTrashed()->where('type', 'endcustomer'))
|
||||
->exists();
|
||||
|
||||
if ($hasEndcustomerPackage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($tenant->hasEventAllowance()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
27
app/Services/Paddle/PaddleCustomerPortalService.php
Normal file
27
app/Services/Paddle/PaddleCustomerPortalService.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Paddle;
|
||||
|
||||
class PaddleCustomerPortalService
|
||||
{
|
||||
public function __construct(private readonly PaddleClient $client) {}
|
||||
|
||||
/**
|
||||
* @param array{subscription_ids?: array<int, string>} $options
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function createSession(string $customerId, array $options = []): array
|
||||
{
|
||||
$payload = [
|
||||
'customer_id' => $customerId,
|
||||
];
|
||||
|
||||
if (! empty($options['subscription_ids'])) {
|
||||
$payload['subscription_ids'] = array_values(
|
||||
array_filter($options['subscription_ids'], 'is_string')
|
||||
);
|
||||
}
|
||||
|
||||
return $this->client->post('/customer-portal-sessions', $payload);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user