Bug/stale skv connection (#428)

* fix(dashboard): enable salary features with "Beta" badge for testing

* fix(errors): enhance Swedish error message patterns for better user feedback

fix(salary): update Nordea Personkonto handling in account encoding logic

* fix(agi): implement feature flag for AGI transmission and update button states

* fix(swedish-payroll): update youth rate eligibility criteria and enhance documentation

* fix(agi-panel): remove feature flag for AGI transmission and simplify button states

* fix(agi-panel): handle stale session errors and improve reconnect flow

* fix(api-client): enhance error handling for 401 responses and clarify subscription issues
This commit is contained in:
Mattsson
2026-05-10 00:39:51 +02:00
committed by GitHub
parent efdd3a50ba
commit c110d3ef99
2 changed files with 75 additions and 51 deletions
+43 -50
View File
@@ -734,62 +734,55 @@ export function AGIPanel(props: AGIPanelProps) {
)}
{!readOnly && !isSigned && (
<div className="flex flex-wrap gap-2">
<Button
size="sm"
variant="outline"
onClick={handleSubmit}
disabled={!!actionLoading || awaitingSigning}
>
{actionLoading === 'submit' ? (
<Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />
) : (
<Send className="mr-1.5 h-3.5 w-3.5" />
)}
Skicka in underlag
</Button>
<Button
size="sm"
onClick={handleCreateSigningLink}
disabled={!!actionLoading || (!underlagSubmitted && !awaitingSigning)}
title={!underlagSubmitted && !awaitingSigning ? 'Skicka in underlag först' : ''}
>
{actionLoading === 'granskning' ? (
<Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />
) : (
<Lock className="mr-1.5 h-3.5 w-3.5" />
)}
Skapa signeringslänk
</Button>
{awaitingSigning && (
<>
{/* Direct AGI submission to Skatteverket is paused while the
APIGW subscription is sorted out at SKV's end. Users still
generate and download the AGI XML from the salary run page
and upload it manually via Mina Sidor. Re-enable the three
buttons below once the subscription is in place. */}
<div className="rounded-md border border-amber-300 bg-amber-50 p-3 dark:border-amber-900/40 dark:bg-amber-900/20">
<p className="text-sm font-medium">
Direktinlämning till Skatteverket är pausad
</p>
<p className="mt-1 text-xs text-muted-foreground">
Ladda ner AGI-filen ovan och lämna in den manuellt via Mina Sidor
hos Skatteverket. Direktinlämning aktiveras igen när vår
anslutning hos Skatteverket är klar.
</p>
</div>
<div className="flex flex-wrap gap-2">
<Button
size="sm"
variant="outline"
onClick={handleUnlock}
disabled={!!actionLoading}
onClick={handleSubmit}
disabled
title="Direktinlämning till Skatteverket är pausad"
>
{actionLoading === 'unlock' ? (
<Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />
) : (
<Unlock className="mr-1.5 h-3.5 w-3.5" />
)}
Lås upp period
<Send className="mr-1.5 h-3.5 w-3.5" />
Skicka in underlag
</Button>
)}
<Button
size="sm"
variant="ghost"
onClick={handleCheckSubmitted}
disabled={!!actionLoading}
>
{actionLoading === 'check' ? (
<Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />
) : (
<Button
size="sm"
onClick={handleCreateSigningLink}
disabled
title="Direktinlämning till Skatteverket är pausad"
>
<Lock className="mr-1.5 h-3.5 w-3.5" />
Skapa signeringslänk
</Button>
<Button
size="sm"
variant="ghost"
onClick={handleCheckSubmitted}
disabled
title="Direktinlämning till Skatteverket är pausad"
>
<Download className="mr-1.5 h-3.5 w-3.5" />
)}
Hämta kvittens
</Button>
</div>
Hämta kvittens
</Button>
</div>
</>
)}
</CardContent>
</Card>
@@ -209,8 +209,39 @@ export async function skvRequest(
// Handle Skatteverket-specific auth/throttle errors uniformly so callers
// can catch a single error type rather than parsing status codes inline.
if (response.status === 401) {
// SKV returns 401 for two distinct reasons that need different remedies:
// 1. Genuine token expiry / invalid bearer (user must re-auth)
// 2. APIGW client lacks subscription for this API (developer portal fix)
// — the bearer is valid but the gateway rejects the call.
// Read the body so we can distinguish and surface a useful message.
const text = await response.text().catch(() => '')
console.error('[skatteverket] 401 from API', { url, body: text })
// APIGW subscription / client-credential problems: the gateway responds
// before the bearer is ever evaluated. The user reconnecting won't help
// here — it's an Utvecklarportalen / APIGW configuration issue.
const lower = text.toLowerCase()
const looksLikeApigwIssue =
lower.includes('client_id') ||
lower.includes('client id') ||
lower.includes('subscription') ||
lower.includes('not subscribed') ||
lower.includes('apigw') ||
lower.includes('api key') ||
lower.includes('consumer')
if (looksLikeApigwIssue) {
throw new SkatteverketAuthError(
'Skatteverkets API-gateway nekade anropet. Kontrollera att din ' +
'APIGW-klient (SKATTEVERKET_APIGW_CLIENT_ID) har prenumeration på ' +
`denna tjänst i Utvecklarportalen. Svar från Skatteverket: ${text || '(tomt svar)'}`,
'ACCESS_DENIED'
)
}
throw new SkatteverketAuthError(
'Sessionen har gått ut. Logga in med BankID igen.',
text
? `Sessionen har gått ut. Logga in med BankID igen. (Skatteverket: ${text})`
: 'Sessionen har gått ut. Logga in med BankID igen.',
'SESSION_EXPIRED'
)
}