From 88d1a730b9733b89dbcc6e5ecfc736ebb29b2c1c Mon Sep 17 00:00:00 2001 From: Hongwei Date: Sat, 18 Jul 2026 08:23:20 +0200 Subject: [PATCH 1/2] fix: correct Berlin Group SCA authorisation URL segment order The payment SCA endpoints called /obp/v1.3/berlin-group/... but OBP-API registers these routes at /berlin-group/v1.3/... with no /obp/ prefix, so both the create-authorisation POST and OTP-submit PUT calls were hitting a non-existent path. --- apps/portal/src/routes/(protected)/otp/+page.server.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/portal/src/routes/(protected)/otp/+page.server.ts b/apps/portal/src/routes/(protected)/otp/+page.server.ts index 4b303bac..80df6529 100644 --- a/apps/portal/src/routes/(protected)/otp/+page.server.ts +++ b/apps/portal/src/routes/(protected)/otp/+page.server.ts @@ -81,7 +81,7 @@ export const actions = { // Step 1: Create authorisation const authResponse = await obp_requests.post( - `/obp/v1.3/berlin-group/${paymentService}/${paymentProduct}/${paymentId}/authorisations`, + `/berlin-group/v1.3/${paymentService}/${paymentProduct}/${paymentId}/authorisations`, {}, token ); @@ -90,7 +90,7 @@ export const actions = { // Step 2: Submit OTP to authorisation await obp_requests.put( - `/obp/v1.3/berlin-group/${paymentService}/${paymentProduct}/${paymentId}/authorisations/${authorisationId}`, + `/berlin-group/v1.3/${paymentService}/${paymentProduct}/${paymentId}/authorisations/${authorisationId}`, { scaAuthenticationData: otp }, token ); From a80a60a6e42e9691e96a511b749d92bd7908d271 Mon Sep 17 00:00:00 2001 From: Hongwei Date: Sat, 18 Jul 2026 09:45:14 +0200 Subject: [PATCH 2/2] fix(portal): call the Berlin Group native SCA endpoints for BG consent approval The SCA confirmation page submitted the OTP to /obp/v3.1.0/banks/{bank}/consents/{id}/challenge, which only transitions consents in status INITIATED -- Berlin Group consents are created in status received, so this endpoint could never succeed regardless of OTP correctness. Call the real BG-native pair instead: start the authorisation in load() (POST .../consents/{id}/authorisations) and submit the answer in the form action (PUT .../consents/{id}/authorisations/{authorisationId}), threading the authorisationId through a hidden form field. --- apps/portal/src/lib/obp/types.ts | 12 ++++ .../+page.server.ts | 56 ++++++++++++++----- .../+page.svelte | 1 + 3 files changed, 54 insertions(+), 15 deletions(-) diff --git a/apps/portal/src/lib/obp/types.ts b/apps/portal/src/lib/obp/types.ts index 62a9e5eb..641c4bba 100644 --- a/apps/portal/src/lib/obp/types.ts +++ b/apps/portal/src/lib/obp/types.ts @@ -312,6 +312,18 @@ export interface OBPBGPaymentAuthorisation { sca_status: string; } +// Berlin Group Consent Authorisation (SCA) types +export interface OBPBGStartConsentAuthorisation { + scaStatus: string; + authorisationId: string; + pushMessage: string; + _links: { scaStatus: string }; +} +export interface OBPBGConsentAuthorisationResult { + scaStatus: string; + _links?: { scaStatus?: { href?: string } }; +} + // Personal Data Field (User Attribute) export interface OBPPersonalDataField { user_attribute_id: string; diff --git a/apps/portal/src/routes/(protected)/confirm-bg-consent-request-sca/+page.server.ts b/apps/portal/src/routes/(protected)/confirm-bg-consent-request-sca/+page.server.ts index 5e74f7b2..f3edb51b 100644 --- a/apps/portal/src/routes/(protected)/confirm-bg-consent-request-sca/+page.server.ts +++ b/apps/portal/src/routes/(protected)/confirm-bg-consent-request-sca/+page.server.ts @@ -4,20 +4,47 @@ import type { RequestEvent, Actions } from '@sveltejs/kit'; import { redirect, isRedirect } from '@sveltejs/kit'; import { obp_requests } from '$lib/obp/requests'; import { OBPRequestError } from '@obp/shared/obp'; -import { env } from '$env/dynamic/private'; +import type { + OBPBGStartConsentAuthorisation, + OBPBGConsentAuthorisationResult +} from '$lib/obp/types'; export async function load(event: RequestEvent) { const consentId = event.url.searchParams.get('CONSENT_ID'); - if (!consentId) { return { loadError: 'Missing required parameter: CONSENT_ID.', consentId: '', - }; + authorisationId: '' + }; + } + + const token = event.locals.session.data.oauth?.access_token; + if (!token) { + return { + loadError: 'No access token found in session.', + consentId, + authorisationId: '' + }; } - return { consentId }; + try { + const startResponse: OBPBGStartConsentAuthorisation = await obp_requests.post( + `/berlin-group/v1.3/consents/${consentId}/authorisations`, + { scaAuthenticationData: '' }, + token + ); + + return { consentId, authorisationId: startResponse.authorisationId }; + } catch (e) { + logger.error('Error starting BG consent authorisation:', e); + let errorMessage = 'Failed to start consent authorisation.'; + if (e instanceof OBPRequestError) { + errorMessage = e.message; + } + return { loadError: errorMessage, consentId, authorisationId: '' }; + } } export const actions = { @@ -25,35 +52,34 @@ export const actions = { const formData = await request.formData(); const otp = formData.get('otp') as string; const consentId = formData.get('consentId') as string; + const authorisationId = formData.get('authorisationId') as string; if (!otp) { return { message: 'Please enter the OTP code.' }; } + if (!authorisationId) { + return { message: 'Missing authorisation id. Please reload the page.' }; + } + const token = locals.session.data.oauth?.access_token; if (!token) { return { message: 'No access token found in session.' }; } - const defaultBankId = env.DEFAULT_BANK_ID; - if (!defaultBankId) { - logger.error('DEFAULT_BANK_ID environment variable is not set'); - return { message: 'Server configuration error: DEFAULT_BANK_ID is not set.' }; - } - try { - const response = await obp_requests.post( - `/obp/v3.1.0/banks/${defaultBankId}/consents/${consentId}/challenge`, - { answer: otp }, + const response: OBPBGConsentAuthorisationResult = await obp_requests.put( + `/berlin-group/v1.3/consents/${consentId}/authorisations/${authorisationId}`, + { scaAuthenticationData: otp }, token ); - if (response.status === 'ACCEPTED' || response.status === 'VALID') { + if (response.scaStatus === 'valid') { redirect(303, `/confirm-bg-consent-request-redirect-uri?CONSENT_ID=${consentId}`); } return { - message: `Challenge was not accepted. Status: ${response.status}` + message: `Challenge was not accepted. Status: ${response.scaStatus}` }; } catch (e) { if (isRedirect(e)) throw e; diff --git a/apps/portal/src/routes/(protected)/confirm-bg-consent-request-sca/+page.svelte b/apps/portal/src/routes/(protected)/confirm-bg-consent-request-sca/+page.svelte index 41d65eb9..2f999ee4 100644 --- a/apps/portal/src/routes/(protected)/confirm-bg-consent-request-sca/+page.svelte +++ b/apps/portal/src/routes/(protected)/confirm-bg-consent-request-sca/+page.svelte @@ -25,6 +25,7 @@
+