feat(payment): Swish QR code and pre-filled payment link #15

Merged
jocke merged 2 commits from feature/swish-qr-payment into master 2026-06-19 15:38:18 +00:00
7 changed files with 507 additions and 61 deletions

View file

@ -24,6 +24,12 @@ STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_PRICE_ID=price_... STRIPE_PRICE_ID=price_...
# ---------- Swish (Phase 0) ---------- # ---------- Swish (Phase 0) ----------
# The Swish number customers pay to. Two formats accepted:
# - Swedish phone number: 0701234567 (normalised to 46… for the payment URL)
# - Swish Business number: 1234567890 (starts with 123, used as-is)
# A Swish Business number (123…) is recommended — get one from your bank
# via a "Swish Företag" agreement. No Swish Commerce API certificate needed;
# the frontend generates a pre-filled QR code + payment link automatically.
SWISH_NUMBER=0701234567 SWISH_NUMBER=0701234567
# ---------- App URL (password reset links in email) ---------- # ---------- App URL (password reset links in email) ----------

View file

@ -49,6 +49,31 @@ test.describe('Payment redirect', () => {
await page.waitForURL(/\/betalning\//) await page.waitForURL(/\/betalning\//)
await expect(page.getByText('Swisha till')).toBeVisible() await expect(page.getByText('Swisha till')).toBeVisible()
await expect(page.getByRole('button', { name: 'Jag har betalat' })).toBeVisible() await expect(
page.getByRole('button', { name: 'Jag har betalat' }),
).toBeVisible()
})
test('shows QR code for desktop scanning', async ({ page }) => {
await page.goto('/compose?plate=QRA222')
await page.getByLabel('Ditt meddelande').fill('Fin bil!')
await page.getByRole('button', { name: 'Fortsätt till betalning' }).click()
await page.waitForURL(/\/betalning\//)
await expect(page.getByRole('img', { name: 'Swish QR-kod' })).toBeVisible()
await expect(page.getByText('Skanna QR-koden')).toBeVisible()
})
test('shows Swish payment link with pre-filled data', async ({ page }) => {
await page.goto('/compose?plate=MNO345')
await page.getByLabel('Ditt meddelande').fill('Hej där!')
await page.getByRole('button', { name: 'Fortsätt till betalning' }).click()
await page.waitForURL(/\/betalning\//)
const swishLink = page.getByRole('link', { name: 'Betala med Swish' })
await expect(swishLink).toBeVisible()
const href = await swishLink.getAttribute('href')
expect(href).toContain('app.swish.nu')
expect(href).toContain('amt=49.00')
}) })
}) })

View file

