fix(payment): make Swish QR code scannable by the Swish app #16

Open
hermes wants to merge 2 commits from fix/swish-qr-scannability into master
4 changed files with 86 additions and 7 deletions

View file

@ -112,6 +112,16 @@ describe('PaymentRedirect', () => {
expect(wrapper.find('.payment__qr-img').exists()).toBe(true)
})
expect(mockToDataURL).toHaveBeenCalledTimes(1)
// Regression guard: the QR must use a spec-compliant 4-module quiet zone
// and pure black-on-white so the Swish app can scan it off a screen.
expect(mockToDataURL).toHaveBeenCalledWith(
expect.stringContaining('app.swish.nu'),
expect.objectContaining({
margin: 4,
errorCorrectionLevel: 'M',
color: { dark: '#000000', light: '#ffffff' },
}),
)
})
it('renders a Swish payment link', async () => {

View file

@ -0,0 +1,52 @@
import { describe, it, expect } from 'vitest'
import { buildSwishPaymentUrl } from '@/api/payment'
describe('buildSwishPaymentUrl', () => {
it('normalises Swedish national format to international', () => {
expect(buildSwishPaymentUrl('0701234567', 49, 'test')).toContain(
'sw=46701234567',
)
})
it('strips a leading + from international format', () => {
const url = buildSwishPaymentUrl('+46701234567', 49, 'test')
expect(url).toContain('sw=46701234567')
expect(url).not.toContain('sw=%2B')
expect(url).not.toContain('sw=+')
})
it('leaves already-international numbers unchanged', () => {
expect(buildSwishPaymentUrl('46701234567', 49, 'test')).toContain(
'sw=46701234567',
)
})
it('leaves Swish Business numbers (123…) unchanged', () => {
expect(buildSwishPaymentUrl('1234567890', 49, 'test')).toContain(
'sw=1234567890',
)
})
it('strips whitespace from the number', () => {
expect(buildSwishPaymentUrl('070 123 45 67', 49, 'test')).toContain(
'sw=46701234567',
)
})
it('includes the amount with two decimal places in amt', () => {
expect(buildSwishPaymentUrl('0701234567', 49, 'test')).toContain(
'amt=49.00',
)
})
it('URL-encodes the message in the msg parameter', () => {
const url = buildSwishPaymentUrl('0701234567', 49, 'ABC 123')
expect(url).toContain('msg=ABC+123')
})
it('uses the correct Swish C2B base URL', () => {
expect(buildSwishPaymentUrl('0701234567', 49, 'test')).toContain(
'https://app.swish.nu/1/p/sw/?',
)
})
})

View file

@ -48,9 +48,12 @@ export function buildSwishPaymentUrl(
Review

💡 Suggestion: The comment says "a leading +" but /[\s+]/g strips all + characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider number.replace(/^\+/, '').replace(/\s/g, '') or adjusting the comment to say "any + characters".

💡 **Suggestion:** The comment says "a leading `+`" but `/[\s+]/g` strips *all* `+` characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider `number.replace(/^\+/, '').replace(/\s/g, '')` or adjusting the comment to say "any `+` characters".
Review

💡 Suggestion (low): /[\s+]/g strips every + from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a leading +. For valid phone numbers this is harmless (a + only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any +", or for leading-only use replace(/\s/g, '').replace(/^\+/, ''). Either is fine.

💡 **Suggestion (low):** `/[\s+]/g` strips *every* `+` from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a *leading* `+`. For valid phone numbers this is harmless (a `+` only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any `+`", or for leading-only use `replace(/\s/g, '').replace(/^\+/, '')`. Either is fine.
Review

💡 Suggestion: The comment says "a leading +" but /[\s+]/g strips all + characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider number.replace(/^\+/, '').replace(/\s/g, '') or adjusting the comment to say "any + characters".

💡 **Suggestion:** The comment says "a leading `+`" but `/[\s+]/g` strips *all* `+` characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider `number.replace(/^\+/, '').replace(/\s/g, '')` or adjusting the comment to say "any `+` characters".
Review

💡 Suggestion (low): /[\s+]/g strips every + from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a leading +. For valid phone numbers this is harmless (a + only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any +", or for leading-only use replace(/\s/g, '').replace(/^\+/, ''). Either is fine.

💡 **Suggestion (low):** `/[\s+]/g` strips *every* `+` from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a *leading* `+`. For valid phone numbers this is harmless (a `+` only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any `+`", or for leading-only use `replace(/\s/g, '').replace(/^\+/, '')`. Either is fine.
* - 123 (Swish Business number) unchanged
* - 46 (already international) unchanged
* - 0 (Swedish national format) 46 + rest without leading 0
* - +46 (international with plus) 46 (the plus is stripped first)
Review

💡 Suggestion: The comment says "a leading +" but /[\s+]/g strips all + characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider number.replace(/^\+/, '').replace(/\s/g, '') or adjusting the comment to say "any + characters".

💡 **Suggestion:** The comment says "a leading `+`" but `/[\s+]/g` strips *all* `+` characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider `number.replace(/^\+/, '').replace(/\s/g, '')` or adjusting the comment to say "any `+` characters".
Review

💡 Suggestion (low): /[\s+]/g strips every + from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a leading +. For valid phone numbers this is harmless (a + only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any +", or for leading-only use replace(/\s/g, '').replace(/^\+/, ''). Either is fine.

💡 **Suggestion (low):** `/[\s+]/g` strips *every* `+` from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a *leading* `+`. For valid phone numbers this is harmless (a `+` only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any `+`", or for leading-only use `replace(/\s/g, '').replace(/^\+/, '')`. Either is fine.
*/
function normalizeSwishNumber(number: string): string {
const trimmed = number.replace(/\s/g, '')
Review

💡 Suggestion: The comment says "a leading +" but /[\s+]/g strips all + characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider number.replace(/^\+/, '').replace(/\s/g, '') or adjusting the comment to say "any + characters".

💡 **Suggestion:** The comment says "a leading `+`" but `/[\s+]/g` strips *all* `+` characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider `number.replace(/^\+/, '').replace(/\s/g, '')` or adjusting the comment to say "any `+` characters".
Review

💡 Suggestion (low): /[\s+]/g strips every + from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a leading +. For valid phone numbers this is harmless (a + only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any +", or for leading-only use replace(/\s/g, '').replace(/^\+/, ''). Either is fine.

💡 **Suggestion (low):** `/[\s+]/g` strips *every* `+` from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a *leading* `+`. For valid phone numbers this is harmless (a `+` only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any `+`", or for leading-only use `replace(/\s/g, '').replace(/^\+/, '')`. Either is fine.
// Strip whitespace and a leading "+": a number stored as "+46 70 …" would
Review

💡 Suggestion: The comment says "a leading +" but /[\s+]/g strips all + characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider number.replace(/^\+/, '').replace(/\s/g, '') or adjusting the comment to say "any + characters".

💡 **Suggestion:** The comment says "a leading `+`" but `/[\s+]/g` strips *all* `+` characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider `number.replace(/^\+/, '').replace(/\s/g, '')` or adjusting the comment to say "any `+` characters".
Review

💡 Suggestion (low): /[\s+]/g strips every + from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a leading +. For valid phone numbers this is harmless (a + only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any +", or for leading-only use replace(/\s/g, '').replace(/^\+/, ''). Either is fine.

💡 **Suggestion (low):** `/[\s+]/g` strips *every* `+` from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a *leading* `+`. For valid phone numbers this is harmless (a `+` only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any `+`", or for leading-only use `replace(/\s/g, '').replace(/^\+/, '')`. Either is fine.
// otherwise miss every prefix check and leak a "+" into the `sw` param.
Review

💡 Suggestion: The comment says "a leading +" but /[\s+]/g strips all + characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider number.replace(/^\+/, '').replace(/\s/g, '') or adjusting the comment to say "any + characters".

💡 **Suggestion:** The comment says "a leading `+`" but `/[\s+]/g` strips *all* `+` characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider `number.replace(/^\+/, '').replace(/\s/g, '')` or adjusting the comment to say "any `+` characters".
Review

💡 Suggestion (low): /[\s+]/g strips every + from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a leading +. For valid phone numbers this is harmless (a + only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any +", or for leading-only use replace(/\s/g, '').replace(/^\+/, ''). Either is fine.

💡 **Suggestion (low):** `/[\s+]/g` strips *every* `+` from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a *leading* `+`. For valid phone numbers this is harmless (a `+` only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any `+`", or for leading-only use `replace(/\s/g, '').replace(/^\+/, '')`. Either is fine.
const trimmed = number.replace(/[\s+]/g, '')
Review

💡 Suggestion: The comment says "a leading +" but /[\s+]/g strips all + characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider number.replace(/^\+/, '').replace(/\s/g, '') or adjusting the comment to say "any + characters".

💡 **Suggestion:** The comment says "a leading `+`" but `/[\s+]/g` strips *all* `+` characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider `number.replace(/^\+/, '').replace(/\s/g, '')` or adjusting the comment to say "any `+` characters".
Review

💡 Suggestion (low): /[\s+]/g strips every + from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a leading +. For valid phone numbers this is harmless (a + only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any +", or for leading-only use replace(/\s/g, '').replace(/^\+/, ''). Either is fine.

💡 **Suggestion (low):** `/[\s+]/g` strips *every* `+` from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a *leading* `+`. For valid phone numbers this is harmless (a `+` only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any `+`", or for leading-only use `replace(/\s/g, '').replace(/^\+/, '')`. Either is fine.
if (trimmed.startsWith('123')) return trimmed
if (trimmed.startsWith('46')) return trimmed
if (trimmed.startsWith('0')) return '46' + trimmed.slice(1)

Review

💡 Suggestion: The comment says "a leading +" but /[\s+]/g strips all + characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider number.replace(/^\+/, '').replace(/\s/g, '') or adjusting the comment to say "any + characters".

💡 **Suggestion:** The comment says "a leading `+`" but `/[\s+]/g` strips *all* `+` characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider `number.replace(/^\+/, '').replace(/\s/g, '')` or adjusting the comment to say "any `+` characters".
Review

💡 Suggestion (low): /[\s+]/g strips every + from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a leading +. For valid phone numbers this is harmless (a + only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any +", or for leading-only use replace(/\s/g, '').replace(/^\+/, ''). Either is fine.

💡 **Suggestion (low):** `/[\s+]/g` strips *every* `+` from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a *leading* `+`. For valid phone numbers this is harmless (a `+` only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any `+`", or for leading-only use `replace(/\s/g, '').replace(/^\+/, '')`. Either is fine.
Review

💡 Suggestion: The comment says "a leading +" but /[\s+]/g strips all + characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider number.replace(/^\+/, '').replace(/\s/g, '') or adjusting the comment to say "any + characters".

💡 **Suggestion:** The comment says "a leading `+`" but `/[\s+]/g` strips *all* `+` characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider `number.replace(/^\+/, '').replace(/\s/g, '')` or adjusting the comment to say "any `+` characters".
Review

💡 Suggestion (low): /[\s+]/g strips every + from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a leading +. For valid phone numbers this is harmless (a + only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any +", or for leading-only use replace(/\s/g, '').replace(/^\+/, ''). Either is fine.

💡 **Suggestion (low):** `/[\s+]/g` strips *every* `+` from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a *leading* `+`. For valid phone numbers this is harmless (a `+` only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any `+`", or for leading-only use `replace(/\s/g, '').replace(/^\+/, '')`. Either is fine.

View file

@ -27,16 +27,30 @@ onMounted(async () => {
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
const info = await fetchSwishInfo()
swishNumber.value = info.number
swishAmount.value = info.amount
} catch {
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
error.value = 'Kunde inte ladda betalningsinformation. Försök igen senare.'
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
return
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
}
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
// QR generation is best-effort and isolated from fetchSwishInfo: if the QR
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
// library throws, the Swish payment link and manual fallback still render
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
// instead of surfacing a misleading "could not load payment info" error.
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
try {
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
if (swishPaymentUrl.value) {
qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
width: 224,
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
margin: 2,
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
color: { dark: '#111827', light: '#ffffff' },
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
// Swish requires a reliably scannable black-on-white QR. The previous
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
// settings (margin 2, #111827, 224px) produced a 2-module quiet zone
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
// half the QR spec minimum which the Swish app's scanner fails to
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
// read when scanning off a screen. Use the spec-compliant 4-module
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
// quiet zone, pure black, and larger modules.
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
width: 288,
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
margin: 4,
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
errorCorrectionLevel: 'M',
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
color: { dark: '#000000', light: '#ffffff' },
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
})
}
} catch {
error.value = 'Kunde inte ladda betalningsinformation. Försök igen senare.'
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
// ignored: payment link + manual fallback remain usable
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
}
})
@ -239,8 +253,8 @@ async function confirmPayment() {
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
}
.payment__qr-img {
width: 224px;
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
height: 224px;
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
width: 288px;
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
height: 288px;
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
border-radius: var(--radius-md);
margin: 0 auto var(--space-sm);
}

Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
Review

💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: mockToDataURL.mockRejectedValue(...) → assert the Swish payment link and manual fallback still render.

💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
Review

💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a console.warn(…) (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.

💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.