Initial Ladill Give extraction — online giving pages at give.ladill.com.
Donation checkout via Ladill Pay (9% fee), church/giving QR pages, SSO, and platform scan forwarding. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Qr;
|
||||
|
||||
use App\Models\QrCode;
|
||||
use App\Models\QrScanEvent;
|
||||
use App\Models\QrWallet;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class QrScanRecorder
|
||||
{
|
||||
public function record(QrCode $qrCode, Request $request): QrScanEvent
|
||||
{
|
||||
$parsed = $this->parseUserAgent((string) $request->userAgent());
|
||||
$ipHash = $this->hashIp((string) $request->ip());
|
||||
$windowHours = (int) config('qr.scan_unique_window_hours', 24);
|
||||
$isUnique = ! QrScanEvent::query()
|
||||
->where('qr_code_id', $qrCode->id)
|
||||
->where('ip_hash', $ipHash)
|
||||
->where('scanned_at', '>=', now()->subHours($windowHours))
|
||||
->exists();
|
||||
|
||||
$event = QrScanEvent::create([
|
||||
'qr_code_id' => $qrCode->id,
|
||||
'scanned_at' => now(),
|
||||
'ip_hash' => $ipHash,
|
||||
'user_agent' => Str::limit((string) $request->userAgent(), 500, ''),
|
||||
'device_type' => $parsed['device_type'],
|
||||
'browser' => $parsed['browser'],
|
||||
'os' => $parsed['os'],
|
||||
'referrer' => Str::limit((string) $request->headers->get('referer'), 255, ''),
|
||||
'is_unique' => $isUnique,
|
||||
]);
|
||||
|
||||
$qrCode->increment('scans_total');
|
||||
if ($isUnique) {
|
||||
$qrCode->increment('unique_scans_total');
|
||||
}
|
||||
$qrCode->update(['last_scanned_at' => now()]);
|
||||
|
||||
QrWallet::query()
|
||||
->where('user_id', $qrCode->user_id)
|
||||
->increment('scans_total');
|
||||
|
||||
return $event;
|
||||
}
|
||||
|
||||
private function hashIp(string $ip): string
|
||||
{
|
||||
return hash('sha256', $ip . '|' . (string) config('app.key'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{device_type: string, browser: string, os: string}
|
||||
*/
|
||||
private function parseUserAgent(string $ua): array
|
||||
{
|
||||
$uaLower = strtolower($ua);
|
||||
|
||||
$device = str_contains($uaLower, 'mobile') || str_contains($uaLower, 'android') || str_contains($uaLower, 'iphone')
|
||||
? 'mobile'
|
||||
: (str_contains($uaLower, 'tablet') || str_contains($uaLower, 'ipad') ? 'tablet' : 'desktop');
|
||||
|
||||
$browser = match (true) {
|
||||
str_contains($uaLower, 'edg/') => 'Edge',
|
||||
str_contains($uaLower, 'chrome/') && ! str_contains($uaLower, 'edg/') => 'Chrome',
|
||||
str_contains($uaLower, 'safari/') && ! str_contains($uaLower, 'chrome/') => 'Safari',
|
||||
str_contains($uaLower, 'firefox/') => 'Firefox',
|
||||
default => 'Other',
|
||||
};
|
||||
|
||||
$os = match (true) {
|
||||
str_contains($uaLower, 'iphone') || str_contains($uaLower, 'ipad') => 'iOS',
|
||||
str_contains($uaLower, 'android') => 'Android',
|
||||
str_contains($uaLower, 'windows') => 'Windows',
|
||||
str_contains($uaLower, 'mac os') || str_contains($uaLower, 'macintosh') => 'macOS',
|
||||
str_contains($uaLower, 'linux') => 'Linux',
|
||||
default => 'Other',
|
||||
};
|
||||
|
||||
return [
|
||||
'device_type' => $device,
|
||||
'browser' => $browser,
|
||||
'os' => $os,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user