Compare commits
No commits in common. "master" and "feature/expired-token-logout" have entirely different histories.
master
...
feature/ex
9 changed files with 70 additions and 540 deletions
|
|
@ -24,12 +24,6 @@ STRIPE_WEBHOOK_SECRET=whsec_...
|
|||
STRIPE_PRICE_ID=price_...
|
||||
|
||||
# ---------- 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
|
||||
|
||||
# ---------- App URL (password reset links in email) ----------
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ on:
|
|||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Leave as "auto" to bump from latest git tag, or enter a specific version (e.g. v0.1.2)'
|
||||
required: false
|
||||
default: 'auto'
|
||||
description: 'Git tag to create for this deploy (e.g. v0.1.2) — not the branch/tag above'
|
||||
required: true
|
||||
default: 'v0.1.0'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
|
|
@ -21,36 +21,12 @@ jobs:
|
|||
git fetch --depth 1 origin ${GITHUB_SHA}
|
||||
git checkout FETCH_HEAD
|
||||
|
||||
- name: Resolve version
|
||||
run: |
|
||||
INPUT_VERSION="${{ github.event.inputs.version }}"
|
||||
if [ -z "$INPUT_VERSION" ] || [ "$INPUT_VERSION" = "auto" ]; then
|
||||
git fetch --tags origin
|
||||
LATEST=$(git tag --list 'v*' --sort=-v:refname | head -1)
|
||||
if [ -z "$LATEST" ]; then LATEST="v0.0.0"; fi
|
||||
BASE="${LATEST#v}"
|
||||
MAJOR=$(echo "$BASE" | cut -d. -f1)
|
||||
MINOR=$(echo "$BASE" | cut -d. -f2)
|
||||
PATCH=$(echo "$BASE" | cut -d. -f3)
|
||||
PATCH=$(( ${PATCH:-0} + 1 ))
|
||||
VERSION="v${MAJOR:-0}.${MINOR:-0}.${PATCH}"
|
||||
echo "Latest tag: $LATEST → auto-bumped to $VERSION"
|
||||
else
|
||||
VERSION="$INPUT_VERSION"
|
||||
echo "Using manual version: $VERSION"
|
||||
fi
|
||||
if ! echo "$VERSION" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||||
echo "ERROR: resolved version '$VERSION' is not valid semver (expected vX.Y.Z)"
|
||||
exit 1
|
||||
fi
|
||||
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Tag version
|
||||
run: |
|
||||
git tag -d ${{ env.VERSION }} 2>/dev/null || true
|
||||
git push origin --delete ${{ env.VERSION }} 2>/dev/null || true
|
||||
git tag ${{ env.VERSION }}
|
||||
git push origin ${{ env.VERSION }}
|
||||
git tag -d ${{ github.event.inputs.version }} 2>/dev/null || true
|
||||
git push origin --delete ${{ github.event.inputs.version }} 2>/dev/null || true
|
||||
git tag ${{ github.event.inputs.version }}
|
||||
git push origin ${{ github.event.inputs.version }}
|
||||
|
||||
- name: Write production .env
|
||||
env:
|
||||
|
|
@ -158,7 +134,7 @@ jobs:
|
|||
run: |
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
echo " Deployed ${{ env.VERSION }} to production"
|
||||
echo " Deployed ${{ github.event.inputs.version }} to production"
|
||||
echo "═══════════════════════════════════════════════════"
|
||||
echo ""
|
||||
docker compose -p bilhej-prod -f docker-compose.prod.yml ps
|
||||
|
|
|
|||
|
|
@ -49,31 +49,6 @@ test.describe('Payment redirect', () => {
|
|||
|
||||
await page.waitForURL(/\/betalning\//)
|
||||
await expect(page.getByText('Swisha till')).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')
|
||||
await expect(page.getByRole('button', { name: 'Jag har betalat' })).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
356
frontend/package-lock.json
generated
356
frontend/package-lock.json
generated
|
|
@ -9,7 +9,6 @@
|
|||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"pinia": "^3.0.4",
|
||||
"qrcode": "^1.5.4",
|
||||
"vue": "^3.5.32",
|
||||
"vue-router": "^5.0.6"
|
||||
},
|
||||
|
|
@ -17,7 +16,6 @@
|
|||
"@playwright/test": "^1.60.0",
|
||||
"@rushstack/eslint-patch": "^1.16.1",
|
||||
"@types/node": "^24.12.2",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@vitejs/plugin-vue": "^6.0.6",
|
||||
"@vitest/coverage-v8": "^4.1.6",
|
||||
"@vue/eslint-config-prettier": "^10.2.0",
|
||||
|
|
@ -793,6 +791,9 @@
|
|||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -810,6 +811,9 @@
|
|||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -827,6 +831,9 @@
|
|||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -844,6 +851,9 @@
|
|||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -861,6 +871,9 @@
|
|||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -878,6 +891,9 @@
|
|||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -1038,16 +1054,6 @@
|
|||
"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": {
|
||||
"version": "8.59.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.1.tgz",
|
||||
|
|
@ -1956,15 +1962,6 @@
|
|||
"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": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
|
||||
|
|
@ -1990,91 +1987,11 @@
|
|||
"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": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
|
|
@ -2087,6 +2004,7 @@
|
|||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/commander": {
|
||||
|
|
@ -2218,15 +2136,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "10.6.0",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
|
||||
|
|
@ -2251,12 +2160,6 @@
|
|||
"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": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
|
|
@ -2815,15 +2718,6 @@
|
|||
"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": {
|
||||
"version": "10.5.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
||||
|
|
@ -2969,6 +2863,7 @@
|
|||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
|
|
@ -3390,6 +3285,9 @@
|
|||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -3411,6 +3309,9 @@
|
|||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -3432,6 +3333,9 @@
|
|||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -3453,6 +3357,9 @@
|
|||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
|
@ -3836,15 +3743,6 @@
|
|||
"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": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
||||
|
|
@ -3889,6 +3787,7 @@
|
|||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
|
|
@ -4037,15 +3936,6 @@
|
|||
"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": {
|
||||
"version": "8.5.13",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz",
|
||||
|
|
@ -4144,23 +4034,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": {
|
||||
"version": "0.2.11",
|
||||
"resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz",
|
||||
|
|
@ -4211,15 +4084,6 @@
|
|||
"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": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
|
|
@ -4230,12 +4094,6 @@
|
|||
"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": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
|
||||
|
|
@ -4350,12 +4208,6 @@
|
|||
"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": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
|
|
@ -5242,12 +5094,6 @@
|
|||
"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": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
|
||||
|
|
@ -5390,12 +5236,6 @@
|
|||
"dev": true,
|
||||
"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": {
|
||||
"version": "2.8.3",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
|
||||
|
|
@ -5411,134 +5251,6 @@
|
|||
"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": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"pinia": "^3.0.4",
|
||||
"qrcode": "^1.5.4",
|
||||
"vue": "^3.5.32",
|
||||
"vue-router": "^5.0.6"
|
||||
},
|
||||
|
|
@ -25,7 +24,6 @@
|
|||
"@playwright/test": "^1.60.0",
|
||||
"@rushstack/eslint-patch": "^1.16.1",
|
||||
"@types/node": "^24.12.2",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@vitejs/plugin-vue": "^6.0.6",
|
||||
"@vitest/coverage-v8": "^4.1.6",
|
||||
"@vue/eslint-config-prettier": "^10.2.0",
|
||||
|
|
|
|||
|
|
@ -5,26 +5,14 @@ import { createRouter, createMemoryHistory } from 'vue-router'
|
|||
import PaymentRedirect from '@/pages/PaymentRedirect.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', () => ({
|
||||
payOrder: 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 QRCode from 'qrcode'
|
||||
const mockPayOrder = vi.mocked(payOrder)
|
||||
const mockFetchSwishInfo = vi.mocked(fetchSwishInfo)
|
||||
const mockToDataURL = vi.mocked(QRCode.toDataURL)
|
||||
|
||||
function createTestRouter() {
|
||||
return createRouter({
|
||||
|
|
@ -71,7 +59,6 @@ describe('PaymentRedirect', () => {
|
|||
number: '0701234567',
|
||||
amount: 49,
|
||||
})
|
||||
mockToDataURL.mockResolvedValue('data:image/png;base64,mock-qr')
|
||||
})
|
||||
|
||||
it('renders heading and amount', async () => {
|
||||
|
|
@ -94,7 +81,7 @@ describe('PaymentRedirect', () => {
|
|||
expect(wrapper.text()).toContain('Beställnings-ID')
|
||||
expect(wrapper.text()).toContain(orderId)
|
||||
expect(wrapper.text()).toContain(
|
||||
'fylls i automatiskt via QR-kod eller länk',
|
||||
'Ange beställnings-ID ovan som meddelande i Swish-appen',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
@ -106,30 +93,13 @@ 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 () => {
|
||||
const { wrapper } = await mountPage()
|
||||
await vi.waitFor(() => {
|
||||
expect(wrapper.find('.payment__submit').exists()).toBe(true)
|
||||
expect(wrapper.find('.btn--primary').exists()).toBe(true)
|
||||
})
|
||||
|
||||
await wrapper.find('.payment__submit').trigger('click')
|
||||
await wrapper.find('.btn--primary').trigger('click')
|
||||
await vi.waitFor(() => {
|
||||
expect(wrapper.text()).toContain('Jag bekräftar att jag har Swishat')
|
||||
expect(wrapper.text()).toContain('0701234567')
|
||||
|
|
@ -140,15 +110,15 @@ describe('PaymentRedirect', () => {
|
|||
it('can cancel confirmation dialog', async () => {
|
||||
const { wrapper } = await mountPage()
|
||||
await vi.waitFor(() => {
|
||||
expect(wrapper.find('.payment__submit').exists()).toBe(true)
|
||||
expect(wrapper.find('.btn--primary').exists()).toBe(true)
|
||||
})
|
||||
|
||||
await wrapper.find('.payment__submit').trigger('click')
|
||||
await wrapper.find('.btn--primary').trigger('click')
|
||||
await vi.waitFor(() => {
|
||||
expect(wrapper.text()).toContain('Avbryt')
|
||||
})
|
||||
|
||||
await wrapper.find('.payment__confirm-cancel').trigger('click')
|
||||
await wrapper.find('.btn--ghost').trigger('click')
|
||||
await vi.waitFor(() => {
|
||||
expect(wrapper.text()).toContain('Swisha till')
|
||||
expect(wrapper.text()).not.toContain('Avbryt')
|
||||
|
|
@ -167,15 +137,16 @@ describe('PaymentRedirect', () => {
|
|||
|
||||
const { wrapper } = await mountPage()
|
||||
await vi.waitFor(() => {
|
||||
expect(wrapper.find('.payment__submit').exists()).toBe(true)
|
||||
expect(wrapper.find('.btn--primary').exists()).toBe(true)
|
||||
})
|
||||
|
||||
await wrapper.find('.payment__submit').trigger('click')
|
||||
await wrapper.find('.btn--primary').trigger('click')
|
||||
await vi.waitFor(() => {
|
||||
expect(wrapper.text()).toContain('Ja, jag har betalat')
|
||||
})
|
||||
|
||||
await wrapper.find('.payment__confirm .btn--primary').trigger('click')
|
||||
const confirmButtons = wrapper.findAll('.btn--primary')
|
||||
await confirmButtons[confirmButtons.length - 1].trigger('click')
|
||||
|
||||
expect(mockPayOrder).toHaveBeenCalledWith('order-1')
|
||||
})
|
||||
|
|
@ -185,15 +156,16 @@ describe('PaymentRedirect', () => {
|
|||
|
||||
const { wrapper } = await mountPage()
|
||||
await vi.waitFor(() => {
|
||||
expect(wrapper.find('.payment__submit').exists()).toBe(true)
|
||||
expect(wrapper.find('.btn--primary').exists()).toBe(true)
|
||||
})
|
||||
|
||||
await wrapper.find('.payment__submit').trigger('click')
|
||||
await wrapper.find('.btn--primary').trigger('click')
|
||||
await vi.waitFor(() => {
|
||||
expect(wrapper.text()).toContain('Ja, jag har betalat')
|
||||
})
|
||||
|
||||
await wrapper.find('.payment__confirm .btn--primary').trigger('click')
|
||||
const confirmButtons = wrapper.findAll('.btn--primary')
|
||||
await confirmButtons[confirmButtons.length - 1].trigger('click')
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(wrapper.text()).toContain('Kunde inte bekräfta betalningen')
|
||||
|
|
@ -212,15 +184,16 @@ describe('PaymentRedirect', () => {
|
|||
|
||||
const { wrapper, router } = await mountPage()
|
||||
await vi.waitFor(() => {
|
||||
expect(wrapper.find('.payment__submit').exists()).toBe(true)
|
||||
expect(wrapper.find('.btn--primary').exists()).toBe(true)
|
||||
})
|
||||
|
||||
await wrapper.find('.payment__submit').trigger('click')
|
||||
await wrapper.find('.btn--primary').trigger('click')
|
||||
await vi.waitFor(() => {
|
||||
expect(wrapper.text()).toContain('Ja, jag har betalat')
|
||||
})
|
||||
|
||||
await wrapper.find('.payment__confirm .btn--primary').trigger('click')
|
||||
const confirmButtons = wrapper.findAll('.btn--primary')
|
||||
await confirmButtons[confirmButtons.length - 1].trigger('click')
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(router.currentRoute.value.name).toBe('orders')
|
||||
|
|
|
|||
|
|
@ -15,44 +15,3 @@ export function payOrder(orderId: string): Promise<Order> {
|
|||
export function fetchSwishInfo(): Promise<SwishInfo> {
|
||||
return request<SwishInfo>('/payment/swish-info')
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a pre-filled Swish payment URL.
|
||||
*
|
||||
* On mobile, tapping this URL opens the Swish app with the amount and
|
||||
* message pre-filled. On desktop, embed it in a QR code for the user
|
||||
* to scan with their phone.
|
||||
*
|
||||
* Uses the Swish "C2B pre-fill" URL scheme documented at
|
||||
* https://developer.swish.nu — no Swish Commerce API certificate required.
|
||||
* The `sw` parameter accepts either a phone number or a Swish Business
|
||||
* number (123…). Phone numbers in Swedish national format (leading 0)
|
||||
* are normalised to international format (46…).
|
||||
*/
|
||||
export function buildSwishPaymentUrl(
|
||||
swishNumber: string,
|
||||
amount: number,
|
||||
message: string,
|
||||
): string {
|
||||
const payee = normalizeSwishNumber(swishNumber)
|
||||
const params = new URLSearchParams({
|
||||
sw: payee,
|
||||
amt: amount.toFixed(2),
|
||||
msg: message,
|
||||
})
|
||||
return `https://app.swish.nu/1/p/sw/?${params.toString()}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalise a Swish number to the format the Swish URL expects.
|
||||
* - 123… (Swish Business number) → unchanged
|
||||
* - 46… (already international) → unchanged
|
||||
* - 0… (Swedish national format) → 46 + rest without leading 0
|
||||
*/
|
||||
function normalizeSwishNumber(number: string): string {
|
||||
const trimmed = number.replace(/\s/g, '')
|
||||
if (trimmed.startsWith('123')) return trimmed
|
||||
if (trimmed.startsWith('46')) return trimmed
|
||||
if (trimmed.startsWith('0')) return '46' + trimmed.slice(1)
|
||||
return trimmed
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ onUnmounted(() => {
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
<style scoped>
|
||||
.admin {
|
||||
max-width: 72rem;
|
||||
margin: var(--space-2xl) auto 0;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import QRCode from 'qrcode'
|
||||
import { payOrder, fetchSwishInfo, buildSwishPaymentUrl } from '@/api/payment'
|
||||
import { payOrder, fetchSwishInfo } from '@/api/payment'
|
||||
import { isSessionExpired } from '@/api/client'
|
||||
|
||||
const router = useRouter()
|
||||
|
|
@ -14,27 +13,12 @@ const swishAmount = ref(49)
|
|||
const paying = ref(false)
|
||||
const error = ref('')
|
||||
const showConfirmation = ref(false)
|
||||
const qrDataUrl = ref('')
|
||||
|
||||
const swishPaymentUrl = computed(() =>
|
||||
swishNumber.value
|
||||
? buildSwishPaymentUrl(swishNumber.value, swishAmount.value, orderId)
|
||||
: '',
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const info = await fetchSwishInfo()
|
||||
swishNumber.value = info.number
|
||||
swishAmount.value = info.amount
|
||||
|
||||
if (swishPaymentUrl.value) {
|
||||
qrDataUrl.value = await QRCode.toDataURL(swishPaymentUrl.value, {
|
||||
width: 224,
|
||||
margin: 2,
|
||||
color: { dark: '#111827', light: '#ffffff' },
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
error.value = 'Kunde inte ladda betalningsinformation. Försök igen senare.'
|
||||
}
|
||||
|
|
@ -94,37 +78,21 @@ async function confirmPayment() {
|
|||
</div>
|
||||
|
||||
<template v-if="!showConfirmation">
|
||||
<!-- QR code — scan with the Swish app (desktop users) -->
|
||||
<div v-if="qrDataUrl" class="payment__qr">
|
||||
<img :src="qrDataUrl" alt="Swish QR-kod" class="payment__qr-img" />
|
||||
<p class="payment__qr-hint">
|
||||
Skanna QR-koden med Swish-appen för att betala
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Direct link — opens the Swish app (mobile users) -->
|
||||
<a
|
||||
v-if="swishPaymentUrl"
|
||||
:href="swishPaymentUrl"
|
||||
class="btn btn--primary btn--lg payment__swish-link"
|
||||
>
|
||||
Betala med Swish
|
||||
</a>
|
||||
|
||||
<!-- Manual fallback -->
|
||||
<div class="payment__swish">
|
||||
<p class="payment__swish-label">Swisha till</p>
|
||||
<p class="payment__swish-number">{{ swishNumber }}</p>
|
||||
<p class="payment__swish-instruction">
|
||||
Belopp och beställnings-ID fylls i automatiskt via QR-kod eller
|
||||
länk.
|
||||
Ange beställnings-ID ovan som meddelande i Swish-appen.
|
||||
</p>
|
||||
<p class="payment__swish-instruction">
|
||||
Betala manuellt om du inte har Swish-appen tillgänglig.
|
||||
Tryck sedan på knappen nedan för att bekräfta.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button class="btn btn--ghost payment__submit" @click="startPayment">
|
||||
<button
|
||||
class="btn btn--primary btn--lg payment__submit"
|
||||
@click="startPayment"
|
||||
>
|
||||
Jag har betalat
|
||||
</button>
|
||||
</template>
|
||||
|
|
@ -233,31 +201,6 @@ async function confirmPayment() {
|
|||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.payment__qr {
|
||||
text-align: center;
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
.payment__qr-img {
|
||||
width: 224px;
|
||||
height: 224px;
|
||||
border-radius: var(--radius-md);
|
||||
margin: 0 auto var(--space-sm);
|
||||
}
|
||||
|
||||
.payment__qr-hint {
|
||||
font-size: 0.8125rem;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.payment__swish-link {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
.payment__swish {
|
||||
background: var(--color-border-light);
|
||||
border: 1px solid var(--color-border);
|
||||
|
|
|
|||
Loading…
Reference in a new issue