@ -9,6 +9,7 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"pinia": "^3.0.4", "pinia": "^3.0.4",
"qrcode": "^1.5.4",
"vue": "^3.5.32", "vue": "^3.5.32",
"vue-router": "^5.0.6" "vue-router": "^5.0.6"
}, },
@ -16,6 +17,7 @@
"@playwright/test": "^1.60.0", "@playwright/test": "^1.60.0",
"@rushstack/eslint-patch": "^1.16.1", "@rushstack/eslint-patch": "^1.16.1",
"@types/node": "^24.12.2", "@types/node": "^24.12.2",
"@types/qrcode": "^1.5.5",
"@vitejs/plugin-vue": "^6.0.6", "@vitejs/plugin-vue": "^6.0.6",
"@vitest/coverage-v8": "^4.1.6", "@vitest/coverage-v8": "^4.1.6",
"@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-prettier": "^10.2.0",
@ -791,9 +793,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -811,9 +810,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -831,9 +827,6 @@
"ppc64" "ppc64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -851,9 +844,6 @@
"s390x" "s390x"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -871,9 +861,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -891,9 +878,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -1054,6 +1038,16 @@
"undici-types": "~7.16.0" "undici-types": "~7.16.0"
} }
}, },
"node_modules/@types/qrcode": {
"version": "1.5.6",
"resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz",
"integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.59.1", "version": "8.59.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.1.tgz",
@ -1962,6 +1956,15 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/chai": { "node_modules/chai": {
"version": "6.2.2", "version": "6.2.2",
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
@ -1987,11 +1990,91 @@
"url": "https://paulmillr.com/funding/" "url": "https://paulmillr.com/funding/"
} }
}, },
"node_modules/cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^6.2.0"
}
},
"node_modules/cliui/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/cliui/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/cliui/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/cliui/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/cliui/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/cliui/node_modules/wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/color-convert": { "node_modules/color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"color-name": "~1.1.4" "color-name": "~1.1.4"
@ -2004,7 +2087,6 @@
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/commander": { "node_modules/commander": {
@ -2136,6 +2218,15 @@
} }
} }
}, },
"node_modules/decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/decimal.js": { "node_modules/decimal.js": {
"version": "10.6.0", "version": "10.6.0",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
@ -2160,6 +2251,12 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/dijkstrajs": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
"license": "MIT"
},
"node_modules/eastasianwidth": { "node_modules/eastasianwidth": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@ -2718,6 +2815,15 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
} }
}, },
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/glob": { "node_modules/glob": {
"version": "10.5.0", "version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
@ -2863,7 +2969,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -3285,9 +3390,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MPL-2.0", "license": "MPL-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -3309,9 +3411,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MPL-2.0", "license": "MPL-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -3333,9 +3432,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MPL-2.0", "license": "MPL-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -3357,9 +3453,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MPL-2.0", "license": "MPL-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -3743,6 +3836,15 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/package-json-from-dist": { "node_modules/package-json-from-dist": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
@ -3787,7 +3889,6 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -3936,6 +4037,15 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
} }
}, },
"node_modules/pngjs": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
"license": "MIT",
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.13", "version": "8.5.13",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz",
@ -4034,6 +4144,23 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/qrcode": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
"license": "MIT",
"dependencies": {
"dijkstrajs": "^1.0.1",
"pngjs": "^5.0.0",
"yargs": "^15.3.1"
},
"bin": {
"qrcode": "bin/qrcode"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/quansync": { "node_modules/quansync": {
"version": "0.2.11", "version": "0.2.11",
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
@ -4084,6 +4211,15 @@
"url": "https://paulmillr.com/funding/" "url": "https://paulmillr.com/funding/"
} }
}, },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/require-from-string": { "node_modules/require-from-string": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
@ -4094,6 +4230,12 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"license": "ISC"
},
"node_modules/reusify": { "node_modules/reusify": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
@ -4208,6 +4350,12 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"license": "ISC"
},
"node_modules/shebang-command": { "node_modules/shebang-command": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@ -5094,6 +5242,12 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/which-module": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
"license": "ISC"
},
"node_modules/why-is-node-running": { "node_modules/why-is-node-running": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
@ -5236,6 +5390,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/y18n": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"license": "ISC"
},
"node_modules/yaml": { "node_modules/yaml": {
"version": "2.8.3", "version": "2.8.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
@ -5251,6 +5411,134 @@
"url": "https://github.com/sponsors/eemeli" "url": "https://github.com/sponsors/eemeli"
} }
}, },
"node_modules/yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"license": "MIT",
"dependencies": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
"find-up": "^4.1.0",
"get-caller-file": "^2.0.1",
"require-directory": "^2.1.1",
"require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0",
"string-width": "^4.2.0",
"which-module": "^2.0.0",
"y18n": "^4.0.0",
"yargs-parser": "^18.1.2"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yargs-parser": {
"version": "18.1.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"license": "ISC",
"dependencies": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/yargs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/yargs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/yargs/node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"license": "MIT",
"dependencies": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yargs/node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"license": "MIT",
"dependencies": {
"p-locate": "^4.1.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yargs/node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"license": "MIT",
"dependencies": {
"p-try": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/yargs/node_modules/p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"license": "MIT",
"dependencies": {
"p-limit": "^2.2.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yargs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yargs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yocto-queue": { "node_modules/yocto-queue": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View file

@ -17,6 +17,7 @@
}, },
"dependencies": { "dependencies": {
"pinia": "^3.0.4", "pinia": "^3.0.4",
"qrcode": "^1.5.4",
"vue": "^3.5.32", "vue": "^3.5.32",
"vue-router": "^5.0.6" "vue-router": "^5.0.6"
}, },
@ -24,6 +25,7 @@
"@playwright/test": "^1.60.0", "@playwright/test": "^1.60.0",
"@rushstack/eslint-patch": "^1.16.1", "@rushstack/eslint-patch": "^1.16.1",
"@types/node": "^24.12.2", "@types/node": "^24.12.2",
"@types/qrcode": "^1.5.5",
"@vitejs/plugin-vue": "^6.0.6", "@vitejs/plugin-vue": "^6.0.6",
"@vitest/coverage-v8": "^4.1.6", "@vitest/coverage-v8": "^4.1.6",
"@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-prettier": "^10.2.0",

View file

@ -5,14 +5,26 @@ import { createRouter, createMemoryHistory } from 'vue-router'
import PaymentRedirect from '@/pages/PaymentRedirect.vue' import PaymentRedirect from '@/pages/PaymentRedirect.vue'
import OrdersPage from '@/pages/OrdersPage.vue' import OrdersPage from '@/pages/OrdersPage.vue'
vi.mock('qrcode', () => ({
default: {
toDataURL: vi.fn().mockResolvedValue('data:image/png;base64,mock-qr'),
},
}))
vi.mock('@/api/payment', () => ({ vi.mock('@/api/payment', () => ({
payOrder: vi.fn(), payOrder: vi.fn(),
fetchSwishInfo: vi.fn(), fetchSwishInfo: vi.fn(),
buildSwishPaymentUrl: vi.fn(
(number: string, amount: number, message: string) =>
`https://app.swish.nu/1/p/sw/?sw=${number}&amt=${amount.toFixed(2)}&msg=${message}`,
),
})) }))
import { payOrder, fetchSwishInfo } from '@/api/payment' import { payOrder, fetchSwishInfo } from '@/api/payment'
import QRCode from 'qrcode'
const mockPayOrder = vi.mocked(payOrder) const mockPayOrder = vi.mocked(payOrder)
const mockFetchSwishInfo = vi.mocked(fetchSwishInfo) const mockFetchSwishInfo = vi.mocked(fetchSwishInfo)
const mockToDataURL = vi.mocked(QRCode.toDataURL)
function createTestRouter() { function createTestRouter() {
return createRouter({ return createRouter({
@ -59,6 +71,7 @@ describe('PaymentRedirect', () => {
number: '0701234567', number: '0701234567',
amount: 49, amount: 49,
}) })
mockToDataURL.mockResolvedValue('data:image/png;base64,mock-qr')
}) })
it('renders heading and amount', async () => { it('renders heading and amount', async () => {
@ -81,7 +94,7 @@ describe('PaymentRedirect', () => {
expect(wrapper.text()).toContain('Beställnings-ID') expect(wrapper.text()).toContain('Beställnings-ID')
expect(wrapper.text()).toContain(orderId) expect(wrapper.text()).toContain(orderId)
expect(wrapper.text()).toContain( expect(wrapper.text()).toContain(
'Ange beställnings-ID ovan som meddelande i Swish-appen', 'fylls i automatiskt via QR-kod eller länk',
) )
}) })
}) })
@ -93,13 +106,30 @@ describe('PaymentRedirect', () => {
}) })
}) })
it('renders QR code after fetching swish info', async () => {
const { wrapper } = await mountPage()
await vi.waitFor(() => {
expect(wrapper.find('.payment__qr-img').exists()).toBe(true)
})
expect(mockToDataURL).toHaveBeenCalledTimes(1)
})
it('renders a Swish payment link', async () => {
const { wrapper } = await mountPage('test-order', 'ABC123')
await vi.waitFor(() => {
const link = wrapper.find('.payment__swish-link')
expect(link.exists()).toBe(true)
expect(link.attributes('href')).toContain('app.swish.nu')
})
})
it('shows confirmation dialog after clicking pay button', async () => { it('shows confirmation dialog after clicking pay button', async () => {
const { wrapper } = await mountPage() const { wrapper } = await mountPage()
await vi.waitFor(() => { await vi.waitFor(() => {
expect(wrapper.find('.btn--primary').exists()).toBe(true) expect(wrapper.find('.payment__submit').exists()).toBe(true)
}) })
await wrapper.find('.btn--primary').trigger('click') await wrapper.find('.payment__submit').trigger('click')
await vi.waitFor(() => { await vi.waitFor(() => {
expect(wrapper.text()).toContain('Jag bekräftar att jag har Swishat') expect(wrapper.text()).toContain('Jag bekräftar att jag har Swishat')
expect(wrapper.text()).toContain('0701234567') expect(wrapper.text()).toContain('0701234567')
@ -110,15 +140,15 @@ describe('PaymentRedirect', () => {
it('can cancel confirmation dialog', async () => { it('can cancel confirmation dialog', async () => {
const { wrapper } = await mountPage() const { wrapper } = await mountPage()
await vi.waitFor(() => { await vi.waitFor(() => {
expect(wrapper.find('.btn--primary').exists()).toBe(true) expect(wrapper.find('.payment__submit').exists()).toBe(true)
}) })
await wrapper.find('.btn--primary').trigger('click') await wrapper.find('.payment__submit').trigger('click')
await vi.waitFor(() => { await vi.waitFor(() => {
expect(wrapper.text()).toContain('Avbryt') expect(wrapper.text()).toContain('Avbryt')
}) })
await wrapper.find('.btn--ghost').trigger('click') await wrapper.find('.payment__confirm-cancel').trigger('click')
await vi.waitFor(() => { await vi.waitFor(() => {
expect(wrapper.text()).toContain('Swisha till') expect(wrapper.text()).toContain('Swisha till')
expect(wrapper.text()).not.toContain('Avbryt') expect(wrapper.text()).not.toContain('Avbryt')
@ -137,16 +167,15 @@ describe('PaymentRedirect', () => {
const { wrapper } = await mountPage() const { wrapper } = await mountPage()
await vi.waitFor(() => { await vi.waitFor(() => {
expect(wrapper.find('.btn--primary').exists()).toBe(true) expect(wrapper.find('.payment__submit').exists()).toBe(true)
}) })
await wrapper.find('.btn--primary').trigger('click') await wrapper.find('.payment__submit').trigger('click')
await vi.waitFor(() => { await vi.waitFor(() => {
expect(wrapper.text()).toContain('Ja, jag har betalat') expect(wrapper.text()).toContain('Ja, jag har betalat')
}) })
const confirmButtons = wrapper.findAll('.btn--primary') await wrapper.find('.payment__confirm .btn--primary').trigger('click')
await confirmButtons[confirmButtons.length - 1].trigger('click')
expect(mockPayOrder).toHaveBeenCalledWith('order-1') expect(mockPayOrder).toHaveBeenCalledWith('order-1')
}) })
@ -156,16 +185,15 @@ describe('PaymentRedirect', () => {
const { wrapper } = await mountPage() const { wrapper } = await mountPage()
await vi.waitFor(() => { await vi.waitFor(() => {
expect(wrapper.find('.btn--primary').exists()).toBe(true) expect(wrapper.find('.payment__submit').exists()).toBe(true)
}) })
await wrapper.find('.btn--primary').trigger('click') await wrapper.find('.payment__submit').trigger('click')
await vi.waitFor(() => { await vi.waitFor(() => {
expect(wrapper.text()).toContain('Ja, jag har betalat') expect(wrapper.text()).toContain('Ja, jag har betalat')
}) })
const confirmButtons = wrapper.findAll('.btn--primary') await wrapper.find('.payment__confirm .btn--primary').trigger('click')
await confirmButtons[confirmButtons.length - 1].trigger('click')
await vi.waitFor(() => { await vi.waitFor(() => {
expect(wrapper.text()).toContain('Kunde inte bekräfta betalningen') expect(wrapper.text()).toContain('Kunde inte bekräfta betalningen')
@ -184,16 +212,15 @@ describe('PaymentRedirect', () => {
const { wrapper, router } = await mountPage() const { wrapper, router } = await mountPage()
await vi.waitFor(() => { await vi.waitFor(() => {
expect(wrapper.find('.btn--primary').exists()).toBe(true) expect(wrapper.find('.payment__submit').exists()).toBe(true)
}) })
await wrapper.find('.btn--primary').trigger('click') await wrapper.find('.payment__submit').trigger('click')
await vi.waitFor(() => { await vi.waitFor(() => {
expect(wrapper.text()).toContain('Ja, jag har betalat') expect(wrapper.text()).toContain('Ja, jag har betalat')
}) })
const confirmButtons = wrapper.findAll('.btn--primary') await wrapper.find('.payment__confirm .btn--primary').trigger('click')
await confirmButtons[confirmButtons.length - 1].trigger('click')
await vi.waitFor(() => { await vi.waitFor(() => {
expect(router.currentRoute.value.name).toBe('orders') expect(router.currentRoute.value.name).toBe('orders')

View file

@ -15,3 +15,44 @@ export function payOrder(orderId: string): Promise<Order> {
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
export function fetchSwishInfo(): Promise<SwishInfo> { export function fetchSwishInfo(): Promise<SwishInfo> {
return request<SwishInfo>('/payment/swish-info') return request<SwishInfo>('/payment/swish-info')
} }
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
/**
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
* Build a pre-filled Swish payment URL.
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
*
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
* On mobile, tapping this URL opens the Swish app with the amount and
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
* message pre-filled. On desktop, embed it in a QR code for the user
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
* to scan with their phone.
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
*
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
* Uses the Swish "C2B pre-fill" URL scheme documented at
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
* https://developer.swish.nu — no Swish Commerce API certificate required.
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
* The `sw` parameter accepts either a phone number or a Swish Business
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
* number (123). Phone numbers in Swedish national format (leading 0)
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
* are normalised to international format (46).
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
*/
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
export function buildSwishPaymentUrl(
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
swishNumber: string,
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
amount: number,
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
message: string,
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
): string {
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
const payee = normalizeSwishNumber(swishNumber)
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
const params = new URLSearchParams({
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
sw: payee,
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
amt: amount.toFixed(2),
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
msg: message,
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
})
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
return `https://app.swish.nu/1/p/sw/?${params.toString()}`
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
}
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
/**
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
* Normalise a Swish number to the format the Swish URL expects.
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
* - 123 (Swish Business number) unchanged
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
* - 46 (already international) unchanged
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
* - 0 (Swedish national format) 46 + rest without leading 0
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
*/
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
function normalizeSwishNumber(number: string): string {
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
const trimmed = number.replace(/\s/g, '')
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
if (trimmed.startsWith('123')) return trimmed
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
if (trimmed.startsWith('46')) return trimmed
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
if (trimmed.startsWith('0')) return '46' + trimmed.slice(1)
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
return trimmed
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
}
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.

Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.
Review

💡 +46 prefix not handled

A number like +46701234567 doesn't match 123, 46, or 0 and passes through unchanged. The + gets URL-encoded to %2B by URLSearchParams, which may break the Swish URL.

Consider: const trimmed = number.replace(/[\s+]/g, '') to strip both spaces and leading +.

💡 **`+46` prefix not handled** A number like `+46701234567` doesn't match `123`, `46`, or `0` and passes through unchanged. The `+` gets URL-encoded to `%2B` by `URLSearchParams`, which may break the Swish URL. Consider: `const trimmed = number.replace(/[\s+]/g, '')` to strip both spaces and leading `+`.

View file

@ -1,7 +1,8 @@
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import { ref, computed, onMounted } from 'vue'
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import { payOrder, fetchSwishInfo } from '@/api/payment' import QRCode from 'qrcode'
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
import { payOrder, fetchSwishInfo, buildSwishPaymentUrl } from '@/api/payment'
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
import { isSessionExpired } from '@/api/client' import { isSessionExpired } from '@/api/client'
const router = useRouter() const router = useRouter()
@ -13,12 +14,27 @@ const swishAmount = ref(49)
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
const paying = ref(false) const paying = ref(false)
const error = ref('') const error = ref('')
const showConfirmation = ref(false) const showConfirmation = ref(false)
const qrDataUrl = ref('')
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
const swishPaymentUrl = computed(() =>
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
swishNumber.value
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
? buildSwishPaymentUrl(swishNumber.value, swishAmount.value, orderId)
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
: '',
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
)
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
onMounted(async () => { onMounted(async () => {
try { try {
const info = await fetchSwishInfo() const info = await fetchSwishInfo()
swishNumber.value = info.number swishNumber.value = info.number
swishAmount.value = info.amount swishAmount.value = info.amount
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
if (swishPaymentUrl.value) {
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
width: 224,
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
margin: 2,
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
color: { dark: '#111827', light: '#ffffff' },
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
})
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
}
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
} catch { } catch {
error.value = 'Kunde inte ladda betalningsinformation. Försök igen senare.' error.value = 'Kunde inte ladda betalningsinformation. Försök igen senare.'
} }
@ -78,21 +94,37 @@ async function confirmPayment() {
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
</div> </div>
<template v-if="!showConfirmation"> <template v-if="!showConfirmation">
<!-- QR code scan with the Swish app (desktop users) -->
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
<div v-if="qrDataUrl" class="payment__qr">
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
<img :src="qrDataUrl" alt="Swish QR-kod" class="payment__qr-img" />
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
<p class="payment__qr-hint">
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
Skanna QR-koden med Swish-appen för att betala
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
</p>
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
</div>
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
<!-- Direct link opens the Swish app (mobile users) -->
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
<a
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
v-if="swishPaymentUrl"
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
:href="swishPaymentUrl"
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
class="btn btn--primary btn--lg payment__swish-link"
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
>
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
Betala med Swish
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
</a>
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
<!-- Manual fallback -->
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
<div class="payment__swish"> <div class="payment__swish">
<p class="payment__swish-label">Swisha till</p> <p class="payment__swish-label">Swisha till</p>
<p class="payment__swish-number">{{ swishNumber }}</p> <p class="payment__swish-number">{{ swishNumber }}</p>
<p class="payment__swish-instruction"> <p class="payment__swish-instruction">
Ange beställnings-ID ovan som meddelande i Swish-appen. Belopp och beställnings-ID fylls i automatiskt via QR-kod eller
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
länk.
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
</p> </p>
<p class="payment__swish-instruction"> <p class="payment__swish-instruction">
Tryck sedan knappen nedan för att bekräfta. Betala manuellt om du inte har Swish-appen tillgänglig.
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
</p> </p>
</div> </div>
<button <button class="btn btn--ghost payment__submit" @click="startPayment">
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
class="btn btn--primary btn--lg payment__submit"
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
@click="startPayment"
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
>
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
Jag har betalat Jag har betalat
</button> </button>
</template> </template>
@ -201,6 +233,31 @@ async function confirmPayment() {
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
color: var(--color-ink); color: var(--color-ink);
} }
.payment__qr {
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
text-align: center;
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
margin-bottom: var(--space-lg);
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
}
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
.payment__qr-img {
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
width: 224px;
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
height: 224px;
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
border-radius: var(--radius-md);
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
margin: 0 auto var(--space-sm);
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
}
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
.payment__qr-hint {
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
font-size: 0.8125rem;
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
color: var(--color-muted);
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
}
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
.payment__swish-link {
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
display: block;
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
width: 100%;
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
text-align: center;
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
text-decoration: none;
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
margin-bottom: var(--space-lg);
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
}
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
.payment__swish { .payment__swish {
background: var(--color-border-light); background: var(--color-border-light);
border: 1px solid var(--color-border); border: 1px solid var(--color-border);

Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```
Review

⚠️ QR generation failure masks the payment UI

QRCode.toDataURL() shares the same try/catch as fetchSwishInfo(). If QR generation throws, the catch fires error.value = 'Kunde inte ladda betalningsinformation…' even though the Swish info was fetched successfully. The user sees a misleading error.

Suggested fix:

swishNumber.value = info.number
swishAmount.value = info.amount

try {
  if (swishPaymentUrl.value) {
    qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
      width: 224, margin: 2,
      color: { dark: '#111827', light: '#ffffff' },
    })
  }
} catch {
  // QR generation failed — degrade gracefully, manual fallback still shows
}
⚠️ **QR generation failure masks the payment UI** `QRCode.toDataURL()` shares the same `try/catch` as `fetchSwishInfo()`. If QR generation throws, the catch fires `error.value = 'Kunde inte ladda betalningsinformation…'` even though the Swish info was fetched successfully. The user sees a misleading error. Suggested fix: ```js swishNumber.value = info.number swishAmount.value = info.amount try { if (swishPaymentUrl.value) { qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, { width: 224, margin: 2, color: { dark: '#111827', light: '#ffffff' }, }) } } catch { // QR generation failed — degrade gracefully, manual fallback still shows } ```