feat(guest): guest checkout without login (Swish + QR) #17

Open
hermes wants to merge 4 commits from feature/guest-checkout into master
16 changed files with 1377 additions and 2 deletions

View file

@ -55,6 +55,7 @@ public class SecurityConfig {
.permitAll()
.requestMatchers("/api/webhooks/**").permitAll()
.requestMatchers("/api/payment/swish-info").permitAll()
.requestMatchers("/api/guest-orders/**").permitAll()
.requestMatchers("/api/vehicles/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated())

View file

@ -0,0 +1,75 @@
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
package se.bilhalsning.controller;
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
import jakarta.validation.Valid;
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
import lombok.RequiredArgsConstructor;
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
import org.springframework.http.HttpStatus;
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
import org.springframework.http.ResponseEntity;
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
import org.springframework.web.bind.annotation.GetMapping;
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
import org.springframework.web.bind.annotation.PathVariable;
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
import org.springframework.web.bind.annotation.PostMapping;
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
import org.springframework.web.bind.annotation.RequestBody;
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
import org.springframework.web.bind.annotation.RequestMapping;
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
import org.springframework.web.bind.annotation.RestController;
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
import se.bilhalsning.dto.CreateGuestOrderRequest;
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
import se.bilhalsning.dto.GuestOrderResponse;
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
import se.bilhalsning.entity.Order;
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
import se.bilhalsning.service.OrderService;
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
import java.util.UUID;
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
/**
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
* Public (no-JWT) endpoints for placing and paying for orders without an
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
* account guest checkout.
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
*
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
* Auth: white-listed in {@code SecurityConfig} so no JWT filter runs on
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
* these paths. The guest token (UUID v4, generated at order create time
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
* in {@link Order#onCreate()}) is the only credential the client holds
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
* and must pass as a path variable. Token brute-force resistance: 122
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
* bits of entropy.
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
*
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
* Routes parallel {@link OrderController} deliberately so the JWT path
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
* stays clean and unmodified.
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
*/
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
@RestController
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
@RequestMapping("/api/guest-orders")
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
@RequiredArgsConstructor
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
public class GuestOrderController {
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
private final OrderService orderService;
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
@PostMapping
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
public ResponseEntity<GuestOrderResponse> create(
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
@Valid @RequestBody CreateGuestOrderRequest request) {
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
Order order = orderService.createGuestOrder(
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
request.plate(),
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
request.letterText(),
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
request.email()
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
);
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
return ResponseEntity.status(HttpStatus.CREATED).body(toResponse(order));
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
}
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
@GetMapping("/{token}")
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
public ResponseEntity<GuestOrderResponse> get(@PathVariable UUID token) {
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
Order order = orderService.getOrderByGuestToken(token);
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
return ResponseEntity.ok(toResponse(order));
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
}
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
@PostMapping("/{token}/pay")
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
public ResponseEntity<GuestOrderResponse> pay(@PathVariable UUID token) {
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
Order order = orderService.confirmGuestPayment(token);
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
return ResponseEntity.ok(toResponse(order));
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
}
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
private GuestOrderResponse toResponse(Order order) {
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
return new GuestOrderResponse(
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
order.getId(),
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
order.getPlate(),
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
order.getLetterText(),
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
order.getStatus().getValue(),
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
order.getTrackingId(),
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
order.getAmountPaid(),
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
order.getCreatedAt(),
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
order.getGuestToken()
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
);
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
}
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.
}
Review

🔴 Critical — public unauthenticated create with no abuse protection. This endpoint is permitAll and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.

🔴 **Critical — public unauthenticated create with no abuse protection.** This endpoint is `permitAll` and writes a DB row per call with no rate limit, captcha, or pre-check. Trivial to abuse (orders-table flooding / cleanup cost). Suggest an IP rate limit + per-email throttle, requiring a successful plate/vehicle lookup first, or a lightweight captcha for the public create path.
Review

🟠 Honor-system pay — no payment verification. confirmGuestPayment marks the order PROCESSING without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated confirmPayment, but harden with Swish Commerce callback verification before real money. Also: amountPaid / PAID status are never set on either path.

🟠 **Honor-system pay — no payment verification.** `confirmGuestPayment` marks the order `PROCESSING` without confirming any Swish payment landed. Anyone with the token (or the customer) can mark an order paid without paying → BilHej mails a free letter. Acknowledged as Phase 0 and consistent with the authenticated `confirmPayment`, but harden with Swish Commerce callback verification before real money. Also: `amountPaid` / `PAID` status are never set on either path.
Review

🟡 No rate limiting on this public endpoint.

POST /api/guest-orders is fully public (permitAll) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated /api/payment/{id}/pay path, there's no user account to ban or audit.

Add per-IP rate limiting (bucket4j, Spring @RateLimiter, or nginx limit_req).

🟡 **No rate limiting on this public endpoint.** `POST /api/guest-orders` is fully public (`permitAll`) with no throttle. An attacker can flood order creation (DB bloat / DoS) or mass-generate self-confirmed "paid" orders for free letters — and unlike the authenticated `/api/payment/{id}/pay` path, there's no user account to ban or audit. Add per-IP rate limiting (bucket4j, Spring `@RateLimiter`, or nginx `limit_req`).
Review

🟡 Honor-system pay — no payment verification.

confirmGuestPayment sets status to PROCESSING + triggers notifyOrderProcessing (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity.

Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

🟡 **Honor-system pay — no payment verification.** `confirmGuestPayment` sets status to `PROCESSING` + triggers `notifyOrderProcessing` (fulfillment) purely on self-confirmation. No Swish callback or API check. This mirrors the existing authenticated path (accepted Phase-0 design), but the guest path removes even account-level traceability — a customer gets a letter printed & mailed for 0 SEK with zero identity. Until Tier 1 (Swish Commerce API + mTLS callback) lands, consider not auto-triggering fulfillment here, or flagging self-confirmed guest orders for manual review before mailing.

View file

@ -0,0 +1,27 @@
package se.bilhalsning.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
/**
* Create an order without an account (guest checkout).
*
* The {@code plate} validation mirrors {@link CreateOrderRequest} so the
* guest path accepts the same Swedish 3-letter + 3-char plate format.
*/
public record CreateGuestOrderRequest(
@NotBlank(message = "Registreringsnummer krävs")
@Pattern(regexp = "^[A-Za-z]{3}\\d{2}[A-Za-z0-9]$", message = "Ogiltigt registreringsnummer")
String plate,
@NotBlank(message = "Brevtext krävs")
@Size(min = 1, max = 1000, message = "Brevtexten måste vara mellan 1 och 1000 tecken")
String letterText,
@NotBlank(message = "E-post krävs")
@Email(message = "Ogiltig e-postadress")
@Size(max = 255, message = "E-postadressen är för lång")
String email
) {}

View file

@ -0,0 +1,24 @@
package se.bilhalsning.dto;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.UUID;
/**
* Response shape for guest-order endpoints.
*
* Identical to {@link OrderResponse} plus {@link #guestToken}, which the
* client needs to (a) build the payment page URL and (b) look up the
* order again without an account. Token is returned only on create and
* by-token lookup it is never re-exposed by order ID alone.
*/
public record GuestOrderResponse(
UUID id,
String plate,
String letterText,
String status,
String trackingId,
BigDecimal amountPaid,
Instant createdAt,
UUID guestToken
) {}

View file

@ -21,7 +21,12 @@ public class Order {
@Column(name = "id", columnDefinition = "uuid", nullable = false, updatable = false)
private UUID id;
@Column(name = "user_id", nullable = false, columnDefinition = "uuid")
/**
* Null for guest orders (no registered user). FK to {@code users(id)}
* is still in place NULL is FK-legal. Either {@code userId} or
* {@code guestToken} is set; never both, never neither.
*/
@Column(name = "user_id", columnDefinition = "uuid")
private UUID userId;
@ManyToOne(fetch = FetchType.LAZY)
@ -55,11 +60,31 @@ public class Order {
@Column(name = "updated_at", nullable = false)
private Instant updatedAt;
/**
* Guest contact email. Stored at order create time so a magic link
* (carrying {@code guestToken}) can be emailed to the customer later.
*/
@Column(name = "guest_email", length = 255)
private String guestEmail;
/**
* Opaque UUID v4 token. The only credential a guest holds. Used as
* the path variable on all {@code /api/guest-orders/{token}/...}
* endpoints. Generated in {@link #onCreate()} for guest orders only.
*/
@Column(name = "guest_token", columnDefinition = "uuid")
private UUID guestToken;
@PrePersist
void onCreate() {
if (this.id == null) {
this.id = UUID.randomUUID();
}
// Guest orders (no userId) get a token for unauthenticated lookup.
// User-owned orders never get one they go through JWT + userId.
if (this.guestToken == null && this.userId == null) {
this.guestToken = UUID.randomUUID();
}
Instant now = Instant.now();
if (this.createdAt == null) {
this.createdAt = now;
@ -159,4 +184,20 @@ public class Order {
public Instant getUpdatedAt() {
return updatedAt;
}
public String getGuestEmail() {
return guestEmail;
}
public void setGuestEmail(String guestEmail) {
this.guestEmail = guestEmail;
}
public UUID getGuestToken() {
return guestToken;
}
public void setGuestToken(UUID guestToken) {
this.guestToken = guestToken;
}
}

View file

@ -21,4 +21,8 @@ public interface OrderRepository extends JpaRepository<Order, UUID> {
@EntityGraph(attributePaths = {"user"})
Optional<Order> findWithUserById(UUID id);
// Guest checkout looks up by opaque token, no JWT. Partial-unique
// index on (guest_token) WHERE NOT NULL enforces uniqueness on writes.
Optional<Order> findByGuestToken(UUID guestToken);
}

View file

@ -27,6 +27,56 @@ public class OrderService {
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
return orderRepository.save(order);
}
/**
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
* Guest checkout creates an order with no registered user. The
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
* {@code guestToken} is generated in {@link Order#onCreate()} so the
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
* caller does not need to handle token creation logic. Returned
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
* order's {@link Order#getGuestToken()} is the only credential the
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
* client receives.
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
*/
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
public Order createGuestOrder(String plate, String letterText, String email) {
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
Order order = new Order();
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
// userId stays null guest order. guestToken auto-generated in PrePersist.
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
order.setGuestEmail(email == null ? null : email.trim().toLowerCase());
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
order.setPlate(plate.toUpperCase().trim());
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
order.setLetterText(letterText);
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
order.setStatus(OrderStatus.PENDING_PAYMENT);
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
return orderRepository.save(order);
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
}
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
/**
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
* Guest-path order lookup by opaque token. Never exposes user-owned
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
* orders via this path: if a guest token resolves to an order with a
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
* non-null {@code userId} (corrupted data, manual SQL insert, etc.),
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
* treat as not-found rather than leak the order.
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
*/
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
public Order getOrderByGuestToken(UUID guestToken) {
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
Order order = orderRepository.findByGuestToken(guestToken)
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
.orElseThrow(() -> new OrderNotFoundException(guestToken));
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
if (order.getUserId() != null) {
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
// Token points at a user-owned order refuse to serve it.
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
throw new OrderNotFoundException(guestToken);
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
}
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
return order;
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
}
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
/**
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
* Honor-system payment confirmation for guest orders. Mirrors
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
* {@link #confirmPayment(UUID, UUID)} but authenticates via the
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
* guest token instead of {@code userId}.
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
*/
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
public Order confirmGuestPayment(UUID guestToken) {
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
Order order = getOrderByGuestToken(guestToken);
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
if (order.getStatus() != OrderStatus.PENDING_PAYMENT) {
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
throw new InvalidOrderStateException(
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
"Beställningen kan inte ändras i detta tillstånd");
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
}
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
order.setStatus(OrderStatus.PROCESSING);
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
Order saved = orderRepository.save(order);
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
orderNotificationService.notifyOrderProcessing(saved);
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
return saved;
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
}
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
public List<Order> getOrdersByUserId(UUID userId) {
return orderRepository.findByUserIdOrderByCreatedAtDesc(userId);
}

Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.
Review

🔴 Critical — honor-system payment on a public route. confirmGuestPayment transitions PENDING_PAYMENT → PROCESSING purely on the client's button click, with no Swish payment verification. Pre-existing on the authed confirmPayment, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set amountPaid (and a paid_at) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.

🔴 **Critical — honor-system payment on a public route.** `confirmGuestPayment` transitions `PENDING_PAYMENT → PROCESSING` purely on the client's button click, with no Swish payment verification. Pre-existing on the authed `confirmPayment`, but this guest endpoint is JWT-less, so the only barrier is knowing the guest token. At minimum set `amountPaid` (and a `paid_at`) here so finance can reconcile against the Swish payout report; ideally gate the transition on a Swish Commerce payment request + callback.
Review

🟢 No tests for the new guest methods. OrderServiceTest already covers createOrder/confirmPayment/cancelOrder/updatePendingOrder with the existing Mockito pattern. Please mirror that for createGuestOrder (plate uppercased, email lowercased, guestToken set via onCreate), getOrderByGuestToken (404 when userId is non-null — the defensive guard deserves a regression test of its own), and confirmGuestPayment (transition + notifyOrderProcessing invocation).

🟢 **No tests for the new guest methods.** `OrderServiceTest` already covers `createOrder`/`confirmPayment`/`cancelOrder`/`updatePendingOrder` with the existing Mockito pattern. Please mirror that for `createGuestOrder` (plate uppercased, email lowercased, `guestToken` set via `onCreate`), `getOrderByGuestToken` (404 when `userId` is non-null — the defensive guard deserves a regression test of its own), and `confirmGuestPayment` (transition + `notifyOrderProcessing` invocation).
Review

🟡 amountPaid is never set on confirmation.

confirmGuestPayment sets status to PROCESSING but never calls order.setAmountPaid(...). Every guest order response returns amountPaid: null, even after "payment." Record the expected amount (from app.payment.letter-price) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

🟡 **`amountPaid` is never set on confirmation.** `confirmGuestPayment` sets status to `PROCESSING` but never calls `order.setAmountPaid(...)`. Every guest order response returns `amountPaid: null`, even after "payment." Record the expected amount (from `app.payment.letter-price`) on confirmation so billing/reconciliation can distinguish paid-value from never-attempted.

View file

@ -0,0 +1,25 @@
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
-- Allows orders without a registered user (guest checkout).
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
-- Users can place and pay for letters without creating an account.
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
--
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
-- user_id: previously NOT NULL - drop the constraint so guest orders
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
-- can be created without a registered user. The FK stays in
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
-- place (NULL user_id is FK-legal).
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
-- guest_email: contact address for the guest. Used to send the magic
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
-- link that lets them revisit their order status.
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
-- guest_token: opaque UUID v4 - the only credential a guest has. Acts
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
-- as their session token for order lookup + payment confirm.
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
ALTER TABLE orders ALTER COLUMN user_id DROP NOT NULL;
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
ALTER TABLE orders ADD COLUMN guest_email VARCHAR(255);
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
ALTER TABLE orders ADD COLUMN guest_token UUID;
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
-- Unique index on guest_token. Both H2 (tests/dev) and PostgreSQL (prod)
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
-- treat NULLs as distinct in a UNIQUE index, so user-owned orders (which
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
-- have a NULL token) never collide, while non-NULL guest tokens are
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
-- enforced unique. A plain index is used instead of a partial
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
-- (WHERE guest_token IS NOT NULL) index because H2 does not support
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
-- partial indexes, and the plain form preserves the intended semantics.
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
CREATE UNIQUE INDEX idx_orders_guest_token
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
ON orders(guest_token);
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
CREATE INDEX idx_orders_guest_email
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.
ON orders(guest_email);
Review

🟡 Missing DB-level orphan guard. The Order Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only onCreate() enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions:

ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest
    CHECK ((user_id IS NULL) <> (guest_token IS NULL));
🟡 **Missing DB-level orphan guard.** The `Order` Javadoc asserts "Either userId or guestToken is set; never both, never neither" — but only `onCreate()` enforces this in Java. A stray INSERT (admin tooling, future script) can violate it. Suggest adding after the column additions: ```sql ALTER TABLE orders ADD CONSTRAINT chk_user_or_guest CHECK ((user_id IS NULL) <> (guest_token IS NULL)); ```
Review

🔵 Unused index. idx_orders_guest_email is created but findByGuestEmail was never added to OrderRepository (the PR body lists it, but only findByGuestToken is there), so there's no read path on guest_email yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

🔵 **Unused index.** `idx_orders_guest_email` is created but `findByGuestEmail` was never added to `OrderRepository` (the PR body lists it, but only `findByGuestToken` is there), so there's no read path on `guest_email` yet. Either add the lookup now or defer the index until the email-link phase to avoid an unused schema object.

View file

@ -0,0 +1,174 @@
package se.bilhalsning.controller;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.UUID;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.MockMvc;
import se.bilhalsning.entity.Order;
import se.bilhalsning.entity.OrderStatus;
import se.bilhalsning.exception.InvalidOrderStateException;
import se.bilhalsning.exception.OrderNotFoundException;
import se.bilhalsning.service.OrderService;
import se.bilhalsning.service.UserService;
@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource(properties = "app.jwt.secret=this-is-a-test-secret-that-is-at-least-32-bytes-long!!")
class GuestOrderControllerTest {
@Autowired
private MockMvc mockMvc;
@MockitoBean
private OrderService orderService;
@MockitoBean
private UserService userService;
// --- POST /api/guest-orders (create) ---
@Test
void shouldCreateGuestOrderWithoutAuth() throws Exception {
UUID orderId = UUID.fromString("d1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11");
UUID guestToken = UUID.fromString("e2eebc99-9c0b-4ef8-bb6d-6bb9bd380a11");
Order savedOrder = new Order();
savedOrder.setId(orderId);
savedOrder.setPlate("ABC123");
savedOrder.setLetterText("Hej fin bil!");
savedOrder.setStatus(OrderStatus.PENDING_PAYMENT);
savedOrder.setGuestEmail("guest@example.com");
savedOrder.setGuestToken(guestToken);
when(orderService.createGuestOrder("ABC123", "Hej fin bil!", "guest@example.com"))
.thenReturn(savedOrder);
mockMvc.perform(post("/api/guest-orders")
.contentType("application/json")
.content("{\"plate\":\"ABC123\",\"letterText\":\"Hej fin bil!\",\"email\":\"guest@example.com\"}"))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.id").value(orderId.toString()))
.andExpect(jsonPath("$.plate").value("ABC123"))
.andExpect(jsonPath("$.letterText").value("Hej fin bil!"))
.andExpect(jsonPath("$.status").value("pending_payment"))
.andExpect(jsonPath("$.guestToken").value(guestToken.toString()));
}
@Test
void shouldRejectInvalidPlateFormat() throws Exception {
mockMvc.perform(post("/api/guest-orders")
.contentType("application/json")
.content("{\"plate\":\"INVALID\",\"letterText\":\"Hej\",\"email\":\"guest@example.com\"}"))
.andExpect(status().isBadRequest());
}
@Test
void shouldRejectBlankEmail() throws Exception {
mockMvc.perform(post("/api/guest-orders")
.contentType("application/json")
.content("{\"plate\":\"ABC123\",\"letterText\":\"Hej\",\"email\":\"\"}"))
.andExpect(status().isBadRequest());
}
@Test
void shouldRejectInvalidEmail() throws Exception {
mockMvc.perform(post("/api/guest-orders")
.contentType("application/json")
.content("{\"plate\":\"ABC123\",\"letterText\":\"Hej\",\"email\":\"not-an-email\"}"))
.andExpect(status().isBadRequest());
}
@Test
void shouldRejectBlankLetterText() throws Exception {
mockMvc.perform(post("/api/guest-orders")
.contentType("application/json")
.content("{\"plate\":\"ABC123\",\"letterText\":\"\",\"email\":\"guest@example.com\"}"))
.andExpect(status().isBadRequest());
}
// --- GET /api/guest-orders/{token} ---
@Test
void shouldGetGuestOrderByToken() throws Exception {
UUID token = UUID.fromString("e2eebc99-9c0b-4ef8-bb6d-6bb9bd380a11");
UUID orderId = UUID.fromString("d1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11");
Order order = new Order();
order.setId(orderId);
order.setPlate("ABC123");
order.setLetterText("Hej bil!");
order.setStatus(OrderStatus.PROCESSING);
order.setGuestToken(token);
when(orderService.getOrderByGuestToken(token)).thenReturn(order);
mockMvc.perform(get("/api/guest-orders/{token}", token))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(orderId.toString()))
.andExpect(jsonPath("$.plate").value("ABC123"))
.andExpect(jsonPath("$.status").value("processing"))
.andExpect(jsonPath("$.guestToken").value(token.toString()));
}
@Test
void shouldReturn404WhenGuestOrderTokenNotFound() throws Exception {
UUID token = UUID.randomUUID();
when(orderService.getOrderByGuestToken(token))
.thenThrow(new OrderNotFoundException(token));
mockMvc.perform(get("/api/guest-orders/{token}", token))
.andExpect(status().isNotFound());
}
// --- POST /api/guest-orders/{token}/pay ---
@Test
void shouldConfirmGuestPayment() throws Exception {
UUID token = UUID.fromString("e2eebc99-9c0b-4ef8-bb6d-6bb9bd380a11");
UUID orderId = UUID.fromString("d1eebc99-9c0b-4ef8-bb6d-6bb9bd380a11");
Order order = new Order();
order.setId(orderId);
order.setPlate("ABC123");
order.setLetterText("Hej bil!");
order.setStatus(OrderStatus.PROCESSING);
order.setGuestToken(token);
when(orderService.confirmGuestPayment(token)).thenReturn(order);
mockMvc.perform(post("/api/guest-orders/{token}/pay", token))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(orderId.toString()))
.andExpect(jsonPath("$.status").value("processing"));
}
@Test
void shouldReturn409WhenPaymentAlreadyConfirmed() throws Exception {
UUID token = UUID.randomUUID();
when(orderService.confirmGuestPayment(token))
.thenThrow(new InvalidOrderStateException("Beställningen kan inte ändras i detta tillstånd"));
mockMvc.perform(post("/api/guest-orders/{token}/pay", token))
.andExpect(status().isConflict());
}
@Test
void shouldReturn404WhenPayingWithUnknownToken() throws Exception {
UUID token = UUID.randomUUID();
when(orderService.confirmGuestPayment(token))
.thenThrow(new OrderNotFoundException(token));
mockMvc.perform(post("/api/guest-orders/{token}/pay", token))
.andExpect(status().isNotFound());
}
}

View file

@ -253,4 +253,122 @@ class OrderServiceTest {
() -> orderService.confirmPayment(orderId, otherUserId));
}
// --- Guest order: createGuestOrder ---
@Test
void shouldCreateGuestOrderWithPendingPaymentStatus() {
when(orderRepository.save(any(Order.class))).thenAnswer(inv -> inv.getArgument(0));
Order result = orderService.createGuestOrder("ABC123", "Hej fin bil!", "guest@example.com");
assertEquals(OrderStatus.PENDING_PAYMENT, result.getStatus());
assertEquals("ABC123", result.getPlate());
assertEquals("Hej fin bil!", result.getLetterText());
assertEquals("guest@example.com", result.getGuestEmail());
assertNull(result.getUserId());
}
@Test
void shouldNormalizeGuestPlateToUppercaseAndTrim() {
when(orderRepository.save(any(Order.class))).thenAnswer(inv -> inv.getArgument(0));
Order result = orderService.createGuestOrder(" abc123 ", "Test", "guest@example.com");
assertEquals("ABC123", result.getPlate());
}
@Test
void shouldNormalizeGuestEmailToLowercaseAndTrim() {
when(orderRepository.save(any(Order.class))).thenAnswer(inv -> inv.getArgument(0));
Order result = orderService.createGuestOrder("ABC123", "Test", " Guest@EXAMPLE.COM ");
assertEquals("guest@example.com", result.getGuestEmail());
}
@Test
void shouldHandleNullGuestEmail() {
when(orderRepository.save(any(Order.class))).thenAnswer(inv -> inv.getArgument(0));
Order result = orderService.createGuestOrder("ABC123", "Test", null);
assertNull(result.getGuestEmail());
}
// --- Guest order: getOrderByGuestToken ---
@Test
void shouldGetGuestOrderByToken() {
UUID token = UUID.randomUUID();
Order order = new Order();
order.setGuestToken(token);
order.setStatus(OrderStatus.PENDING_PAYMENT);
when(orderRepository.findByGuestToken(token)).thenReturn(Optional.of(order));
Order result = orderService.getOrderByGuestToken(token);
assertSame(order, result);
}
@Test
void shouldThrowWhenGuestTokenNotFound() {
UUID token = UUID.randomUUID();
when(orderRepository.findByGuestToken(token)).thenReturn(Optional.empty());
assertThrows(OrderNotFoundException.class,
() -> orderService.getOrderByGuestToken(token));
}
@Test
void shouldThrowWhenGuestTokenPointsToUserOwnedOrder() {
UUID token = UUID.randomUUID();
Order order = new Order();
order.setGuestToken(token);
order.setUserId(UUID.randomUUID()); // security: user-owned order must not be exposed
when(orderRepository.findByGuestToken(token)).thenReturn(Optional.of(order));
assertThrows(OrderNotFoundException.class,
() -> orderService.getOrderByGuestToken(token));
}
// --- Guest order: confirmGuestPayment ---
@Test
void shouldConfirmGuestPaymentForPendingOrder() {
UUID token = UUID.randomUUID();
Order order = new Order();
order.setGuestToken(token);
order.setStatus(OrderStatus.PENDING_PAYMENT);
when(orderRepository.findByGuestToken(token)).thenReturn(Optional.of(order));
when(orderRepository.save(any(Order.class))).thenAnswer(inv -> inv.getArgument(0));
Order result = orderService.confirmGuestPayment(token);
assertEquals(OrderStatus.PROCESSING, result.getStatus());
verify(orderNotificationService).notifyOrderProcessing(result);
}
@Test
void shouldThrowWhenConfirmingGuestPaymentForNonPendingOrder() {
UUID token = UUID.randomUUID();
Order order = new Order();
order.setGuestToken(token);
order.setStatus(OrderStatus.CANCELLED);
when(orderRepository.findByGuestToken(token)).thenReturn(Optional.of(order));
assertThrows(InvalidOrderStateException.class,
() -> orderService.confirmGuestPayment(token));
verify(orderRepository, never()).save(any(Order.class));
verify(orderNotificationService, never()).notifyOrderProcessing(any());
}
@Test
void shouldThrowWhenConfirmingGuestPaymentForUnknownToken() {
UUID token = UUID.randomUUID();
when(orderRepository.findByGuestToken(token)).thenReturn(Optional.empty());
assertThrows(OrderNotFoundException.class,
() -> orderService.confirmGuestPayment(token));
}
}

View file

@ -0,0 +1,39 @@
import { request } from './client'
/**
* Guest order placed and paid for without an account. {@link guestToken}
* is the only credential the client holds; it is returned only on create
* and from a by-token lookup. Pass it as the query string on the next
* page so a refresh keeps the session alive.
*/
export interface GuestOrder {
id: string
plate: string
letterText: string
status: string
trackingId: string | null
amountPaid: number | null
createdAt: string
guestToken: string
}
export function createGuestOrder(
plate: string,
letterText: string,
email: string,
): Promise<GuestOrder> {
return request<GuestOrder>('/guest-orders', {
method: 'POST',
body: JSON.stringify({ plate, letterText, email }),
})
}
export function fetchGuestOrder(token: string): Promise<GuestOrder> {
return request<GuestOrder>(`/guest-orders/${token}`)
}
export function payGuestOrder(token: string): Promise<GuestOrder> {
return request<GuestOrder>(`/guest-orders/${token}/pay`, {
method: 'POST',
})
}

View file

@ -0,0 +1,188 @@
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
<script setup lang="ts">
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
import { ref, computed } from 'vue'
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
import { useRouter } from 'vue-router'
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
import { createGuestOrder } from '@/api/guestOrders'
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
const router = useRouter()
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
const plate = ref('')
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
const letterText = ref('')
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
const email = ref('')
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
const submitting = ref(false)
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
const errorMessage = ref('')
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
const maxChars = 1000
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
const charCount = computed(() => letterText.value.length)
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
const PLATE_RE = /^[A-Za-z]{3}\d{2}[A-Za-z0-9]$/
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
const canSubmit = computed(
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
() =>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
PLATE_RE.test(plate.value.trim()) &&
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
letterText.value.trim().length > 0 &&
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
/\S+@\S+\.\S+/.test(email.value.trim()) &&
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
!submitting.value,
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
)
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
async function handleSubmit() {
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
if (!canSubmit.value) return
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
submitting.value = true
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
errorMessage.value = ''
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
try {
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
const order = await createGuestOrder(
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
plate.value.trim(),
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
letterText.value,
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
email.value.trim(),
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
)
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
// Token rides in the query string so the payment page survives refresh.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
await router.push({
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
name: 'guest-payment',
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
params: { orderId: order.id },
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
query: { token: order.guestToken, plate: order.plate },
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
})
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
} catch {
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
errorMessage.value = 'Kunde inte skapa beställningen. Försök igen senare.'
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
} finally {
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
submitting.value = false
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
}
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
}
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
</script>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
<template>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
<div class="guest-checkout">
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
<div class="guest-checkout__card">
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
<h1 class="guest-checkout__title">Skicka ett brev</h1>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
<p class="guest-checkout__subtitle">
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
49 kr betala med Swish. Inget konto behövs.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
</p>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
<form class="guest-checkout__form" @submit.prevent="handleSubmit">
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
<div class="field">
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
<label for="plate" class="field__label">Registreringsnummer</label>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
<input
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
id="plate"
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
v-model="plate"
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
type="text"
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
class="field__input"
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
placeholder="ABC123"
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
maxlength="6"
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
autocomplete="off"
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
spellcheck="false"
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
/>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
</div>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
<div class="field">
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
<label for="letter" class="field__label">Ditt meddelande</label>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
<textarea
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
id="letter"
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
v-model="letterText"
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
class="field__input guest-checkout__textarea"
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
:maxlength="maxChars"
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
rows="10"
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
placeholder="Skriv ditt meddelande här..."
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
></textarea>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
<p class="field__hint guest-checkout__counter">
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
{{ charCount }} / {{ maxChars }} tecken
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
</p>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
</div>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
<div class="field">
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
<label for="email" class="field__label"
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
>E-post (för kvitto och orderlänk)</label
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
<input
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
id="email"
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
v-model="email"
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
type="email"
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
class="field__input"
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
placeholder="namn@example.se"
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
autocomplete="email"
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
/>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
<p class="field__hint">
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Vi skickar en magisk länk du kan följa ditt brev senare.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
</p>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
</div>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
<div v-if="errorMessage" class="message message--error">
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
{{ errorMessage }}
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
</div>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
<button
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
type="submit"
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
class="btn btn--primary btn--lg guest-checkout__submit"
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
:disabled="!canSubmit"
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
{{ submitting ? 'Skickar...' : 'Fortsätt till betalning' }}
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
</button>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
<p class="guest-checkout__login-hint">
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Har du redan ett konto?
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
<RouterLink to="/logga-in">Logga in</RouterLink>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
</p>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
</form>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
</div>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
</div>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
</template>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
<style scoped>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
.guest-checkout {
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
max-width: 32rem;
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
margin: clamp(var(--space-xl), 6vw, var(--space-3xl)) auto;
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
padding: 0 var(--page-gutter);
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
}
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
.guest-checkout__card {
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
background: var(--color-surface);
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
border: 1px solid var(--color-border);
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
border-radius: var(--radius-xl);
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
padding: var(--space-xl);
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
box-shadow: var(--shadow-card);
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
}
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
.guest-checkout__title {
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
margin: 0 0 var(--space-xs) 0;
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
font-size: 1.5rem;
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
color: var(--color-ink);
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
}
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
.guest-checkout__subtitle {
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
margin: 0 0 var(--space-xl) 0;
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
color: var(--color-muted);
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
font-size: 0.9375rem;
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
}
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
.guest-checkout__form {
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
display: flex;
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
flex-direction: column;
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
gap: var(--space-md);
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
}
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
.guest-checkout__textarea {
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
resize: vertical;
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
min-height: 10rem;
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
font-family: inherit;
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
}
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
.guest-checkout__counter {
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
text-align: right;
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
}
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
.guest-checkout__submit {
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
width: 100%;
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
margin-top: var(--space-sm);
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
}
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
.guest-checkout__login-hint {
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
text-align: center;
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
margin: var(--space-sm) 0 0 0;
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
font-size: 0.875rem;
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
color: var(--color-muted);
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
}
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
.guest-checkout__login-hint a {
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
color: var(--color-primary);
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
text-decoration: underline;
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
}
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.
</style>
Review

🟠 Guest token in the URL query string. The token is the customer's sole credential but here it's pushed into ?token=…, so it lands in browser history and the nginx access log (and risks Referer leakage). The magic-link /gast-order/:token is inherently URL-based, but this payment page needn't be — prefer sessionStorage / Pinia store / route state (or a #fragment) to keep the credential out of the query string.

🟠 **Guest token in the URL query string.** The token is the customer's sole credential but here it's pushed into `?token=…`, so it lands in browser history and the nginx access log (and risks `Referer` leakage). The magic-link `/gast-order/:token` is inherently URL-based, but this payment page needn't be — prefer `sessionStorage` / Pinia store / route state (or a `#fragment`) to keep the credential out of the query string.
Review

🔵 Client email regex weaker than backend @Email. /\S+@\S+\.\S+/ lets a@b / x@.y through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend @Email message.

🔵 **Client email regex weaker than backend `@Email`.** `/\S+@\S+\.\S+/` lets `a@b` / `x@.y` through client-side, only to fail server-side with a confusing message. Mirror a stricter pattern or drop the client check and surface the backend `@Email` message.

View file

@ -0,0 +1,198 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { fetchGuestOrder, type GuestOrder } from '@/api/guestOrders'
const route = useRoute()
const token = route.params.token as string
const order = ref<GuestOrder | null>(null)
const loading = ref(true)
const error = ref('')
const statusLabel = computed(() => {
if (!order.value) return ''
switch (order.value.status) {
case 'pending_payment':
return 'Väntar på betalning'
case 'paid':
case 'processing':
return 'Behandlas'
case 'sent':
return 'Skickat'
case 'delivered':
return 'Levererat'
case 'failed':
return 'Misslyckades'
case 'cancelled':
return 'Avbrutet'
default:
return order.value.status
}
})
onMounted(async () => {
if (!token) {
error.value = 'Ogiltig orderlänk.'
loading.value = false
return
}
try {
order.value = await fetchGuestOrder(token)
} catch {
error.value = 'Kunde inte hitta beställningen. Kontrollera länken.'
} finally {
loading.value = false
}
})
</script>
<template>
<div class="guest-order">
<div class="guest-order__card">
<h1 class="guest-order__title">Din beställning</h1>
<div v-if="loading" class="guest-order__state">Laddar</div>
<div v-else-if="error" class="message message--error">{{ error }}</div>
<template v-else-if="order">
<div class="guest-order__row">
<span class="guest-order__label">Registreringsnummer</span>
<span class="guest-order__value">{{ order.plate }}</span>
</div>
<div class="guest-order__row">
<span class="guest-order__label">Beställnings-ID</span>
<span class="guest-order__value guest-order__value--mono">{{
order.id
}}</span>
</div>
<div class="guest-order__row">
<span class="guest-order__label">Skapad</span>
<span class="guest-order__value">
{{ new Date(order.createdAt).toLocaleString('sv-SE') }}
</span>
</div>
<hr class="guest-order__divider" />
<div class="guest-order__row guest-order__row--status">
<span class="guest-order__label">Status</span>
<span class="guest-order__status">{{ statusLabel }}</span>
</div>
<p v-if="order.status === 'pending_payment'" class="guest-order__hint">
<RouterLink
:to="{
name: 'guest-payment',
params: { orderId: order.id },
query: { token, plate: order.plate },
}"
>
till betalningssidan
</RouterLink>
</p>
<div class="guest-order__letter">
<p class="guest-order__letter-label">Ditt brev</p>
<p class="guest-order__letter-body">{{ order.letterText }}</p>
</div>
</template>
</div>
</div>
</template>
<style scoped>
.guest-order {
max-width: 32rem;
margin: clamp(var(--space-xl), 6vw, var(--space-3xl)) auto;
padding: 0 var(--page-gutter);
}
.guest-order__card {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-xl);
padding: var(--space-xl);
box-shadow: var(--shadow-card);
}
.guest-order__title {
margin: 0 0 var(--space-lg) 0;
font-size: 1.5rem;
color: var(--color-ink);
}
.guest-order__row {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: var(--space-md);
margin-bottom: var(--space-sm);
}
.guest-order__row--status {
margin-top: var(--space-sm);
}
.guest-order__label {
font-size: 0.8125rem;
color: var(--color-muted);
text-transform: uppercase;
letter-spacing: 0.04em;
}
.guest-order__value {
font-size: 0.9375rem;
color: var(--color-ink);
text-align: right;
}
.guest-order__value--mono {
font-family: ui-monospace, monospace;
font-size: 0.8125rem;
word-break: break-all;
}
.guest-order__divider {
margin: var(--space-md) 0;
border: none;
border-top: 1px solid var(--color-border);
}
.guest-order__status {
font-size: 1rem;
font-weight: 600;
color: var(--color-primary-dark);
}
.guest-order__hint {
margin: var(--space-lg) 0 0 0;
font-size: 0.875rem;
}
.guest-order__hint a {
color: var(--color-primary);
text-decoration: underline;
}
.guest-order__letter {
margin-top: var(--space-xl);
padding-top: var(--space-lg);
border-top: 1px solid var(--color-border);
}
.guest-order__letter-label {
margin: 0 0 var(--space-sm) 0;
font-size: 0.75rem;
font-weight: 600;
color: var(--color-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.guest-order__letter-body {
margin: 0;
font-family: var(--font-serif);
font-size: 0.9375rem;
line-height: 1.7;
color: var(--color-ink);
white-space: pre-wrap;
}
</style>

View file

@ -0,0 +1,386 @@
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<script setup lang="ts">
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
import { ref, computed, onMounted } from 'vue'
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
import { useRouter, useRoute } from 'vue-router'
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
import QRCode from 'qrcode'
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
import { fetchGuestOrder, payGuestOrder } from '@/api/guestOrders'
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
import { fetchSwishInfo, buildSwishPaymentUrl } from '@/api/payment'
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
const router = useRouter()
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
const route = useRoute()
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
const orderId = route.params.orderId as string
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
const token = (route.query.token as string) || ''
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
const plate = ref((route.query.plate as string) || '')
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
const swishNumber = ref('')
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
const swishAmount = ref(49)
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
const paying = ref(false)
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
const error = ref('')
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
const showConfirmation = ref(false)
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
const qrDataUrl = ref('')
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
const swishPaymentUrl = computed(() =>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
swishNumber.value
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
? buildSwishPaymentUrl(swishNumber.value, swishAmount.value, orderId)
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
: '',
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
)
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
const magicOrderUrl = computed(() =>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
token ? `${window.location.origin}/gast-order/${token}` : '',
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
)
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
onMounted(async () => {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
if (!token) {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
error.value = 'Saknar order-token. Gå tillbaka och försök igen.'
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
return
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
try {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
const info = await fetchSwishInfo()
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
swishNumber.value = info.number
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
swishAmount.value = info.amount
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
// Pre-load plate display so the payment page shows what they're paying for
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
// even if they opened it directly without the plate query string.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
const order = await fetchGuestOrder(token)
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
if (order.status !== 'pending_payment') {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
// Already paid bounce them to the status page.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
await router.push({ name: 'guest-order', params: { token } })
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
return
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
plate.value = plate.value || order.plate
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
if (swishPaymentUrl.value) {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
width: 224,
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
margin: 2,
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
color: { dark: '#111827', light: '#ffffff' },
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
})
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
} catch {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
error.value = 'Kunde inte ladda betalningsinformation. Försök igen senare.'
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
})
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
function startPayment() {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
showConfirmation.value = true
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
function cancelPayment() {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
showConfirmation.value = false
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
async function confirmPayment() {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
paying.value = true
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
error.value = ''
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
try {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
await payGuestOrder(token)
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
await router.push({ name: 'guest-order', params: { token } })
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
} catch {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
error.value = 'Kunde inte bekräfta betalningen. Försök igen.'
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
} finally {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
paying.value = false
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</script>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<template>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<div class="page">
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<div class="page__card">
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<h1 class="page__title">Betalning</h1>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<p class="page__plate">
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Registreringsnummer: <strong>{{ plate || '—' }}</strong>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</p>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<div class="payment__order-ref">
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<p class="payment__order-ref-label">Beställnings-ID</p>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<p class="payment__order-id">{{ orderId }}</p>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</div>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<div class="payment__summary">
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<div class="payment__row">
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<span class="payment__label">Att betala</span>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<span class="payment__amount">{{ swishAmount }} kr</span>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</div>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</div>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<div
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
v-if="error"
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
class="message message--error"
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
style="margin-bottom: var(--space-md)"
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
{{ error }}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</div>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<template v-if="!showConfirmation">
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<!-- QR code scan with the Swish app (desktop users) -->
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<div v-if="qrDataUrl" class="payment__qr">
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<img :src="qrDataUrl" alt="Swish QR-kod" class="payment__qr-img" />
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<p class="payment__qr-hint">
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Skanna QR-koden med Swish-appen för att betala
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</p>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</div>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<!-- Direct link opens the Swish app (mobile users) -->
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<a
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
v-if="swishPaymentUrl"
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
:href="swishPaymentUrl"
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
class="btn btn--primary btn--lg payment__swish-link"
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Betala med Swish
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</a>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<!-- Manual fallback -->
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<div class="payment__swish">
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<p class="payment__swish-label">Swisha till</p>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<p class="payment__swish-number">{{ swishNumber }}</p>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<p class="payment__swish-instruction">
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Belopp och beställnings-ID fylls i automatiskt via QR-kod eller
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
länk.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</p>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<p class="payment__swish-instruction">
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Betala manuellt om du inte har Swish-appen tillgänglig.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</p>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</div>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<button class="btn btn--ghost payment__submit" @click="startPayment">
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Jag har betalat
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</button>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</template>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<template v-else>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<div class="payment__confirm">
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<p class="payment__confirm-text">
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Jag bekräftar att jag har Swishat {{ swishAmount }} kr till
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
{{ swishNumber }} med meddelande: {{ orderId }}.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</p>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<div class="payment__confirm-actions">
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<button
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
class="btn btn--ghost payment__confirm-cancel"
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
:disabled="paying"
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
@click="cancelPayment"
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Avbryt
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</button>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<button
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
class="btn btn--primary"
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
:disabled="paying"
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
@click="confirmPayment"
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
{{ paying ? 'Bearbetar...' : 'Ja, jag har betalat' }}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</button>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</div>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</div>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</template>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<!-- Magic order link shown after page mount so the user can copy it now -->
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<div v-if="magicOrderUrl" class="guest-payment__magic-link">
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<p class="payment__swish-label">Din orderlänk</p>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<p class="guest-payment__magic-hint">
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Spara denna länk för att följa ditt brev senare. (E-postbekräftelse
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
kommer i en senare fas.)
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</p>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<code class="guest-payment__magic-url">{{ magicOrderUrl }}</code>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</div>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</div>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</div>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</template>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
<style scoped>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.page {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
max-width: 28rem;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
margin: clamp(var(--space-xl), 6vw, var(--space-3xl)) auto 0;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
padding: 0 var(--page-gutter);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.page__card {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
background: var(--color-surface);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
border: 1px solid var(--color-border);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
border-radius: var(--radius-xl);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
padding: var(--space-xl);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
box-shadow: var(--shadow-card);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.page__title {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
margin: 0 0 var(--space-sm) 0;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
font-size: 1.5rem;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
color: var(--color-ink);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.page__plate {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
margin: 0 0 var(--space-md) 0;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
font-size: 0.875rem;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
color: var(--color-muted);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.payment__order-ref {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
margin: 0 0 var(--space-xl) 0;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
padding: var(--space-md);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
background: var(--color-border-light);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
border: 1px solid var(--color-border);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
border-radius: var(--radius-md);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.payment__order-ref-label {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
margin: 0 0 var(--space-xs) 0;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
font-size: 0.75rem;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
font-weight: 600;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
color: var(--color-muted);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
text-transform: uppercase;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
letter-spacing: 0.05em;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.payment__order-id {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
margin: 0;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
font-family: ui-monospace, monospace;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
font-size: 0.8125rem;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
color: var(--color-ink);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
word-break: break-all;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
line-height: 1.5;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.payment__summary {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
margin-bottom: var(--space-lg);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
padding-bottom: var(--space-lg);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
border-bottom: 1px solid var(--color-border);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.payment__row {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
display: flex;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
justify-content: space-between;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
align-items: center;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.payment__label {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
font-size: 0.875rem;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
color: var(--color-muted);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.payment__amount {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
font-size: 1.5rem;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
font-weight: 700;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
color: var(--color-ink);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.payment__qr {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
text-align: center;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
margin-bottom: var(--space-lg);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.payment__qr-img {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
width: 224px;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
height: 224px;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
border-radius: var(--radius-md);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
margin: 0 auto var(--space-sm);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.payment__qr-hint {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
font-size: 0.8125rem;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
color: var(--color-muted);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.payment__swish-link {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
display: block;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
width: 100%;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
text-align: center;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
text-decoration: none;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
margin-bottom: var(--space-lg);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.payment__swish {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
background: var(--color-border-light);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
border: 1px solid var(--color-border);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
border-radius: var(--radius-md);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
padding: var(--space-lg);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
margin-bottom: var(--space-lg);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
text-align: center;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.payment__swish-label {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
margin: 0 0 var(--space-xs) 0;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
font-size: 0.75rem;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
font-weight: 600;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
color: var(--color-muted);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
text-transform: uppercase;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
letter-spacing: 0.05em;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.payment__swish-number {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
margin: 0 0 var(--space-md) 0;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
font-size: 1.75rem;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
font-weight: 700;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
color: var(--color-ink);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
letter-spacing: 0.05em;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.payment__swish-instruction {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
margin: 0;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
font-size: 0.8125rem;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
color: var(--color-muted);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
line-height: 1.5;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.payment__swish-instruction + .payment__swish-instruction {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
margin-top: var(--space-xs);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.payment__submit {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
width: 100%;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.payment__confirm {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
padding: var(--space-md) 0;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.payment__confirm-text {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
margin: 0 0 var(--space-lg) 0;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
font-size: 0.9375rem;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
color: var(--color-ink);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
line-height: 1.6;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.payment__confirm-actions {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
display: flex;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
gap: var(--space-md);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.payment__confirm-cancel {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
flex: 1;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.payment__confirm-actions .btn--primary {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
flex: 2;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.guest-payment__magic-link {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
margin-top: var(--space-xl);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
padding-top: var(--space-lg);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
border-top: 1px solid var(--color-border);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.guest-payment__magic-hint {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
margin: 0 0 var(--space-xs) 0;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
font-size: 0.75rem;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
color: var(--color-muted);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
line-height: 1.5;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.guest-payment__magic-url {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
display: block;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
font-family: ui-monospace, monospace;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
font-size: 0.75rem;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
color: var(--color-ink);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
background: var(--color-border-light);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
padding: var(--space-sm) var(--space-md);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
border-radius: var(--radius-sm);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
word-break: break-all;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
user-select: all;
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
@media (max-width: 639px) {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
.page {
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
padding: 0 var(--page-gutter);
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
}
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.
</style>
Review

🔴 Swish QR quiet zone too small — won't scan on desktop.

margin: 2 is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages.

Fix: margin: 4. Also change color.dark from #111827 (dark gray) to #000000 (Swish spec: "black and white"), and bump width to ≥256 for an ~80–90 char pre-fill URL. Same config as PaymentRedirect.vue — fix both.

🔴 **Swish QR quiet zone too small — won't scan on desktop.** `margin: 2` is half the ISO/IEC 18004 minimum of 4 modules. The Swish app's scanner is stricter than a phone camera and fails to lock the finder pattern when scanning a QR shown on a screen — this is the documented root cause of Swish-QR outages. Fix: `margin: 4`. Also change `color.dark` from `#111827` (dark gray) to `#000000` (Swish spec: "black and white"), and bump `width` to ≥256 for an ~80–90 char pre-fill URL. Same config as `PaymentRedirect.vue` — fix both.
Review

🔵 Isolate QR generation in its own try/catch.

This toDataURL call is inside the onMounted try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

🔵 **Isolate QR generation in its own try/catch.** This `toDataURL` call is inside the `onMounted` try (line 38). If it throws, the catch at line 60 shows "Kunde inte ladda betalningsinformation" — hiding the Swish link + manual fallback too. Wrap only the QR call so a QR failure still renders the payment link and manual number.

View file

@ -20,6 +20,9 @@ import OrdersPage from '@/pages/OrdersPage.vue'
import EditOrderPage from '@/pages/EditOrderPage.vue'
import AdminPage from '@/pages/AdminPage.vue'
import PaymentRedirect from '@/pages/PaymentRedirect.vue'
import GuestCheckoutPage from '@/pages/GuestCheckoutPage.vue'
import GuestPaymentRedirect from '@/pages/GuestPaymentRedirect.vue'
import GuestOrderPage from '@/pages/GuestOrderPage.vue'
import { useAuthStore } from '@/stores/authStore'
import { getActivePinia } from 'pinia'
@ -88,6 +91,24 @@ const router = createRouter({
component: PaymentRedirect,
meta: { requiresAuth: true },
},
{
// Guest checkout — no account required to place and pay for an order.
path: '/gast-bestallning',
name: 'guest-checkout',
component: GuestCheckoutPage,
},
{
// Guest payment page — token carried in query so refresh keeps the session.
path: '/gast-betalning/:orderId',
name: 'guest-payment',
component: GuestPaymentRedirect,
},
{
// Magic-link landing — order status by opaque token.
path: '/gast-order/:token',
name: 'guest-order',
component: GuestOrderPage,
},
{
path: '/registrera',
name: 'register',

View file

@ -12,8 +12,12 @@ export default defineConfig({
},
server: {
port: 3000,
host: true,
proxy: {
'/api': 'http://backend:8080',
// Allow running Vite locally outside Docker (set
// VITE_API_PROXY_TARGET=http://localhost:8080 npm run dev) by pointing
// the proxy at the host port instead of the compose service name.
'/api': process.env.VITE_API_PROXY_TARGET || 'http://backend:8080',
},
},
preview: {