feat: qr code generation

- QrCodeService: generate unique 48-char token, create QR PNG (400x400, error-correction H)
- QrCodeController: show, generate, download PNG, deactivate
- Admin QR page: preview, copy URL, download, regenerate, deactivate
- Existing active QR deactivated on regenerate
- Token-based URL (not program ID) for PDPA compliance

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Saufi
2026-05-16 19:42:33 +08:00
parent d0be749f29
commit 12324091dd
3 changed files with 274 additions and 2 deletions

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Services;
use App\Models\Program;
use App\Models\ProgramQrCode;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use SimpleSoftwareIO\QrCode\Facades\QrCode;
class QrCodeService
{
public function generateForProgram(Program $program): ProgramQrCode
{
// Deactivate existing active QR codes
$program->qrCodes()->where('is_active', true)->update(['is_active' => false]);
$token = Str::random(48);
$url = route('public.checkin.show', $token);
$path = 'public/qrcodes/' . $token . '.png';
$absPath = Storage::path($path);
// Ensure directory exists
Storage::makeDirectory('public/qrcodes');
// Generate QR code PNG (400×400, with quiet zone)
$png = QrCode::format('png')
->size(400)
->margin(2)
->errorCorrection('H')
->generate($url);
Storage::put($path, $png);
return $program->qrCodes()->create([
'token' => $token,
'qr_image_path' => $path,
'is_active' => true,
]);
}
public function getPublicUrl(ProgramQrCode $qrCode): string
{
return Storage::url($qrCode->qr_image_path);
}
public function getRawPng(ProgramQrCode $qrCode): string
{
return Storage::get($qrCode->qr_image_path);
}
}