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:
@@ -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'
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user