authorizeAbility($request, 'settings.view'); $organization = $this->organization($request); $plans = app(\App\Services\Frontdesk\PlanService::class); if (! $plans->hasFeature($organization, 'integrations')) { return view('frontdesk.integrations.upgrade', [ 'organization' => $organization, 'proPriceMinor' => $plans->proPriceMinor(), ]); } $canManage = app(\App\Services\Frontdesk\FrontdeskPermissions::class) ->isAdmin($this->member($request)); $webhook = WebhookEndpoint::owned($this->ownerRef($request)) ->where('organization_id', $organization->id) ->first(); return view('frontdesk.integrations.edit', [ 'organization' => $organization, 'canManage' => $canManage, 'webhook' => $webhook, 'webhookEvents' => config('frontdesk.webhook_events', []), 'integrations' => config('frontdesk.integrations', []), 'icalUrl' => $this->signedIcalUrl($organization->id, $this->ownerRef($request)), 'credential' => $credentials->forOrganization($organization), ]); } public function update(Request $request): RedirectResponse { $this->authorizeAbility($request, 'settings.manage'); $organization = $this->organization($request); if (! app(\App\Services\Frontdesk\PlanService::class)->hasFeature($organization, 'integrations')) { return back()->with('error', 'Webhooks and calendar feeds require Frontdesk Pro.'); } $validated = $request->validate([ 'webhook_url' => ['nullable', 'url', 'max:500'], 'webhook_secret' => ['nullable', 'string', 'max:128'], 'webhook_events' => ['nullable', 'array'], 'webhook_events.*' => ['string'], 'webhook_active' => ['boolean'], ]); if (empty($validated['webhook_url'])) { WebhookEndpoint::owned($this->ownerRef($request)) ->where('organization_id', $organization->id) ->delete(); return back()->with('success', 'Integration settings saved.'); } WebhookEndpoint::updateOrCreate( [ 'owner_ref' => $this->ownerRef($request), 'organization_id' => $organization->id, ], [ 'url' => $validated['webhook_url'], 'secret' => $validated['webhook_secret'] ?? null, 'events' => array_values($validated['webhook_events'] ?? config('frontdesk.webhook_events')), 'is_active' => $request->boolean('webhook_active', true), ], ); return back()->with('success', 'Integration settings saved.'); } public function saveSms(Request $request, MessagingCredentialsService $credentials): RedirectResponse { $this->authorizeAbility($request, 'settings.manage'); $organization = $this->organization($request); if (! app(\App\Services\Frontdesk\PlanService::class)->hasFeature($organization, 'integrations')) { return back()->with('error', 'Messaging integrations require Frontdesk Pro.'); } $data = $request->validate([ 'sms_api_key' => ['required', 'string', 'max:200'], 'sms_sender_id' => ['required', 'string', 'max:11'], ]); $result = $credentials->validateAndSaveSms( $organization, $data['sms_api_key'], $data['sms_sender_id'], ); if (! ($result['ok'] ?? false)) { return back()->withInput()->with('error', $result['error'] ?? 'Could not save SMS credentials.'); } return back()->with('success', 'Ladill SMS connected. Host alerts will use your key and sender ID.'); } public function disconnectSms(Request $request, MessagingCredentialsService $credentials): RedirectResponse { $this->authorizeAbility($request, 'settings.manage'); $organization = $this->organization($request); $credentials->disconnectSms($organization); return back()->with('success', 'Ladill SMS disconnected.'); } public function saveBird(Request $request, MessagingCredentialsService $credentials): RedirectResponse { $this->authorizeAbility($request, 'settings.manage'); $organization = $this->organization($request); if (! app(\App\Services\Frontdesk\PlanService::class)->hasFeature($organization, 'integrations')) { return back()->with('error', 'Messaging integrations require Frontdesk Pro.'); } $data = $request->validate([ 'bird_api_key' => ['required', 'string', 'max:200'], 'bird_from_email' => ['required', 'email', 'max:255'], 'bird_from_name' => ['nullable', 'string', 'max:100'], ]); $result = $credentials->validateAndSaveBird( $organization, $data['bird_api_key'], $data['bird_from_email'], $data['bird_from_name'] ?? null, ); if (! ($result['ok'] ?? false)) { return back()->withInput()->with('error', $result['error'] ?? 'Could not save Bird credentials.'); } return back()->with('success', 'Ladill Bird connected. Host email alerts will use your key and from address.'); } public function disconnectBird(Request $request, MessagingCredentialsService $credentials): RedirectResponse { $this->authorizeAbility($request, 'settings.manage'); $organization = $this->organization($request); $credentials->disconnectBird($organization); return back()->with('success', 'Ladill Bird disconnected.'); } public function previewSenders(Request $request, CustomerSmsClient $sms): JsonResponse { $this->authorizeAbility($request, 'settings.manage'); $data = $request->validate([ 'sms_api_key' => ['required', 'string', 'max:200'], ]); $result = $sms->senders(trim($data['sms_api_key'])); if (! ($result['ok'] ?? false)) { return response()->json(['error' => $result['error'] ?? 'Could not load senders.'], 422); } return response()->json($result['data'] ?? []); } protected function signedIcalUrl(int $organizationId, string $ownerRef): string { $token = hash_hmac('sha256', "{$organizationId}:{$ownerRef}", (string) config('app.key')); return route('frontdesk.integrations.ical', [ 'organization' => $organizationId, 'owner' => $ownerRef, 'token' => $token, ]); } }