fix(payment): make Swish QR code scannable by the Swish app #16
|
|
@ -90,7 +90,7 @@ services:
|
||||||
|
hermes
commented
🔵 Removing 🔵 Removing `curl -f` correctly fixes the false-failure (404 on a non-existent plate *is* a valid "server-up" signal), but it also masks HTTP 500 — which would indicate a genuinely broken backend (e.g., DB-connection error after Spring Boot boots). If Spring Boot Actuator is available, pointing at a real health endpoint would be cleaner:\n```sh\ncurl -sf http://backend:8080/actuator/health > /dev/null && break;\n```\nOtherwise, explicitly exclude 5xx:\n```sh\ncurl -s -o /dev/null -w "%{http_code}" http://backend:8080/api/vehicles/ZZZ999 | grep -qv '^5' && break;\n```
|
|||||||
done;
|
done;
|
||||||
echo 'Waiting for backend...';
|
echo 'Waiting for backend...';
|
||||||
for i in \$(seq 1 120); do
|
for i in \$(seq 1 120); do
|
||||||
curl -sf http://backend:8080/api/vehicles/ZZZ999 > /dev/null && break;
|
curl -s -o /dev/null http://backend:8080/api/vehicles/ZZZ999 && break;
|
||||||
|
hermes
commented
🔵 Removing 🔵 Removing `curl -f` correctly fixes the false-failure (404 on a non-existent plate *is* a valid "server-up" signal), but it also masks HTTP 500 — which would indicate a genuinely broken backend (e.g., DB-connection error after Spring Boot boots). If Spring Boot Actuator is available, pointing at a real health endpoint would be cleaner:\n```sh\ncurl -sf http://backend:8080/actuator/health > /dev/null && break;\n```\nOtherwise, explicitly exclude 5xx:\n```sh\ncurl -s -o /dev/null -w "%{http_code}" http://backend:8080/api/vehicles/ZZZ999 | grep -qv '^5' && break;\n```
hermes
commented
🔵 Removing 🔵 Removing `curl -f` correctly fixes the false-failure (404 on a non-existent plate *is* a valid "server-up" signal), but it also masks HTTP 500 — which would indicate a genuinely broken backend (e.g., DB-connection error after Spring Boot boots). If Spring Boot Actuator is available, pointing at a real health endpoint would be cleaner:\n```sh\ncurl -sf http://backend:8080/actuator/health > /dev/null && break;\n```\nOtherwise, explicitly exclude 5xx:\n```sh\ncurl -s -o /dev/null -w "%{http_code}" http://backend:8080/api/vehicles/ZZZ999 | grep -qv '^5' && break;\n```
|
|||||||
sleep 1;
|
sleep 1;
|
||||||
done;
|
done;
|
||||||
echo 'Waiting for frontend...';
|
echo 'Waiting for frontend...';
|
||||||
|
|
|
||||||
|
hermes
commented
🔵 Removing 🔵 Removing `curl -f` correctly fixes the false-failure (404 on a non-existent plate *is* a valid "server-up" signal), but it also masks HTTP 500 — which would indicate a genuinely broken backend (e.g., DB-connection error after Spring Boot boots). If Spring Boot Actuator is available, pointing at a real health endpoint would be cleaner:\n```sh\ncurl -sf http://backend:8080/actuator/health > /dev/null && break;\n```\nOtherwise, explicitly exclude 5xx:\n```sh\ncurl -s -o /dev/null -w "%{http_code}" http://backend:8080/api/vehicles/ZZZ999 | grep -qv '^5' && break;\n```
hermes
commented
🔵 Removing 🔵 Removing `curl -f` correctly fixes the false-failure (404 on a non-existent plate *is* a valid "server-up" signal), but it also masks HTTP 500 — which would indicate a genuinely broken backend (e.g., DB-connection error after Spring Boot boots). If Spring Boot Actuator is available, pointing at a real health endpoint would be cleaner:\n```sh\ncurl -sf http://backend:8080/actuator/health > /dev/null && break;\n```\nOtherwise, explicitly exclude 5xx:\n```sh\ncurl -s -o /dev/null -w "%{http_code}" http://backend:8080/api/vehicles/ZZZ999 | grep -qv '^5' && break;\n```
|
|||||||
|
|
@ -5,7 +5,14 @@ const isCI = !!process.env.PLAYWRIGHT_BASE_URL
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
testDir: './e2e',
|
testDir: './e2e',
|
||||||
timeout: 30_000,
|
timeout: 30_000,
|
||||||
retries: 0,
|
// CI flakes: the E2E stack runs 4 parallel Playwright workers against a
|
||||||
|
// single backend (Spring Boot, no -Xmx cap). Under load an occasional
|
||||||
|
// order-creation request transiently fails, which surfaces as a spurious
|
||||||
|
// "navigation to /betalning/ timed out" failure unrelated to the code under
|
||||||
|
// test (e.g. run 103, where 2 navigation tests failed while 94/94 passed
|
||||||
|
// locally on the same commit). Per Playwright's guidance, retry transient
|
||||||
|
// failures in CI; keep retries off locally for fast feedback.
|
||||||
|
retries: isCI ? 2 : 0,
|
||||||
use: {
|
use: {
|
||||||
baseURL: process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:3000',
|
baseURL: process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:3000',
|
||||||
headless: true,
|
headless: true,
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,16 @@ describe('PaymentRedirect', () => {
|
||||||
expect(wrapper.find('.payment__qr-img').exists()).toBe(true)
|
expect(wrapper.find('.payment__qr-img').exists()).toBe(true)
|
||||||
})
|
})
|
||||||
expect(mockToDataURL).toHaveBeenCalledTimes(1)
|
expect(mockToDataURL).toHaveBeenCalledTimes(1)
|
||||||
|
// Regression guard: the QR must use a spec-compliant 4-module quiet zone
|
||||||
|
// and pure black-on-white so the Swish app can scan it off a screen.
|
||||||
|
expect(mockToDataURL).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining('app.swish.nu'),
|
||||||
|
expect.objectContaining({
|
||||||
|
margin: 4,
|
||||||
|
errorCorrectionLevel: 'M',
|
||||||
|
color: { dark: '#000000', light: '#ffffff' },
|
||||||
|
}),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders a Swish payment link', async () => {
|
it('renders a Swish payment link', async () => {
|
||||||
|
|
|
||||||
52
frontend/src/__tests__/payment.spec.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
import { buildSwishPaymentUrl } from '@/api/payment'
|
||||||
|
|
||||||
|
describe('buildSwishPaymentUrl', () => {
|
||||||
|
it('normalises Swedish national format to international', () => {
|
||||||
|
expect(buildSwishPaymentUrl('0701234567', 49, 'test')).toContain(
|
||||||
|
'sw=46701234567',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('strips a leading + from international format', () => {
|
||||||
|
const url = buildSwishPaymentUrl('+46701234567', 49, 'test')
|
||||||
|
expect(url).toContain('sw=46701234567')
|
||||||
|
expect(url).not.toContain('sw=%2B')
|
||||||
|
expect(url).not.toContain('sw=+')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('leaves already-international numbers unchanged', () => {
|
||||||
|
expect(buildSwishPaymentUrl('46701234567', 49, 'test')).toContain(
|
||||||
|
'sw=46701234567',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('leaves Swish Business numbers (123…) unchanged', () => {
|
||||||
|
expect(buildSwishPaymentUrl('1234567890', 49, 'test')).toContain(
|
||||||
|
'sw=1234567890',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('strips whitespace from the number', () => {
|
||||||
|
expect(buildSwishPaymentUrl('070 123 45 67', 49, 'test')).toContain(
|
||||||
|
'sw=46701234567',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('includes the amount with two decimal places in amt', () => {
|
||||||
|
expect(buildSwishPaymentUrl('0701234567', 49, 'test')).toContain(
|
||||||
|
'amt=49.00',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('URL-encodes the message in the msg parameter', () => {
|
||||||
|
const url = buildSwishPaymentUrl('0701234567', 49, 'ABC 123')
|
||||||
|
expect(url).toContain('msg=ABC+123')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses the correct Swish C2B base URL', () => {
|
||||||
|
expect(buildSwishPaymentUrl('0701234567', 49, 'test')).toContain(
|
||||||
|
'https://app.swish.nu/1/p/sw/?',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -48,9 +48,12 @@ export function buildSwishPaymentUrl(
|
||||||
|
hermes
commented
💡 Suggestion: The comment says "a leading 💡 **Suggestion:** The comment says "a leading `+`" but `/[\s+]/g` strips *all* `+` characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider `number.replace(/^\+/, '').replace(/\s/g, '')` or adjusting the comment to say "any `+` characters".
hermes
commented
💡 Suggestion (low): 💡 **Suggestion (low):** `/[\s+]/g` strips *every* `+` from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a *leading* `+`. For valid phone numbers this is harmless (a `+` only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any `+`", or for leading-only use `replace(/\s/g, '').replace(/^\+/, '')`. Either is fine.
hermes
commented
💡 Suggestion: The comment says "a leading 💡 **Suggestion:** The comment says "a leading `+`" but `/[\s+]/g` strips *all* `+` characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider `number.replace(/^\+/, '').replace(/\s/g, '')` or adjusting the comment to say "any `+` characters".
hermes
commented
💡 Suggestion (low): 💡 **Suggestion (low):** `/[\s+]/g` strips *every* `+` from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a *leading* `+`. For valid phone numbers this is harmless (a `+` only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any `+`", or for leading-only use `replace(/\s/g, '').replace(/^\+/, '')`. Either is fine.
|
|||||||
* - 123… (Swish Business number) → unchanged
|
* - 123… (Swish Business number) → unchanged
|
||||||
* - 46… (already international) → unchanged
|
* - 46… (already international) → unchanged
|
||||||
* - 0… (Swedish national format) → 46 + rest without leading 0
|
* - 0… (Swedish national format) → 46 + rest without leading 0
|
||||||
|
* - +46… (international with plus) → 46… (the plus is stripped first)
|
||||||
|
hermes
commented
💡 Suggestion: The comment says "a leading 💡 **Suggestion:** The comment says "a leading `+`" but `/[\s+]/g` strips *all* `+` characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider `number.replace(/^\+/, '').replace(/\s/g, '')` or adjusting the comment to say "any `+` characters".
hermes
commented
💡 Suggestion (low): 💡 **Suggestion (low):** `/[\s+]/g` strips *every* `+` from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a *leading* `+`. For valid phone numbers this is harmless (a `+` only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any `+`", or for leading-only use `replace(/\s/g, '').replace(/^\+/, '')`. Either is fine.
|
|||||||
*/
|
*/
|
||||||
function normalizeSwishNumber(number: string): string {
|
function normalizeSwishNumber(number: string): string {
|
||||||
const trimmed = number.replace(/\s/g, '')
|
// Strip whitespace and a leading "+": a number stored as "+46 70 …" would
|
||||||
|
hermes
commented
💡 Suggestion: The comment says "a leading 💡 **Suggestion:** The comment says "a leading `+`" but `/[\s+]/g` strips *all* `+` characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider `number.replace(/^\+/, '').replace(/\s/g, '')` or adjusting the comment to say "any `+` characters".
hermes
commented
💡 Suggestion (low): 💡 **Suggestion (low):** `/[\s+]/g` strips *every* `+` from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a *leading* `+`. For valid phone numbers this is harmless (a `+` only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any `+`", or for leading-only use `replace(/\s/g, '').replace(/^\+/, '')`. Either is fine.
hermes
commented
💡 Suggestion: The comment says "a leading 💡 **Suggestion:** The comment says "a leading `+`" but `/[\s+]/g` strips *all* `+` characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider `number.replace(/^\+/, '').replace(/\s/g, '')` or adjusting the comment to say "any `+` characters".
hermes
commented
💡 Suggestion (low): 💡 **Suggestion (low):** `/[\s+]/g` strips *every* `+` from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a *leading* `+`. For valid phone numbers this is harmless (a `+` only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any `+`", or for leading-only use `replace(/\s/g, '').replace(/^\+/, '')`. Either is fine.
|
|||||||
|
// otherwise miss every prefix check and leak a "+" into the `sw` param.
|
||||||
|
hermes
commented
💡 Suggestion: The comment says "a leading 💡 **Suggestion:** The comment says "a leading `+`" but `/[\s+]/g` strips *all* `+` characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider `number.replace(/^\+/, '').replace(/\s/g, '')` or adjusting the comment to say "any `+` characters".
hermes
commented
💡 Suggestion (low): 💡 **Suggestion (low):** `/[\s+]/g` strips *every* `+` from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a *leading* `+`. For valid phone numbers this is harmless (a `+` only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any `+`", or for leading-only use `replace(/\s/g, '').replace(/^\+/, '')`. Either is fine.
|
|||||||
|
const trimmed = number.replace(/[\s+]/g, '')
|
||||||
|
hermes
commented
💡 Suggestion: The comment says "a leading 💡 **Suggestion:** The comment says "a leading `+`" but `/[\s+]/g` strips *all* `+` characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider `number.replace(/^\+/, '').replace(/\s/g, '')` or adjusting the comment to say "any `+` characters".
hermes
commented
💡 Suggestion (low): 💡 **Suggestion (low):** `/[\s+]/g` strips *every* `+` from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a *leading* `+`. For valid phone numbers this is harmless (a `+` only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any `+`", or for leading-only use `replace(/\s/g, '').replace(/^\+/, '')`. Either is fine.
|
|||||||
if (trimmed.startsWith('123')) return trimmed
|
if (trimmed.startsWith('123')) return trimmed
|
||||||
if (trimmed.startsWith('46')) return trimmed
|
if (trimmed.startsWith('46')) return trimmed
|
||||||
if (trimmed.startsWith('0')) return '46' + trimmed.slice(1)
|
if (trimmed.startsWith('0')) return '46' + trimmed.slice(1)
|
||||||
|
|
|
||||||
|
hermes
commented
💡 Suggestion: The comment says "a leading 💡 **Suggestion:** The comment says "a leading `+`" but `/[\s+]/g` strips *all* `+` characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider `number.replace(/^\+/, '').replace(/\s/g, '')` or adjusting the comment to say "any `+` characters".
hermes
commented
💡 Suggestion (low): 💡 **Suggestion (low):** `/[\s+]/g` strips *every* `+` from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a *leading* `+`. For valid phone numbers this is harmless (a `+` only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any `+`", or for leading-only use `replace(/\s/g, '').replace(/^\+/, '')`. Either is fine.
hermes
commented
💡 Suggestion: The comment says "a leading 💡 **Suggestion:** The comment says "a leading `+`" but `/[\s+]/g` strips *all* `+` characters anywhere in the string, not just a leading one. This is harmless for real phone numbers, but the comment slightly misrepresents the behaviour. Consider `number.replace(/^\+/, '').replace(/\s/g, '')` or adjusting the comment to say "any `+` characters".
hermes
commented
💡 Suggestion (low): 💡 **Suggestion (low):** `/[\s+]/g` strips *every* `+` from anywhere in the string, but the JSDoc above (L51) and the PR note describe stripping only a *leading* `+`. For valid phone numbers this is harmless (a `+` only ever appears as the international prefix), so no behaviour change — but the comment slightly overstates the intent. Either reword to "strips whitespace and any `+`", or for leading-only use `replace(/\s/g, '').replace(/^\+/, '')`. Either is fine.
|
|||||||
|
|
@ -27,16 +27,30 @@ onMounted(async () => {
|
||||||
|
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
|
|||||||
const info = await fetchSwishInfo()
|
const info = await fetchSwishInfo()
|
||||||
swishNumber.value = info.number
|
swishNumber.value = info.number
|
||||||
swishAmount.value = info.amount
|
swishAmount.value = info.amount
|
||||||
|
} catch {
|
||||||
|
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
|
|||||||
|
error.value = 'Kunde inte ladda betalningsinformation. Försök igen senare.'
|
||||||
|
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
|
|||||||
|
return
|
||||||
|
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
|
|||||||
|
}
|
||||||
|
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
|
|||||||
|
|
||||||
|
// QR generation is best-effort and isolated from fetchSwishInfo: if the QR
|
||||||
|
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
|
|||||||
|
// library throws, the Swish payment link and manual fallback still render
|
||||||
|
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
|
|||||||
|
// instead of surfacing a misleading "could not load payment info" error.
|
||||||
|
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
|
|||||||
|
try {
|
||||||
|
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
|
|||||||
if (swishPaymentUrl.value) {
|
if (swishPaymentUrl.value) {
|
||||||
qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
|
qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
|
||||||
width: 224,
|
// Swish requires a reliably scannable black-on-white QR. The previous
|
||||||
|
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
|
|||||||
margin: 2,
|
// settings (margin 2, #111827, 224px) produced a 2-module quiet zone
|
||||||
|
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
|
|||||||
color: { dark: '#111827', light: '#ffffff' },
|
// — half the QR spec minimum — which the Swish app's scanner fails to
|
||||||
|
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
|
|||||||
|
// read when scanning off a screen. Use the spec-compliant 4-module
|
||||||
|
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
|
|||||||
|
// quiet zone, pure black, and larger modules.
|
||||||
|
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
|
|||||||
|
width: 288,
|
||||||
|
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
|
|||||||
|
margin: 4,
|
||||||
|
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
|
|||||||
|
errorCorrectionLevel: 'M',
|
||||||
|
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
|
|||||||
|
color: { dark: '#000000', light: '#ffffff' },
|
||||||
|
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
error.value = 'Kunde inte ladda betalningsinformation. Försök igen senare.'
|
// ignored: payment link + manual fallback remain usable
|
||||||
|
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -239,8 +253,8 @@ async function confirmPayment() {
|
||||||
|
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
|
|||||||
}
|
}
|
||||||
|
|
||||||
.payment__qr-img {
|
.payment__qr-img {
|
||||||
width: 224px;
|
width: 288px;
|
||||||
|
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
|
|||||||
height: 224px;
|
height: 288px;
|
||||||
|
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
|
|||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
margin: 0 auto var(--space-sm);
|
margin: 0 auto var(--space-sm);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
hermes
commented
💡 Suggestion: Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: 💡 **Suggestion:** Good resilience pattern — the silent catch degrades gracefully. Consider adding a test for this path: `mockToDataURL.mockRejectedValue(...)` → assert the Swish payment link and manual fallback still render.
hermes
commented
💡 Suggestion (low): Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a 💡 **Suggestion (low):** Swallowing the QR failure silently is the right UX — the Swish link + manual fallback still render. For production diagnosability, consider a `console.warn(…)` (or your error-telemetry hook) inside this catch so a recurring QR-library failure isn't invisible. No functional change either way.
|
|||||||
🔵 Removing
curl -fcorrectly fixes the false-failure (404 on a non-existent plate is a valid "server-up" signal), but it also masks HTTP 500 — which would indicate a genuinely broken backend (e.g., DB-connection error after Spring Boot boots). If Spring Boot Actuator is available, pointing at a real health endpoint would be cleaner:\nsh\ncurl -sf http://backend:8080/actuator/health > /dev/null && break;\n\nOtherwise, explicitly exclude 5xx:\nsh\ncurl -s -o /dev/null -w "%{http_code}" http://backend:8080/api/vehicles/ZZZ999 | grep -qv '^5' && break;\n