Deploy Ladill Mini / deploy (push) Successful in 40s
Persist payment QR business fields on update, handle is_active correctly, and keep the amount bottom sheet flush to the screen while moving with the keyboard on iOS. Co-authored-by: Cursor <cursoragent@cursor.com>
181 lines
5.8 KiB
PHP
181 lines
5.8 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Mini;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\QrCode;
|
|
use App\Services\Qr\QrCodeManagerService;
|
|
use App\Services\Qr\QrImageGeneratorService;
|
|
use App\Services\Qr\QrPdfExporter;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\View\View;
|
|
use RuntimeException;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
|
|
|
class PaymentQrController extends Controller
|
|
{
|
|
public function __construct(
|
|
private QrCodeManagerService $manager,
|
|
private QrImageGeneratorService $imageGenerator,
|
|
private QrPdfExporter $pdfExporter,
|
|
) {}
|
|
|
|
public function index(Request $request): View
|
|
{
|
|
$account = ladill_account();
|
|
$qrCodes = $account->qrCodes()
|
|
->where('type', QrCode::TYPE_PAYMENT)
|
|
->latest()
|
|
->get();
|
|
|
|
$previewDataUris = $qrCodes->mapWithKeys(function (QrCode $qr) {
|
|
return [$qr->id => $this->imageGenerator->previewDataUri($qr)];
|
|
});
|
|
|
|
return view('mini.payment-qrs.index', [
|
|
'qrCodes' => $qrCodes,
|
|
'previewDataUris' => $previewDataUris,
|
|
]);
|
|
}
|
|
|
|
public function create(): View
|
|
{
|
|
return view('mini.payment-qrs.create');
|
|
}
|
|
|
|
public function store(Request $request): RedirectResponse
|
|
{
|
|
$account = ladill_account();
|
|
|
|
$data = $request->validate([
|
|
'label' => 'required|string|max:120',
|
|
'business_name' => 'required|string|max:120',
|
|
'branch_label' => 'nullable|string|max:80',
|
|
]);
|
|
|
|
$data['type'] = QrCode::TYPE_PAYMENT;
|
|
$data['currency'] = 'GHS';
|
|
|
|
try {
|
|
$qrCode = $this->manager->create($account, $data);
|
|
} catch (RuntimeException $e) {
|
|
return back()->withInput()->with('error', $e->getMessage());
|
|
}
|
|
|
|
return redirect()->route('mini.payment-qrs.show', $qrCode)->with('success', 'Payment QR created.');
|
|
}
|
|
|
|
public function show(QrCode $paymentQr): View
|
|
{
|
|
$this->authorizePaymentQr($paymentQr);
|
|
|
|
$previewDataUri = $this->imageGenerator->previewDataUri($paymentQr);
|
|
|
|
return view('mini.payment-qrs.show', [
|
|
'qrCode' => $paymentQr->fresh(),
|
|
'previewDataUri' => $previewDataUri,
|
|
]);
|
|
}
|
|
|
|
public function update(Request $request, QrCode $paymentQr): RedirectResponse
|
|
{
|
|
$this->authorizePaymentQr($paymentQr);
|
|
|
|
$data = $request->validate([
|
|
'label' => 'sometimes|string|max:120',
|
|
'business_name' => 'sometimes|string|max:120',
|
|
'branch_label' => 'nullable|string|max:80',
|
|
'is_active' => 'sometimes|boolean',
|
|
]);
|
|
|
|
$data['is_active'] = $request->boolean('is_active', $paymentQr->is_active);
|
|
|
|
try {
|
|
$this->manager->update($paymentQr, $data);
|
|
} catch (RuntimeException $e) {
|
|
return back()->withInput()->with('error', $e->getMessage());
|
|
}
|
|
|
|
return back()->with('success', 'Payment QR updated.');
|
|
}
|
|
|
|
public function destroy(QrCode $paymentQr): RedirectResponse
|
|
{
|
|
$this->authorizePaymentQr($paymentQr);
|
|
|
|
$this->manager->delete($paymentQr);
|
|
|
|
return redirect()
|
|
->route('mini.payment-qrs.index')
|
|
->with('success', 'Payment QR deleted.');
|
|
}
|
|
|
|
public function preview(QrCode $paymentQr): Response
|
|
{
|
|
$this->authorizePaymentQr($paymentQr);
|
|
|
|
$qrCode = $this->imageGenerator->ensureValidImages($paymentQr);
|
|
$bytes = $this->imageGenerator->normalizeStoredPng($qrCode->png_path);
|
|
|
|
if ($bytes === null) {
|
|
$bytes = $this->imageGenerator->renderPng($qrCode->encodedPayload(), $qrCode->style());
|
|
$this->imageGenerator->generateAndStore($qrCode);
|
|
}
|
|
|
|
return response($bytes, 200, [
|
|
'Content-Type' => 'image/png',
|
|
'Cache-Control' => 'private, max-age=3600',
|
|
]);
|
|
}
|
|
|
|
public function download(QrCode $paymentQr, string $format): StreamedResponse
|
|
{
|
|
$this->authorizePaymentQr($paymentQr);
|
|
|
|
$qrCode = $this->imageGenerator->ensureValidImages($paymentQr);
|
|
$path = $format === 'svg' ? $qrCode->svg_path : $qrCode->png_path;
|
|
$filename = Str::slug($qrCode->label).'-qr.'.($format === 'svg' ? 'svg' : 'png');
|
|
|
|
if ($format === 'png') {
|
|
$bytes = $this->imageGenerator->normalizeStoredPng($path);
|
|
if ($bytes === null) {
|
|
$bytes = $this->imageGenerator->renderPng($qrCode->encodedPayload(), $qrCode->style());
|
|
$this->imageGenerator->generateAndStore($qrCode);
|
|
}
|
|
|
|
return response()->streamDownload(fn () => print($bytes), $filename, [
|
|
'Content-Type' => 'image/png',
|
|
]);
|
|
}
|
|
|
|
if ($format === 'pdf') {
|
|
$bytes = $this->imageGenerator->normalizeStoredPng($qrCode->png_path);
|
|
if ($bytes === null) {
|
|
$bytes = $this->imageGenerator->renderPng($qrCode->encodedPayload(), $qrCode->style());
|
|
$this->imageGenerator->generateAndStore($qrCode);
|
|
}
|
|
|
|
$pdf = $this->pdfExporter->fromPng($bytes, $qrCode->label);
|
|
$filename = Str::slug($qrCode->label).'-qr.pdf';
|
|
|
|
return response()->streamDownload(fn () => print($pdf), $filename, [
|
|
'Content-Type' => 'application/pdf',
|
|
]);
|
|
}
|
|
|
|
abort_unless($path && Storage::disk('qr')->exists($path), 404);
|
|
|
|
return Storage::disk('qr')->download($path, $filename);
|
|
}
|
|
|
|
private function authorizePaymentQr(QrCode $paymentQr): void
|
|
{
|
|
abort_unless($paymentQr->type === QrCode::TYPE_PAYMENT, 404);
|
|
abort_unless($paymentQr->user_id === ladill_account()->id, 403);
|
|
}
|
|
}
|