qrCodes() ->where('type', QrCode::TYPE_PAYMENT) ->latest() ->get(); return response()->json([ 'data' => $qrCodes->map(fn (QrCode $qr) => PaymentPresenter::presentQr($qr, $this->previewUrl($qr)))->values(), ]); } public function store(Request $request): JsonResponse { abort_unless($request->user()->tokenCan('mini:write'), 403, 'Token requires mini:write ability.'); $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(ladill_account(), $data); } catch (RuntimeException $e) { return response()->json(['message' => $e->getMessage()], 422); } return response()->json([ 'data' => PaymentPresenter::presentQr($qrCode->fresh(), $this->previewUrl($qrCode)), ], 201); } public function show(QrCode $paymentQr): JsonResponse { $this->authorizePaymentQr($paymentQr); return response()->json([ 'data' => PaymentPresenter::presentQr($paymentQr, $this->previewUrl($paymentQr)), ]); } public function update(Request $request, QrCode $paymentQr): JsonResponse { abort_unless($request->user()->tokenCan('mini:write'), 403, 'Token requires mini:write ability.'); $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'], ]); if ($request->has('is_active')) { $data['is_active'] = $request->boolean('is_active'); } try { $this->manager->update($paymentQr, $data); } catch (RuntimeException $e) { return response()->json(['message' => $e->getMessage()], 422); } return response()->json([ 'data' => PaymentPresenter::presentQr($paymentQr->fresh(), $this->previewUrl($paymentQr)), ]); } public function destroy(QrCode $paymentQr): JsonResponse { abort_unless(request()->user()->tokenCan('mini:write'), 403, 'Token requires mini:write ability.'); $this->authorizePaymentQr($paymentQr); $this->manager->delete($paymentQr); return response()->json(['data' => ['message' => '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', ]); } private function previewUrl(QrCode $qr): string { return route('api.mini.payment-qrs.preview', $qr); } private function authorizePaymentQr(QrCode $paymentQr): void { abort_unless($paymentQr->type === QrCode::TYPE_PAYMENT, 404); abort_unless($paymentQr->user_id === ladill_account()->id, 403); } }