diff --git a/mocks/collections.json b/mocks/collections.json
index 3cb07081f7..6fbfe92897 100644
--- a/mocks/collections.json
+++ b/mocks/collections.json
@@ -34,6 +34,7 @@
"get-gpass-stadspas:standard",
"get-gpass-transacties:standard",
"get-gpass-aanbiedingen-transacties:standard",
+ "post-toggle-stadspas:standard",
"get-krefia:standard",
"get-kvk:standard",
"post-loodmetingen-auth:standard",
diff --git a/mocks/fixtures/gpass-pashouders.json b/mocks/fixtures/gpass-pashouders.json
index a8901f7543..e3e60ddeed 100644
--- a/mocks/fixtures/gpass-pashouders.json
+++ b/mocks/fixtures/gpass-pashouders.json
@@ -66,7 +66,7 @@
"categorie": "Minima stadspas",
"categorie_code": "M",
"actief": true,
- "expiry_date": "2024-07-31T21:59:59.000Z",
+ "expiry_date": "2090-07-31T21:59:59.000Z",
"heeft_budget": false,
"vervangen": false,
"securitycode": "012346",
diff --git a/mocks/routes/gpass.js b/mocks/routes/gpass.js
index e1ae6af72b..b57988fb45 100644
--- a/mocks/routes/gpass.js
+++ b/mocks/routes/gpass.js
@@ -1,5 +1,3 @@
-const UID = require('uid-safe');
-
const settings = require('../settings');
const RESPONSES = {
PASHOUDER: require('../fixtures/gpass-pashouders.json'),
@@ -25,15 +23,21 @@ module.exports = [
},
{
id: 'get-gpass-stadspas',
- url: `${settings.MOCK_BASE_PATH}/gpass/rest/sales/v1/pas/*`,
+ url: `${settings.MOCK_BASE_PATH}/gpass/rest/sales/v1/pas/:pasnummer`,
method: 'GET',
variants: [
{
id: 'standard',
- type: 'json',
+ type: 'middleware',
options: {
- status: 200,
- body: RESPONSES.STADSPAS,
+ middleware: (req, res) => {
+ res.send({
+ ...RESPONSES.STADSPAS,
+ pasnummer: req.params.pasnummer,
+ pasnummer_volledig: `volledig.${req.params.pasnummer}`,
+ id: req.params.pasnummer,
+ });
+ },
},
},
],
@@ -68,4 +72,28 @@ module.exports = [
},
],
},
+ {
+ id: 'post-toggle-stadspas',
+ url: `${settings.MOCK_BASE_PATH}/gpass/rest/sales/v1/togglepas/:pasnummer`,
+ method: 'POST',
+ // Add delay to make loading icon visibile in the front end when pressing the block button.
+ delay: 2500,
+ variants: [
+ {
+ id: 'standard',
+ type: 'middleware',
+ options: {
+ middleware: (req, res) => {
+ // return res.status(500).end();
+ res.send({
+ // NOT sure if this is the same response as the real API
+ ...RESPONSES.STADSPAS,
+ pasnummer: req.params.pasnummer,
+ actief: false,
+ });
+ },
+ },
+ },
+ ],
+ },
];
diff --git a/package-lock.json b/package-lock.json
index 722699edf9..a1d9d7e296 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -110,6 +110,7 @@
"sass": "^1.83.0",
"slugme": "^1.1.1",
"supercluster": "^7.1.5",
+ "swr": "^2.3.0",
"thenby": "^1.3.4",
"throttle-debounce": "^5.0.2",
"ts-node": "^10.9.2",
@@ -16877,6 +16878,19 @@
"integrity": "sha512-TBzhheU15s+o54Cgk9qxuYcZMiqSm/SkvKnapoGHOF66kz0Y5aGjpzj5BT/vpBbn6rTPJ9tUYXQxuDWfsjiGMw==",
"dev": true
},
+ "node_modules/swr": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.0.tgz",
+ "integrity": "sha512-NyZ76wA4yElZWBHzSgEJc28a0u6QZvhb6w0azeL2k7+Q1gAzVK+IqQYXhVOC/mzi+HZIozrZvBVeSeOZNR2bqA==",
+ "license": "MIT",
+ "dependencies": {
+ "dequal": "^2.0.3",
+ "use-sync-external-store": "^1.4.0"
+ },
+ "peerDependencies": {
+ "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
@@ -17597,6 +17611,15 @@
"react": "*"
}
},
+ "node_modules/use-sync-external-store": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
+ "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
diff --git a/package.json b/package.json
index 4f55c95b29..3319605ebf 100644
--- a/package.json
+++ b/package.json
@@ -139,6 +139,7 @@
"sass": "^1.83.0",
"slugme": "^1.1.1",
"supercluster": "^7.1.5",
+ "swr": "^2.3.0",
"thenby": "^1.3.4",
"throttle-debounce": "^5.0.2",
"ts-node": "^10.9.2",
diff --git a/src/client/components/Modal/Modal.tsx b/src/client/components/Modal/Modal.tsx
index 1835c80ae4..bc2541ce17 100644
--- a/src/client/components/Modal/Modal.tsx
+++ b/src/client/components/Modal/Modal.tsx
@@ -36,10 +36,7 @@ export function Modal({
return isOpen
? ReactDOM.createPortal(
-
+
- De stadspas heeft een einddatum van 31 juli 2025
+ Voor alle stadspassen geldt de einddatum van 31 juli 2025
{
describe('getThemaTitle', () => {
diff --git a/src/client/pages/HLI/test-helpers.ts b/src/client/pages/HLI/test-helpers.ts
new file mode 100644
index 0000000000..c55ff1391f
--- /dev/null
+++ b/src/client/pages/HLI/test-helpers.ts
@@ -0,0 +1,45 @@
+import {
+ StadspasFrontend,
+ StadspasOwner,
+} from '../../../server/services/hli/stadspas-types';
+
+/** Create a createStadspas function that returns stadspassen with incrementing ID's */
+export function stadspasCreator() {
+ let id = 0;
+
+ function create(
+ firstname: string,
+ actief: boolean,
+ // eslint-disable-next-line no-magic-numbers
+ passNumber: number = 123123123
+ ): StadspasFrontend {
+ id++;
+
+ const owner: StadspasOwner = {
+ firstname,
+ lastname: 'Crepin',
+ initials: 'KC',
+ };
+
+ const passNumberComplete = 'volledig.' + passNumber;
+
+ return {
+ urlTransactions: 'http://example.com/url-transactions',
+ transactionsKeyEncrypted: '123-xxx-000',
+ id: `stadspas-id-${id}`,
+ passNumber,
+ passNumberComplete,
+ owner,
+ dateEnd: '31-07-2025',
+ dateEndFormatted: '31 juli 2025',
+ budgets: [],
+ balanceFormatted: '€5,50',
+ balance: 5.5,
+ blockPassURL: 'http://example.com/stadspas/block',
+ actief,
+ securityCode: '123-securitycode-123',
+ };
+ }
+
+ return create;
+}
diff --git a/src/client/pages/HLI/useHliThemaData.ts b/src/client/pages/HLI/useHliThemaData.ts
index ed48dca8b0..1f4a868408 100644
--- a/src/client/pages/HLI/useHliThemaData.ts
+++ b/src/client/pages/HLI/useHliThemaData.ts
@@ -5,6 +5,7 @@ import {
routes,
tableConfig,
} from './HLI-thema-config';
+import { useStadspassen } from './useStadspassen.hook';
import { HLIRegeling } from '../../../server/services/hli/hli-regelingen-types';
import {
hasFailedDependency,
@@ -16,7 +17,7 @@ import { useAppStateGetter } from '../../hooks/useAppState';
export function useHliThemaData() {
const { HLI } = useAppStateGetter();
- const stadspassen = HLI.content?.stadspas;
+ const stadspassen = useStadspassen();
const hasStadspas = !!HLI.content?.stadspas?.length;
const regelingen = addLinkElementToProperty
(
HLI.content?.regelingen ?? [],
diff --git a/src/client/pages/HLI/useStadspassen.hook.tsx b/src/client/pages/HLI/useStadspassen.hook.tsx
new file mode 100644
index 0000000000..63b1f9e0ba
--- /dev/null
+++ b/src/client/pages/HLI/useStadspassen.hook.tsx
@@ -0,0 +1,63 @@
+import useSWR from 'swr';
+import useSWRMutation from 'swr/mutation';
+
+import { PasblokkadeByPasnummer } from '../../../server/services/hli/stadspas-types';
+import { ApiResponse_DEPRECATED } from '../../../universal/helpers/api';
+import { useAppStateGetter } from '../../hooks/useAppState';
+
+export function useStadspassen() {
+ const { HLI } = useAppStateGetter();
+ const { data: passBlokkadeByPasnummer } = useBlockStadspas();
+ const stadspassen = (HLI.content?.stadspas || []).map((pas) => {
+ const isGeblokkeerd = passBlokkadeByPasnummer?.[pas.passNumber];
+ const stadspas = {
+ ...pas,
+ actief: isGeblokkeerd ?? pas.actief,
+ };
+
+ return stadspas;
+ });
+
+ return stadspassen;
+}
+
+type BlokkeerURL = string;
+
+export function useBlockStadspas() {
+ const { data } = useSWR('pasblokkades');
+ const mutation = useSWRMutation<
+ | PasblokkadeByPasnummer
+ | ApiResponse_DEPRECATED,
+ Error,
+ 'pasblokkades',
+ BlokkeerURL
+ >(
+ 'pasblokkades',
+ async (_key, { arg }) => {
+ const response = await fetch(arg, {
+ credentials: 'include',
+ }).then((response) => response.json());
+
+ if (response.status !== 'OK') {
+ throw new Error(response.message);
+ }
+
+ return response;
+ },
+ {
+ revalidate: false,
+ populateCache: (response, pasBlokkadeByPasnummer) => {
+ const newState = {
+ ...pasBlokkadeByPasnummer,
+ ...('content' in response ? response.content : {}),
+ };
+ return newState;
+ },
+ }
+ );
+
+ return {
+ ...mutation,
+ data,
+ };
+}
diff --git a/src/client/pages/MockApp.tsx b/src/client/pages/MockApp.tsx
index 6d90fad77d..0a3da3d9f7 100644
--- a/src/client/pages/MockApp.tsx
+++ b/src/client/pages/MockApp.tsx
@@ -3,6 +3,9 @@ import { Component } from 'react';
import { MemoryRouter, Route } from 'react-router-dom';
import { MutableSnapshot, RecoilRoot } from 'recoil';
+import { AppState } from '../../universal/types';
+import { appStateAtom } from '../hooks/useAppState';
+
interface MockAppProps {
routePath: string;
routeEntry: string;
@@ -24,3 +27,29 @@ export default function MockApp({
);
}
+
+export function componentCreator(conf: {
+ component: () => JSX.Element;
+ routeEntry: string;
+ routePath: string;
+}) {
+ function createComponent(state: AppState) {
+ function initializeState(snapshot: MutableSnapshot) {
+ snapshot.set(appStateAtom, state);
+ }
+
+ function Component() {
+ return (
+
+ );
+ }
+
+ return Component;
+ }
+ return createComponent;
+}
diff --git a/src/client/pages/Profile/private/useAantalBewonersOpAdres.hook.ts b/src/client/pages/Profile/private/useAantalBewonersOpAdres.hook.ts
index 83313845aa..9038cbd572 100644
--- a/src/client/pages/Profile/private/useAantalBewonersOpAdres.hook.ts
+++ b/src/client/pages/Profile/private/useAantalBewonersOpAdres.hook.ts
@@ -3,7 +3,7 @@ import { useEffect } from 'react';
import { FeatureToggle } from '../../../../universal/config/feature-toggles';
import {
apiPristineResult,
- ApiResponse,
+ ApiResponse_DEPRECATED,
} from '../../../../universal/helpers/api';
import { AppState } from '../../../../universal/types';
import { useDataApi } from '../../../hooks/api/useDataApi';
@@ -12,7 +12,7 @@ export function useAantalBewonersOpAdres(
brpContent: AppState['BRP']['content']
) {
const [{ data: residentData }, fetchResidentCount] = useDataApi<
- ApiResponse<{ residentCount: number }>
+ ApiResponse_DEPRECATED<{ residentCount: number }>
>(
{
url: brpContent?.fetchUrlAantalBewoners ?? '',
diff --git a/src/client/pages/VergunningDetail/DocumentDetails.tsx b/src/client/pages/VergunningDetail/DocumentDetails.tsx
index c49a36b85f..05fe33bb80 100644
--- a/src/client/pages/VergunningDetail/DocumentDetails.tsx
+++ b/src/client/pages/VergunningDetail/DocumentDetails.tsx
@@ -5,7 +5,7 @@ import type {
VergunningDocument,
} from '../../../server/services/vergunningen/vergunningen';
import {
- ApiResponse,
+ ApiResponse_DEPRECATED,
apiPristineResult,
apiSuccessResult,
} from '../../../universal/helpers/api';
@@ -35,7 +35,7 @@ export function DocumentDetails({
isLoading: isLoadingDocuments,
},
fetchDocuments,
- ] = useDataApi>(
+ ] = useDataApi>(
{
postpone: true,
transformResponse: ({ content }) => {
diff --git a/src/server/openapi-amsapp.yml b/src/server/openapi-amsapp.yml
index 3ad25c9c02..0685cc1c2d 100644
--- a/src/server/openapi-amsapp.yml
+++ b/src/server/openapi-amsapp.yml
@@ -188,6 +188,40 @@ paths:
'application/json':
schema:
$ref: '#/components/schemas/ApplicationError'
+ /private/api/v1/services/amsapp/stadspas/block/{transactionsKeyEncrypted}:
+ post:
+ security:
+ - ApiKeyAuth: []
+ description: |
+ Blocks a stadspas with the passNumber supplied in transactionsKeyEncrypted.
+ parameters:
+ - name: transactionsKeyEncrypted
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ '200':
+ description: Blocking stadspas was successful
+ '400':
+ description: Returns bad request error
+ content:
+ 'application/json':
+ schema:
+ $ref: '#/components/schemas/ErrorBadRequest'
+ '401':
+ description: Returns unauthorized error (invalid api key)
+ content:
+ 'application/json':
+ schema:
+ $ref: '#/components/schemas/ErrorUnauthorized'
+ '500':
+ description: |
+ Returns Application error, any error in code or from failed requests.
+ content:
+ 'application/json':
+ schema:
+ $ref: '#/components/schemas/ApplicationError'
components:
securitySchemes:
diff --git a/src/server/routing/bff-routes.ts b/src/server/routing/bff-routes.ts
index d55391ee26..b8eabf6a7e 100644
--- a/src/server/routing/bff-routes.ts
+++ b/src/server/routing/bff-routes.ts
@@ -29,6 +29,7 @@ export const BffEndpoints = {
// Stadspas
STADSPAS_TRANSACTIONS:
'/services/stadspas/transactions/:transactionsKeyEncrypted?',
+ STADSPAS_BLOCK_PASS: '/services/stadspas/block/:transactionsKeyEncrypted',
// Vergunningen V2
VERGUNNINGENv2_ZAKEN_SOURCE: '/services/vergunningen/v2/zaken/:id?',
@@ -75,18 +76,21 @@ export const BffEndpoints = {
LOODMETING_DOCUMENT_DOWNLOAD: '/services/lood/document/:id',
};
+const AMSAPP_BASE = '/services/amsapp';
+
export const ExternalConsumerEndpoints = {
// Publicly accessible
public: {
- STADSPAS_AMSAPP_LOGIN: `/services/amsapp/stadspas/login/:token`,
- STADSPAS_ADMINISTRATIENUMMER: `/services/amsapp/stadspas/administratienummer/:token`,
- STADSPAS_APP_LANDING: `/services/amsapp/stadspas/app-landing`,
+ STADSPAS_AMSAPP_LOGIN: `${AMSAPP_BASE}/stadspas/login/:token`,
+ STADSPAS_ADMINISTRATIENUMMER: `${AMSAPP_BASE}/stadspas/administratienummer/:token`,
+ STADSPAS_APP_LANDING: `${AMSAPP_BASE}/stadspas/app-landing`,
},
// Privately accessible
private: {
- STADSPAS_PASSEN: `${BFF_BASE_PATH_PRIVATE}/services/amsapp/stadspas/passen/:administratienummerEncrypted`,
- STADSPAS_DISCOUNT_TRANSACTIONS: `${BFF_BASE_PATH_PRIVATE}/services/amsapp/stadspas/aanbiedingen/transactions/:transactionsKeyEncrypted`,
- STADSPAS_BUDGET_TRANSACTIONS: `${BFF_BASE_PATH_PRIVATE}/services/amsapp/stadspas/budget/transactions/:transactionsKeyEncrypted`,
+ STADSPAS_PASSEN: `${BFF_BASE_PATH_PRIVATE}${AMSAPP_BASE}/stadspas/passen/:administratienummerEncrypted`,
+ STADSPAS_DISCOUNT_TRANSACTIONS: `${BFF_BASE_PATH_PRIVATE}${AMSAPP_BASE}/stadspas/aanbiedingen/transactions/:transactionsKeyEncrypted`,
+ STADSPAS_BUDGET_TRANSACTIONS: `${BFF_BASE_PATH_PRIVATE}${AMSAPP_BASE}/stadspas/budget/transactions/:transactionsKeyEncrypted`,
+ STADSPAS_BLOCK_PAS: `${BFF_BASE_PATH_PRIVATE}${AMSAPP_BASE}/stadspas/block/:transactionsKeyEncrypted`,
},
};
diff --git a/src/server/routing/route-handlers.ts b/src/server/routing/route-handlers.ts
index c9177bae3c..c33c920276 100644
--- a/src/server/routing/route-handlers.ts
+++ b/src/server/routing/route-handlers.ts
@@ -82,13 +82,13 @@ export function nocache(_req: Request, res: Response, next: NextFunction) {
next();
}
-export function requestID(req: Request, res: Response, next: NextFunction) {
+export function requestID(_req: Request, res: Response, next: NextFunction) {
const REQUEST_ID_BYTE_LENGTH = 18;
res.locals.requestID = uid.sync(REQUEST_ID_BYTE_LENGTH);
next();
}
-export function clearRequestCache(req: Request, res: Response) {
+export function clearRequestCache(_req: Request, res: Response) {
const requestID = res.locals.requestID!;
clearSessionCache(requestID);
}
diff --git a/src/server/routing/route-helpers.test.ts b/src/server/routing/route-helpers.test.ts
index 57847632ee..6e67a0138a 100644
--- a/src/server/routing/route-helpers.test.ts
+++ b/src/server/routing/route-helpers.test.ts
@@ -14,7 +14,10 @@ import {
} from './route-helpers';
import { bffApiHost } from '../../testing/setup';
import { RequestMock, ResponseMock } from '../../testing/utils';
-import { ApiResponse, apiErrorResult } from '../../universal/helpers/api';
+import {
+ ApiResponse_DEPRECATED,
+ apiErrorResult,
+} from '../../universal/helpers/api';
import { oidcConfigDigid, oidcConfigEherkenning } from '../auth/auth-config';
import { cache } from '../helpers/source-api-request';
@@ -44,7 +47,7 @@ describe('route-helpers', () => {
describe('sendResponse tests', async () => {
test('Sends 200 when status OK', () => {
- const response: ApiResponse = {
+ const response: ApiResponse_DEPRECATED = {
status: 'OK',
content: null,
};
diff --git a/src/server/routing/route-helpers.ts b/src/server/routing/route-helpers.ts
index 163875e17d..102be8e5ba 100644
--- a/src/server/routing/route-helpers.ts
+++ b/src/server/routing/route-helpers.ts
@@ -3,7 +3,10 @@ import { generatePath, matchPath } from 'react-router-dom';
import { PUBLIC_BFF_ENDPOINTS } from './bff-routes';
import { HTTP_STATUS_CODES } from '../../universal/constants/errorCodes';
-import { ApiResponse, apiErrorResult } from '../../universal/helpers/api';
+import {
+ ApiResponse_DEPRECATED,
+ apiErrorResult,
+} from '../../universal/helpers/api';
import { BFF_API_BASE_URL } from '../config/app';
/* eslint-disable @typescript-eslint/no-empty-object-type */
@@ -53,7 +56,10 @@ export function generateFullApiUrlBFF(
}
/** Sets the right statuscode and sends a response. */
-export function sendResponse(res: Response, apiResponse: ApiResponse) {
+export function sendResponse(
+ res: Response,
+ apiResponse: ApiResponse_DEPRECATED
+) {
if (apiResponse.status === 'ERROR') {
res.status(
typeof apiResponse.code === 'number'
diff --git a/src/server/routing/router-protected.ts b/src/server/routing/router-protected.ts
index 08ed25c85d..113f0d467f 100644
--- a/src/server/routing/router-protected.ts
+++ b/src/server/routing/router-protected.ts
@@ -26,14 +26,15 @@ import {
loadServicesAll,
loadServicesSSE,
} from '../services/controller';
+import { fetchDecosDocument } from '../services/decos/decos-service';
import {
fetchZorgnedAVDocument,
+ handleBlockStadspas,
handleFetchTransactionsRequest,
} from '../services/hli/hli-route-handlers';
import { attachDocumentDownloadRoute } from '../services/shared/document-download-route-handler';
import { fetchErfpachtV2DossiersDetail } from '../services/simple-connect/erfpacht';
import { fetchBBDocument } from '../services/toeristische-verhuur/toeristische-verhuur-powerbrowser-bb-vergunning';
-import { fetchDecosDocument } from '../services/decos/decos-service';
import {
fetchVergunningDetail,
fetchZakenFromSource,
@@ -240,6 +241,7 @@ attachDocumentDownloadRoute(
// HLI Stadspas transacties
router.get(BffEndpoints.STADSPAS_TRANSACTIONS, handleFetchTransactionsRequest);
+router.get(BffEndpoints.STADSPAS_BLOCK_PASS, handleBlockStadspas);
// HLI Regelingen / doc download
attachDocumentDownloadRoute(
diff --git a/src/server/routing/router-public.ts b/src/server/routing/router-public.ts
index 0fa7524be3..926f119bd6 100644
--- a/src/server/routing/router-public.ts
+++ b/src/server/routing/router-public.ts
@@ -10,10 +10,14 @@ import {
} from '../../universal/config/myarea-datasets';
import { AppRoutes } from '../../universal/config/routes';
import { HTTP_STATUS_CODES } from '../../universal/constants/errorCodes';
-import { ApiResponse, apiSuccessResult } from '../../universal/helpers/api';
+import {
+ ApiResponse_DEPRECATED,
+ apiSuccessResult,
+} from '../../universal/helpers/api';
import { getAuth, getReturnToUrlZaakStatus } from '../auth/auth-helpers';
import { authRoutes } from '../auth/auth-routes';
import { RELEASE_VERSION } from '../config/app';
+import { getFromEnv } from '../helpers/env';
import {
QueryParamsCMSFooter,
fetchCMSCONTENT,
@@ -29,7 +33,6 @@ import {
QueryParamsMaintenanceNotifications,
fetchMaintenanceNotificationsActual,
} from '../services/cms-maintenance-notifications';
-import { getFromEnv } from '../helpers/env';
export const router = express.Router();
@@ -138,7 +141,7 @@ router.get(
const id = req.params.id;
const datasetCategoryId = getDatasetCategoryId(datasetId);
- let response: ApiResponse | null = null;
+ let response: ApiResponse_DEPRECATED | null = null;
try {
if (datasetCategoryId && datasetId && id) {
diff --git a/src/server/routing/router-stadspas-external-consumer.ts b/src/server/routing/router-stadspas-external-consumer.ts
index 83af71057e..799538c451 100644
--- a/src/server/routing/router-stadspas-external-consumer.ts
+++ b/src/server/routing/router-stadspas-external-consumer.ts
@@ -22,6 +22,7 @@ import { getApiConfig } from '../helpers/source-api-helpers';
import { requestData } from '../helpers/source-api-request';
import { fetchAdministratienummer } from '../services/hli/hli-zorgned-service';
import {
+ blockStadspas,
fetchStadspasBudgetTransactions,
fetchStadspasDiscountTransactions,
} from '../services/hli/stadspas';
@@ -29,12 +30,72 @@ import { fetchStadspassenByAdministratienummer } from '../services/hli/stadspas-
import {
StadspasAMSAPPFrontend,
StadspasBudget,
+ TransactionKeysEncryptedWithoutSessionID,
} from '../services/hli/stadspas-types';
import { captureException, captureMessage } from '../services/monitoring';
const AMSAPP_PROTOCOl = 'amsterdam://';
const AMSAPP_STADSPAS_DEEP_LINK = `${AMSAPP_PROTOCOl}stadspas`;
+// PUBLIC INTERNET NETWORK ROUTER
+// ==============================
+export const routerInternet = express.Router();
+routerInternet.BFF_ID = 'external-consumer-public';
+
+routerInternet.get(
+ ExternalConsumerEndpoints.public.STADSPAS_AMSAPP_LOGIN,
+ async (req: Request<{ token: string }>, res: Response) => {
+ return res.redirect(
+ authRoutes.AUTH_LOGIN_DIGID +
+ `?returnTo=${RETURNTO_AMSAPP_STADSPAS_ADMINISTRATIENUMMER}&amsapp-session-token=${req.params.token}`
+ );
+ }
+);
+
+routerInternet.get(
+ ExternalConsumerEndpoints.public.STADSPAS_ADMINISTRATIENUMMER,
+ sendAdministratienummerResponse
+);
+
+routerInternet.get(
+ ExternalConsumerEndpoints.public.STADSPAS_APP_LANDING,
+ sendAppLandingResponse
+);
+
+// PRIVATE NETWORK ROUTER
+// ======================
+export const routerPrivateNetwork = express.Router();
+routerPrivateNetwork.BFF_ID = 'external-consumer-private-network';
+
+export const stadspasExternalConsumerRouter = {
+ internet: routerInternet,
+ privateNetwork: routerPrivateNetwork,
+};
+
+routerPrivateNetwork.get(
+ ExternalConsumerEndpoints.private.STADSPAS_PASSEN,
+ apiKeyVerificationHandler,
+ sendStadspassenResponse
+);
+
+routerPrivateNetwork.get(
+ ExternalConsumerEndpoints.private.STADSPAS_DISCOUNT_TRANSACTIONS,
+ apiKeyVerificationHandler,
+ sendDiscountTransactionsResponse
+);
+
+routerPrivateNetwork.get(
+ ExternalConsumerEndpoints.private.STADSPAS_BUDGET_TRANSACTIONS,
+ apiKeyVerificationHandler,
+ sendBudgetTransactionsResponse
+);
+
+routerPrivateNetwork.post(
+ ExternalConsumerEndpoints.private.STADSPAS_BLOCK_PAS,
+ apiKeyVerificationHandler,
+ sendStadspasBlockRequest
+);
+
type ApiError = {
code: string;
message: string;
@@ -65,22 +126,6 @@ const apiResponseErrors: Record = {
},
} as const;
-export const routerInternet = express.Router();
-routerInternet.BFF_ID = 'external-consumer-public';
-
-export const routerPrivateNetwork = express.Router();
-routerPrivateNetwork.BFF_ID = 'external-consumer-private-network';
-
-routerInternet.get(
- ExternalConsumerEndpoints.public.STADSPAS_AMSAPP_LOGIN,
- async (req: Request<{ token: string }>, res: Response) => {
- return res.redirect(
- authRoutes.AUTH_LOGIN_DIGID +
- `?returnTo=${RETURNTO_AMSAPP_STADSPAS_ADMINISTRATIENUMMER}&amsapp-session-token=${req.params.token}`
- );
- }
-);
-
type RenderProps = {
nonce: string;
promptOpenApp: boolean;
@@ -196,11 +241,6 @@ async function sendAdministratienummerResponse(
return res.render('amsapp-stadspas-administratienummer', renderProps);
}
-routerInternet.get(
- ExternalConsumerEndpoints.public.STADSPAS_ADMINISTRATIENUMMER,
- sendAdministratienummerResponse
-);
-
function sendAppLandingResponse(_req: Request, res: Response) {
const renderProps: RenderProps = {
...baseRenderProps,
@@ -209,11 +249,6 @@ function sendAppLandingResponse(_req: Request, res: Response) {
return res.render('amsapp-stadspas-administratienummer', renderProps);
}
-routerInternet.get(
- ExternalConsumerEndpoints.public.STADSPAS_APP_LANDING,
- sendAppLandingResponse
-);
-
async function sendStadspassenResponse(
req: Request<{ administratienummerEncrypted: string }>,
res: Response
@@ -264,14 +299,12 @@ async function sendStadspassenResponse(
);
}
-routerPrivateNetwork.get(
- ExternalConsumerEndpoints.private.STADSPAS_PASSEN,
- apiKeyVerificationHandler,
- sendStadspassenResponse
-);
+type TransactionKeysEncryptedRequest = Request<{
+ transactionsKeyEncrypted: TransactionKeysEncryptedWithoutSessionID;
+}>;
async function sendDiscountTransactionsResponse(
- req: Request<{ transactionsKeyEncrypted: string }>,
+ req: TransactionKeysEncryptedRequest,
res: Response
) {
const response = await fetchStadspasDiscountTransactions(
@@ -282,12 +315,6 @@ async function sendDiscountTransactionsResponse(
sendResponse(res, response);
}
-routerPrivateNetwork.get(
- ExternalConsumerEndpoints.private.STADSPAS_DISCOUNT_TRANSACTIONS,
- apiKeyVerificationHandler,
- sendDiscountTransactionsResponse
-);
-
/** Sends transformed budget transactions.
*
* # Url Params
@@ -295,7 +322,7 @@ routerPrivateNetwork.get(
* `transactionsKeyEncrypted`: is available in the response of `sendStadspassenResponse`.
*/
async function sendBudgetTransactionsResponse(
- req: Request<{ transactionsKeyEncrypted: string }>,
+ req: TransactionKeysEncryptedRequest,
res: Response
) {
const response = await fetchStadspasBudgetTransactions(
@@ -307,16 +334,16 @@ async function sendBudgetTransactionsResponse(
return sendResponse(res, response);
}
-routerPrivateNetwork.get(
- ExternalConsumerEndpoints.private.STADSPAS_BUDGET_TRANSACTIONS,
- apiKeyVerificationHandler,
- sendBudgetTransactionsResponse
-);
-
-export const stadspasExternalConsumerRouter = {
- internet: routerInternet,
- privateNetwork: routerPrivateNetwork,
-};
+async function sendStadspasBlockRequest(
+ req: TransactionKeysEncryptedRequest,
+ res: Response
+) {
+ const response = await blockStadspas(
+ res.locals.requestID,
+ req.params.transactionsKeyEncrypted
+ );
+ return sendResponse(res, response);
+}
export const forTesting = {
sendAdministratienummerResponse,
diff --git a/src/server/services/afis/afis-business-partner.ts b/src/server/services/afis/afis-business-partner.ts
index 0cf3c2806a..2af224e060 100644
--- a/src/server/services/afis/afis-business-partner.ts
+++ b/src/server/services/afis/afis-business-partner.ts
@@ -14,7 +14,7 @@ import {
import { FeatureToggle } from '../../../universal/config/feature-toggles';
import {
apiErrorResult,
- ApiResponse,
+ ApiResponse_DEPRECATED,
apiSuccessResult,
getFailedDependencies,
getSettledResult,
@@ -54,7 +54,7 @@ function transformBusinessPartnerAddressResponse(
async function fetchBusinessPartnerAddress(
requestID: RequestID,
businessPartnerId: string
-): Promise> {
+): Promise> {
const additionalConfig: DataRequestConfig = {
transformResponse: transformBusinessPartnerAddressResponse,
formatUrl(config) {
@@ -202,8 +202,8 @@ export async function fetchAfisBusinessPartnerDetails(
const fullNameResponse = getSettledResult(fullNameResult);
const addressResponse = getSettledResult(addressResult);
- let phoneResponse: ApiResponse;
- let emailResponse: ApiResponse;
+ let phoneResponse: ApiResponse_DEPRECATED;
+ let emailResponse: ApiResponse_DEPRECATED;
if (addressResponse.status === 'OK' && addressResponse.content) {
const phoneRequest = fetchPhoneNumber(
diff --git a/src/server/services/afis/afis-facturen.ts b/src/server/services/afis/afis-facturen.ts
index 1e8721f22c..18eebab382 100644
--- a/src/server/services/afis/afis-facturen.ts
+++ b/src/server/services/afis/afis-facturen.ts
@@ -6,7 +6,7 @@ import { firstBy } from 'thenby';
import { FeatureToggle } from '../../../universal/config/feature-toggles';
import {
apiErrorResult,
- ApiResponse,
+ ApiResponse_DEPRECATED,
apiSuccessResult,
getFailedDependencies,
getSettledResult,
@@ -141,7 +141,7 @@ function transformDeelbetalingenResponse(
async function fetchAfisFacturenDeelbetalingen(
requestID: RequestID,
params: AfisFacturenParams
-): Promise> {
+): Promise> {
const config = await getAfisApiConfig(
{
formatUrl: ({ url }) => formatFactuurRequestURL(url, params),
@@ -415,7 +415,7 @@ export async function fetchAfisFacturen(
requestID: RequestID,
sessionID: SessionID,
params: AfisFacturenParams
-): Promise> {
+): Promise> {
let deelbetalingen: AfisFactuurDeelbetalingen | undefined;
if (params.state === 'open' || params.state === 'afgehandeld') {
diff --git a/src/server/services/bag.ts b/src/server/services/bag.ts
index e2737eeb77..43f0132123 100644
--- a/src/server/services/bag.ts
+++ b/src/server/services/bag.ts
@@ -1,6 +1,9 @@
import { LatLngLiteral } from 'leaflet';
-import { apiErrorResult, ApiResponse } from '../../universal/helpers/api';
+import {
+ apiErrorResult,
+ ApiResponse_DEPRECATED,
+} from '../../universal/helpers/api';
import { getLatLngCoordinates } from '../../universal/helpers/bag';
import { Adres } from '../../universal/types';
import { BAGQueryParams } from '../../universal/types/bag';
@@ -17,7 +20,7 @@ export interface BAGData {
export async function fetchBAG(
requestID: RequestID,
sourceAddress: Adres | null
-): Promise> {
+): Promise> {
if (!sourceAddress?.straatnaam || !sourceAddress.huisnummer) {
return apiErrorResult('Could not query BAG, no address supplied.', null);
}
diff --git a/src/server/services/brp.ts b/src/server/services/brp.ts
index 537835c342..ff064da06a 100644
--- a/src/server/services/brp.ts
+++ b/src/server/services/brp.ts
@@ -5,7 +5,7 @@ import slug from 'slugme';
import { AppRoutes } from '../../universal/config/routes';
import { Themas } from '../../universal/config/thema';
import {
- ApiResponse,
+ ApiResponse_DEPRECATED,
ApiSuccessResponse,
apiDependencyError,
apiSuccessResult,
@@ -278,7 +278,7 @@ export async function fetchAantalBewoners(
data: {
addressKey: addressKeyEncrypted,
},
- transformResponse: (responseData: ApiResponse) => {
+ transformResponse: (responseData: ApiResponse_DEPRECATED) => {
if (responseData.status === 'OK') {
return responseData.content;
}
diff --git a/src/server/services/buurt/buurt.ts b/src/server/services/buurt/buurt.ts
index 46a0b1c536..d8f9e62d6d 100644
--- a/src/server/services/buurt/buurt.ts
+++ b/src/server/services/buurt/buurt.ts
@@ -6,7 +6,7 @@ import {
POLYLINE_GEOMETRY_TYPES,
} from '../../../universal/config/myarea-datasets';
import {
- ApiResponse,
+ ApiResponse_DEPRECATED,
apiErrorResult,
apiSuccessResult,
} from '../../../universal/helpers/api';
@@ -174,7 +174,7 @@ export async function fetchDataset(
return response;
}
-type ApiDatasetResponse = ApiResponse;
+type ApiDatasetResponse = ApiResponse_DEPRECATED;
export async function loadDatasetFeatures(
requestID: RequestID,
diff --git a/src/server/services/buurt/helpers.test.ts b/src/server/services/buurt/helpers.test.ts
index d52cdb983a..48037224c9 100644
--- a/src/server/services/buurt/helpers.test.ts
+++ b/src/server/services/buurt/helpers.test.ts
@@ -31,7 +31,7 @@ import {
refineFilterSelection,
} from './helpers';
import { remoteApiHost } from '../../../testing/setup';
-import { ApiResponse } from '../../../universal/helpers/api';
+import { ApiResponse_DEPRECATED } from '../../../universal/helpers/api';
const DSO_API_RESULT = {
_links: {
@@ -614,7 +614,7 @@ describe('Buurt helpers', () => {
});
it('Should datasetApiResult', () => {
- const apiResponses: ApiResponse[] = [
+ const apiResponses: ApiResponse_DEPRECATED[] = [
{
status: 'OK',
content: { features: features.slice(0, 2) },
diff --git a/src/server/services/buurt/helpers.ts b/src/server/services/buurt/helpers.ts
index 4faddae81e..57a3039dc2 100644
--- a/src/server/services/buurt/helpers.ts
+++ b/src/server/services/buurt/helpers.ts
@@ -25,7 +25,7 @@ import {
} from '../../../universal/config/myarea-datasets';
import {
ApiErrorResponse,
- ApiResponse,
+ ApiResponse_DEPRECATED,
ApiSuccessResponse,
} from '../../../universal/helpers/api';
import { capitalizeFirstLetter } from '../../../universal/helpers/text';
@@ -432,7 +432,7 @@ export function createFeaturePropertiesFromPropertyFilterConfig(
}
export function datasetApiResult(
- results: ApiResponse[]
+ results: ApiResponse_DEPRECATED[]
) {
const errors = results
.filter(
diff --git a/src/server/services/cms-content.ts b/src/server/services/cms-content.ts
index 912819cfc6..f29d55b63d 100644
--- a/src/server/services/cms-content.ts
+++ b/src/server/services/cms-content.ts
@@ -8,7 +8,7 @@ import sanitizeHtml, { IOptions } from 'sanitize-html';
import { IS_TAP } from '../../universal/config/env';
import {
apiErrorResult,
- ApiResponse,
+ ApiResponse_DEPRECATED,
ApiSuccessResponse,
apiSuccessResult,
getSettledResult,
@@ -190,7 +190,7 @@ async function getGeneralPage(
requestID: RequestID,
profileType: ProfileType = 'private',
forceRenew: boolean = false
-): Promise> {
+): Promise> {
const apiData = fileCache.getKey>(
'CMS_CONTENT_GENERAL_INFO_' + profileType
);
@@ -246,10 +246,11 @@ async function getGeneralPage(
async function getFooter(
requestID: RequestID,
forceRenew: boolean = false
-): Promise> {
+): Promise> {
const apiData =
- fileCache.getKey>('CMS_CONTENT_FOOTER') ??
- null;
+ fileCache.getKey>(
+ 'CMS_CONTENT_FOOTER'
+ ) ?? null;
if (apiData && !forceRenew) {
return Promise.resolve(apiData);
@@ -268,7 +269,7 @@ async function getFooter(
}
// Try to get stale cache instead.
const staleApiData =
- fileCache.getKeyStale>(
+ fileCache.getKeyStale>(
'CMS_CONTENT_FOOTER'
);
@@ -299,7 +300,7 @@ async function fetchCmsBase(
const footerInfoPageRequest = getFooter(requestID, forceRenew);
const requests: Promise<
- ApiResponse
+ ApiResponse_DEPRECATED
>[] = [generalInfoPageRequest, footerInfoPageRequest];
const [generalInfo, footer] = await Promise.allSettled(requests);
diff --git a/src/server/services/cms-maintenance-notifications.ts b/src/server/services/cms-maintenance-notifications.ts
index bb7b446648..10c462557b 100644
--- a/src/server/services/cms-maintenance-notifications.ts
+++ b/src/server/services/cms-maintenance-notifications.ts
@@ -4,7 +4,7 @@ import { marked } from 'marked';
import { IS_TAP } from '../../universal/config/env';
import { Themas } from '../../universal/config/thema';
import {
- ApiResponse,
+ ApiResponse_DEPRECATED,
ApiSuccessResponse,
apiSuccessResult,
} from '../../universal/helpers/api';
@@ -124,7 +124,7 @@ function transformCMSEventResponse(
async function fetchCMSMaintenanceNotifications(
requestID: RequestID,
useCache: boolean = true
-): Promise> {
+): Promise> {
const cachedData = fileCache.getKey<
ApiSuccessResponse
>('CMS_MAINTENANCE_NOTIFICATIONS');
diff --git a/src/server/services/controller.ts b/src/server/services/controller.ts
index 25143d6b2f..935e3fc0fd 100644
--- a/src/server/services/controller.ts
+++ b/src/server/services/controller.ts
@@ -4,7 +4,7 @@ import { streamEndpointQueryParamKeys } from '../../universal/config/app';
import { FeatureToggle } from '../../universal/config/feature-toggles';
import {
apiErrorResult,
- ApiResponse,
+ ApiResponse_DEPRECATED,
apiSuccessResult,
getSettledResult,
} from '../../universal/helpers/api';
@@ -94,7 +94,7 @@ function getServiceTipsMap(profileType: ProfileType) {
}
export function addServiceResultHandler<
- T extends Promise>>,
+ T extends Promise>>,
>(res: Response, servicePromise: T, serviceName: string) {
if (IS_DEBUG) {
// eslint-disable-next-line no-console
diff --git a/src/server/services/decos/decos-service.ts b/src/server/services/decos/decos-service.ts
index bd22f1cb56..0c5884b149 100644
--- a/src/server/services/decos/decos-service.ts
+++ b/src/server/services/decos/decos-service.ts
@@ -28,7 +28,7 @@ import {
} from './helpers';
import {
ApiErrorResponse,
- ApiResponse,
+ ApiResponse_DEPRECATED,
ApiSuccessResponse,
apiSuccessResult,
getSettledResult,
@@ -447,7 +447,9 @@ export async function fetchDecosWorkflowDates(
requestID: RequestID,
zaakID: DecosZaakBase['key'],
stepTitles: DecosWorkflowStepTitle[]
-): Promise | null>> {
+): Promise<
+ ApiResponse_DEPRECATED | null>
+> {
const apiConfigWorkflows = getApiConfig('DECOS_API', {
formatUrl: (config) => {
return `${config.url}/items/${zaakID}/workflows`;
diff --git a/src/server/services/decos/decos-types.ts b/src/server/services/decos/decos-types.ts
index 14b259ea32..5771b06178 100644
--- a/src/server/services/decos/decos-types.ts
+++ b/src/server/services/decos/decos-types.ts
@@ -1,4 +1,4 @@
-import { ApiResponse } from '../../../universal/helpers/api';
+import { ApiResponse_DEPRECATED } from '../../../universal/helpers/api';
import { SomeOtherString } from '../../../universal/helpers/types';
import { GenericDocument } from '../../../universal/types';
import { DecosCaseType } from '../../../universal/types/vergunningen';
@@ -124,7 +124,7 @@ export type DecosTransformerOptions = {
decosZaakTransformer?: DecosZaakTransformer;
fetchDecosWorkflowDates?: (
stepTitles: DecosWorkflowStepTitle[]
- ) => Promise | null>>;
+ ) => Promise | null>>;
};
export type DecosZaakTransformer = {
diff --git a/src/server/services/hli/hli-route-handlers.ts b/src/server/services/hli/hli-route-handlers.ts
index 28b465c536..44be37f744 100644
--- a/src/server/services/hli/hli-route-handlers.ts
+++ b/src/server/services/hli/hli-route-handlers.ts
@@ -1,16 +1,18 @@
import { Request, Response } from 'express';
-import { fetchStadspasBudgetTransactions } from './stadspas';
+import { blockStadspas, fetchStadspasBudgetTransactions } from './stadspas';
import { StadspasBudget, StadspasFrontend } from './stadspas-types';
import { getAuth } from '../../auth/auth-helpers';
import { AuthProfileAndToken } from '../../auth/auth-types';
import { sendResponse, sendUnauthorized } from '../../routing/route-helpers';
import { fetchDocument } from '../zorgned/zorgned-service';
+type TransactionKeysEncryptedRequest = Request<{
+ transactionsKeyEncrypted: StadspasFrontend['transactionsKeyEncrypted'];
+}>;
+
export async function handleFetchTransactionsRequest(
- req: Request<{
- transactionsKeyEncrypted: StadspasFrontend['transactionsKeyEncrypted'];
- }>,
+ req: TransactionKeysEncryptedRequest,
res: Response
) {
const authProfileAndToken = getAuth(req);
@@ -40,3 +42,22 @@ export async function fetchZorgnedAVDocument(
);
return response;
}
+
+export async function handleBlockStadspas(
+ req: TransactionKeysEncryptedRequest,
+ res: Response
+) {
+ const authProfileAndToken = getAuth(req);
+
+ if (!authProfileAndToken) {
+ return sendUnauthorized(res);
+ }
+
+ const response = await blockStadspas(
+ res.locals.requestID,
+ req.params.transactionsKeyEncrypted,
+ authProfileAndToken.profile.sid
+ );
+
+ return sendResponse(res, response);
+}
diff --git a/src/server/services/hli/stadspas-config-and-content.ts b/src/server/services/hli/stadspas-config-and-content.ts
index 6a172a7303..f259243619 100644
--- a/src/server/services/hli/stadspas-config-and-content.ts
+++ b/src/server/services/hli/stadspas-config-and-content.ts
@@ -26,7 +26,7 @@ export function getBudgetNotifications(stadspassen: StadspasFrontend[]) {
const createNotificationBudget = (
description: string,
- stadspasId?: string
+ stadspasPassNumber?: string
) => ({
id: `stadspas-budget-notification`,
datePublished: dateFormat(new Date(), 'yyyy-MM-dd'),
@@ -36,8 +36,10 @@ export function getBudgetNotifications(stadspassen: StadspasFrontend[]) {
)}!`,
description,
link: {
- to: stadspasId
- ? generatePath(AppRoutes['HLI/STADSPAS'], { id: stadspasId })
+ to: stadspasPassNumber
+ ? generatePath(AppRoutes['HLI/STADSPAS'], {
+ passNumber: stadspasPassNumber,
+ })
: AppRoutes.HLI,
title: 'Check het saldo',
},
@@ -64,7 +66,10 @@ export function getBudgetNotifications(stadspassen: StadspasFrontend[]) {
) {
const notification = isParent
? createNotificationBudget(BUDGET_NOTIFICATION_PARENT)
- : createNotificationBudget(BUDGET_NOTIFICATION_CHILD, stadspas?.id);
+ : createNotificationBudget(
+ BUDGET_NOTIFICATION_CHILD,
+ stadspas?.passNumber.toString()
+ );
notifications.push(notification);
}
diff --git a/src/server/services/hli/stadspas-gpass-service.test.ts b/src/server/services/hli/stadspas-gpass-service.test.ts
index 500c747b85..53e7a19283 100644
--- a/src/server/services/hli/stadspas-gpass-service.test.ts
+++ b/src/server/services/hli/stadspas-gpass-service.test.ts
@@ -1,9 +1,11 @@
import { describe, expect, Mock } from 'vitest';
+import { blockStadspas } from './stadspas';
import { GPASS_API_TOKEN } from './stadspas-config-and-content';
import {
fetchGpassDiscountTransactions,
forTesting,
+ mutateGpassBlockPass,
} from './stadspas-gpass-service';
import { fetchStadspassenByAdministratienummer } from './stadspas-gpass-service';
import {
@@ -15,10 +17,18 @@ import {
StadspasHouderSource,
StadspasTransactiesResponseSource,
} from './stadspas-types';
+import { remoteApi } from '../../../testing/utils';
import { HTTP_STATUS_CODES } from '../../../universal/constants/errorCodes';
-import { apiSuccessResult } from '../../../universal/helpers/api';
+import {
+ ApiErrorResponse,
+ apiSuccessResult,
+} from '../../../universal/helpers/api';
import { getApiConfig } from '../../helpers/source-api-helpers';
import { requestData } from '../../helpers/source-api-request';
+import { BffEndpoints } from '../../routing/bff-routes';
+
+vi.mock('../../helpers/source-api-request');
+vi.mock('../../helpers/source-api-helpers');
describe('stadspas-gpass-service', () => {
describe('getHeaders', () => {
@@ -144,6 +154,7 @@ describe('stadspas-gpass-service', () => {
);
expect(transformedResponse).toStrictEqual({
id: '1',
+ actief: false,
owner: {
firstname: 'John',
lastname: 'Doe',
@@ -452,8 +463,6 @@ describe('stadspas-gpass-service', () => {
]);
});
});
- vi.mock('../../helpers/source-api-request');
- vi.mock('../../helpers/source-api-helpers');
describe('fetchStadspassenByAdministratienummer', () => {
const requestID = 'test-request-id';
@@ -500,7 +509,7 @@ describe('stadspas-gpass-service', () => {
});
test('should return transformed stadspassen if stadspasHouderResponse status is OK', async () => {
- const pashouder: StadspasHouderSource = {
+ const pashouder = {
voornaam: 'John',
achternaam: 'Doe',
tussenvoegsel: 'van',
@@ -605,6 +614,7 @@ describe('stadspas-gpass-service', () => {
infix: 'van',
initials: 'J.D.',
},
+ actief: false,
dateEnd: '2023-12-31',
dateEndFormatted: '31 december 2023',
budgets: [
@@ -758,4 +768,75 @@ describe('stadspas-gpass-service', () => {
expect(result).toStrictEqual(errorResponse);
});
});
+
+ describe('blockStadspass', async () => {
+ const PassBlockedSuccessfulResponse = {
+ content: null,
+ status: 'OK',
+ };
+
+ const requestId = '123';
+ const transactionKeysEncrypted = '123';
+ const passNumber = 123;
+
+ test('Uses decrypt and fetcher', async () => {
+ remoteApi.post(
+ BffEndpoints.STADSPAS_BLOCK_PASS,
+ PassBlockedSuccessfulResponse
+ );
+
+ const response = (await blockStadspas(
+ requestId,
+ // This cannot be decrypted so we expect an error response.
+ transactionKeysEncrypted
+ )) as ApiErrorResponse;
+ expect(response.message).toContain('Failed to decrypt');
+ });
+
+ test('Will block a pass that is active', async () => {
+ remoteApi
+ .get(`/stadspas/rest/sales/v1/pas/${passNumber}?include_balance=true`)
+ .reply(200, { actief: false });
+ (requestData as Mock).mockResolvedValueOnce({
+ status: 'OK',
+ content: { actief: true },
+ });
+
+ remoteApi.post(
+ BffEndpoints.STADSPAS_BLOCK_PASS,
+ PassBlockedSuccessfulResponse
+ );
+ (requestData as Mock).mockResolvedValueOnce({
+ status: 'OK',
+ content: null,
+ });
+
+ const response = await mutateGpassBlockPass(requestId, 123, '123');
+ expect(response).toStrictEqual({ status: 'OK', content: null });
+ });
+
+ test('Can only block and not toggle the stadspas', async () => {
+ remoteApi
+ .get(`/stadspas/rest/sales/v1/pas/${passNumber}?include_balance=true`)
+ .reply(200, { actief: false });
+ (requestData as Mock).mockResolvedValueOnce({
+ status: 'OK',
+ content: { actief: false },
+ });
+
+ remoteApi.post(
+ BffEndpoints.STADSPAS_BLOCK_PASS,
+ PassBlockedSuccessfulResponse
+ );
+
+ const response = await mutateGpassBlockPass(requestId, 123, '123');
+ expect(response).toStrictEqual({
+ code: 403,
+ content: null,
+ message:
+ 'The citypass is not active. We cannot unblock an active pass.',
+ status: 'ERROR',
+ });
+ });
+ });
});
diff --git a/src/server/services/hli/stadspas-gpass-service.ts b/src/server/services/hli/stadspas-gpass-service.ts
index f2c1c92e47..b1754df34e 100644
--- a/src/server/services/hli/stadspas-gpass-service.ts
+++ b/src/server/services/hli/stadspas-gpass-service.ts
@@ -1,3 +1,5 @@
+import { HttpStatusCode } from 'axios';
+import { isPast } from 'date-fns';
import memoizee from 'memoizee';
import { fetchAdministratienummer } from './hli-zorgned-service';
@@ -9,19 +11,24 @@ import {
StadspasBudget,
StadspasBudgetTransaction,
StadspasDetailBudgetSource,
- StadspasDetailSource,
StadspasDiscountTransaction,
StadspasDiscountTransactions,
StadspasDiscountTransactionsResponseSource,
StadspasHouderSource,
StadspasOwner,
StadspasPasHouderResponse,
+ StadspasDetailSource,
StadspasTransactieSource,
StadspasTransactiesResponseSource,
StadspasTransactionQueryParams,
+ PasblokkadeByPasnummer,
} from './stadspas-types';
import { HTTP_STATUS_CODES } from '../../../universal/constants/errorCodes';
import {
+ apiErrorResult,
+ ApiResponse_DEPRECATED,
+ ApiResponse,
+ ApiSuccessResponse,
apiSuccessResult,
getSettledResult,
} from '../../../universal/helpers/api';
@@ -99,6 +106,7 @@ function transformStadspasResponse(
balanceFormatted: `€${displayAmount(balance)}`,
passNumber: gpassStadspasResonseData.pasnummer,
passNumberComplete: gpassStadspasResonseData.pasnummer_volledig,
+ actief: gpassStadspasResonseData.actief,
securityCode,
};
@@ -108,6 +116,21 @@ function transformStadspasResponse(
return gpassStadspasResonseData;
}
+export async function fetchStadspasSource(
+ requestID: RequestID,
+ passNumber: number,
+ administratienummer: string
+): Promise> {
+ const dataRequestConfig = getApiConfig('GPASS', {
+ formatUrl: ({ url }) => `${url}/rest/sales/v1/pas/${passNumber}`,
+ headers: getHeaders(administratienummer),
+ params: {
+ include_balance: true,
+ },
+ });
+ return requestData(dataRequestConfig, requestID);
+}
+
export async function fetchStadspassenByAdministratienummer(
requestID: RequestID,
administratienummer: string
@@ -115,7 +138,6 @@ export async function fetchStadspassenByAdministratienummer(
const dataRequestConfig = getApiConfig('GPASS');
const GPASS_ENDPOINT_PASHOUDER = `${dataRequestConfig.url}/rest/sales/v1/pashouder`;
- const GPASS_ENDPOINT_PAS = `${dataRequestConfig.url}/rest/sales/v1/pas`;
const headers = getHeaders(administratienummer);
const stadspasHouderResponse = await requestData(
@@ -150,22 +172,32 @@ export async function fetchStadspassenByAdministratienummer(
const pasRequests = [];
for (const pashouder of pashouders) {
- for (const pas of pashouder.passen.filter((pas) => pas.actief)) {
- const url = `${GPASS_ENDPOINT_PAS}/${pas.pasnummer}`;
- const request = requestData(
- {
- ...dataRequestConfig,
- url,
- transformResponse: (stadspas) =>
- transformStadspasResponse(stadspas, pashouder, pas.securitycode),
- headers,
- params: {
- include_balance: true,
- },
- },
- requestID
- );
- pasRequests.push(request);
+ const passen = pashouder.passen.filter(
+ (pas) => pas.actief || !isPast(new Date(pas.expiry_date))
+ );
+ for (const pas of passen) {
+ const response = fetchStadspasSource(
+ requestID,
+ pas.pasnummer,
+ administratienummer
+ ).then((response) => {
+ if (response.content && response.status === 'OK') {
+ const pasTransformed = transformStadspasResponse(
+ response.content,
+ pashouder,
+ pas.securitycode
+ );
+ const stadspas: ApiSuccessResponse = {
+ ...response,
+ content: pasTransformed,
+ };
+
+ return stadspas;
+ }
+ return response;
+ });
+
+ pasRequests.push(response);
}
}
@@ -319,6 +351,45 @@ export async function fetchGpassDiscountTransactions(
);
}
+export async function mutateGpassBlockPass(
+ requestID: RequestID,
+ passNumber: number,
+ administratienummer: string
+): Promise> {
+ const passResponse = await fetchStadspasSource(
+ requestID,
+ passNumber,
+ administratienummer
+ );
+ if (passResponse.status !== 'OK') {
+ return passResponse;
+ }
+ // This may not give unexpected results so we do extra typechecking on the source input.
+ if (
+ typeof passResponse.content?.actief !== 'boolean' ||
+ !passResponse.content.actief
+ ) {
+ return apiErrorResult(
+ 'The citypass is not active. We cannot unblock an active pass.',
+ null,
+ HttpStatusCode.Forbidden
+ );
+ }
+
+ const config = getApiConfig('GPASS', {
+ method: 'POST',
+ formatUrl: ({ url }) => `${url}/rest/sales/v1/togglepas/${passNumber}`,
+ transformResponse: (pas: StadspasDetailSource) => {
+ if (pas.actief) {
+ throw Error('City pass is still active after trying to block it.');
+ }
+ return { [pas.pasnummer]: pas.actief };
+ },
+ });
+
+ return requestData(config, requestID);
+}
+
export const forTesting = {
transformTransactions,
transformGpassAanbiedingenResponse,
diff --git a/src/server/services/hli/stadspas-types.ts b/src/server/services/hli/stadspas-types.ts
index eb0d1839e6..c161a7fadb 100644
--- a/src/server/services/hli/stadspas-types.ts
+++ b/src/server/services/hli/stadspas-types.ts
@@ -85,7 +85,7 @@ export type SecurityCode = string;
export interface StadspasHouderPasSource {
actief: boolean;
- budgetten: unknown[]; // Did not see the exact shape of this data, encountered an empty array.
+ budgetten: unknown[]; // Did not see the exact shape of this data, encountered an empty array.
categorie: string;
categorie_code: string;
expiry_date: string;
@@ -93,7 +93,7 @@ export interface StadspasHouderPasSource {
id: number;
pasnummer: number;
pasnummer_volledig: string;
- passoort: { id: number, naam: string };
+ passoort: { id: number; naam: string };
securitycode: SecurityCode;
vervangen: boolean;
}
@@ -173,17 +173,23 @@ export interface Stadspas {
budgets: StadspasBudget[];
balanceFormatted: string;
balance: number;
+ actief: boolean;
securityCode: SecurityCode;
}
+export type TransactionKeysEncrypted = string;
+
export interface StadspasFrontend extends Stadspas {
urlTransactions: string;
transactionsKeyEncrypted: string;
+ blockPassURL: string | null;
link?: LinkProps;
}
+export type TransactionKeysEncryptedWithoutSessionID = string;
+
export interface StadspasAMSAPPFrontend extends Stadspas {
- transactionsKeyEncrypted: string;
+ transactionsKeyEncrypted: TransactionKeysEncryptedWithoutSessionID;
}
export interface StadspasTransactionQueryParams {
@@ -222,3 +228,8 @@ export interface StadspasDiscountTransaction {
}
export type StadspasAdministratieNummer = string;
+
+export type PasblokkadeByPasnummer = Record<
+ StadspasFrontend['passNumber'],
+ boolean
+>;
diff --git a/src/server/services/hli/stadspas.test.ts b/src/server/services/hli/stadspas.test.ts
index 57a913b09f..091febfb19 100644
--- a/src/server/services/hli/stadspas.test.ts
+++ b/src/server/services/hli/stadspas.test.ts
@@ -5,6 +5,7 @@ import {
fetchStadspassen,
} from './stadspas-gpass-service';
import {
+ Stadspas,
StadspasDiscountTransactions,
StadspasDiscountTransactionsResponseSource,
} from './stadspas-types';
@@ -38,6 +39,7 @@ function createStadspasHouderResponse() {
function createPas(
actief: boolean,
+ // eslint-disable-next-line no-magic-numbers
pasnummer: number = 777777777777,
securitycode: string = '012345'
) {
@@ -74,8 +76,9 @@ function createPas(
};
}
-function createTransformedPas(firstname: string, initials: string) {
+function createTransformedPas(firstname: string, initials: string): Stadspas {
return {
+ actief: true,
balance: 0,
balanceFormatted: '€0,00',
budgets: [
diff --git a/src/server/services/hli/stadspas.ts b/src/server/services/hli/stadspas.ts
index a92442d615..b3b8db001a 100644
--- a/src/server/services/hli/stadspas.ts
+++ b/src/server/services/hli/stadspas.ts
@@ -2,6 +2,7 @@ import { generatePath } from 'react-router-dom';
import { getBudgetNotifications } from './stadspas-config-and-content';
import {
+ mutateGpassBlockPass,
fetchGpassBudgetTransactions,
fetchGpassDiscountTransactions,
fetchStadspassen,
@@ -15,6 +16,7 @@ import { AppRoutes } from '../../../universal/config/routes';
import { HTTP_STATUS_CODES } from '../../../universal/constants/errorCodes';
import {
apiErrorResult,
+ ApiResponse_DEPRECATED,
apiSuccessResult,
} from '../../../universal/helpers/api';
import { AuthProfileAndToken } from '../../auth/auth-types';
@@ -26,43 +28,52 @@ import { captureException } from '../monitoring';
export async function fetchStadspas(
requestID: RequestID,
authProfileAndToken: AuthProfileAndToken
-) {
+): Promise> {
const stadspasResponse = await fetchStadspassen(
requestID,
authProfileAndToken
);
- if (stadspasResponse.status === 'OK') {
- const stadspassen: StadspasFrontend[] =
- stadspasResponse.content.stadspassen.map((stadspas) => {
- const [transactionsKeyEncrypted] = encrypt(
- `${authProfileAndToken.profile.sid}:${stadspasResponse.content.administratienummer}:${stadspas.passNumber}`
- );
-
- const urlTransactions = generateFullApiUrlBFF(
- BffEndpoints.STADSPAS_TRANSACTIONS,
- {
- transactionsKeyEncrypted,
- }
- );
-
- return {
- ...stadspas,
- urlTransactions,
- transactionsKeyEncrypted,
- link: {
- to: generatePath(AppRoutes['HLI/STADSPAS'], {
- id: stadspas.id,
- }),
- title: `Stadspas van ${stadspas.owner.firstname}`,
- },
- };
- });
-
- return apiSuccessResult(stadspassen);
+ if (stadspasResponse.status !== 'OK') {
+ return stadspasResponse;
}
- return stadspasResponse;
+ const stadspassen: StadspasFrontend[] =
+ stadspasResponse.content.stadspassen.map((stadspas) => {
+ const [transactionsKeyEncrypted] = encrypt(
+ `${authProfileAndToken.profile.sid}:${stadspasResponse.content.administratienummer}:${stadspas.passNumber}`
+ );
+
+ const urlTransactions = generateFullApiUrlBFF(
+ BffEndpoints.STADSPAS_TRANSACTIONS,
+ {
+ transactionsKeyEncrypted,
+ }
+ );
+
+ let blockPassURL = null;
+ if (stadspas.actief) {
+ blockPassURL = generateFullApiUrlBFF(BffEndpoints.STADSPAS_BLOCK_PASS, {
+ transactionsKeyEncrypted,
+ });
+ }
+
+ const stadspasFrontend: StadspasFrontend = {
+ ...stadspas,
+ urlTransactions,
+ transactionsKeyEncrypted,
+ blockPassURL,
+ link: {
+ to: generatePath(AppRoutes['HLI/STADSPAS'], {
+ passNumber: stadspas.passNumber,
+ }),
+ title: `Stadspas van ${stadspas.owner.firstname}`,
+ },
+ };
+ return stadspasFrontend;
+ });
+
+ return apiSuccessResult(stadspassen);
}
async function decryptEncryptedRouteParamAndValidateSessionIDStadspasTransactionsKey(
@@ -112,18 +123,18 @@ async function decryptEncryptedRouteParamAndValidateSessionIDStadspasTransaction
});
}
-async function decryptAndFetch(
+export async function stadspasDecryptAndFetch(
fetchTransactionFn: (
administratienummer: StadspasAdministratieNummer,
pasnummer: StadspasFrontend['passNumber']
) => T,
transactionsKeyEncrypted: string,
- verifySessionId?: AuthProfileAndToken['profile']['sid']
+ sessionId?: AuthProfileAndToken['profile']['sid']
) {
const decryptResult =
await decryptEncryptedRouteParamAndValidateSessionIDStadspasTransactionsKey(
transactionsKeyEncrypted,
- verifySessionId
+ sessionId
);
if (decryptResult.status === 'OK') {
@@ -140,7 +151,7 @@ export async function fetchStadspasDiscountTransactions(
requestID: RequestID,
transactionsKeyEncrypted: StadspasFrontend['transactionsKeyEncrypted']
) {
- return decryptAndFetch(
+ return stadspasDecryptAndFetch(
(administratienummer, pasnummer) =>
fetchGpassDiscountTransactions(requestID, administratienummer, pasnummer),
transactionsKeyEncrypted
@@ -153,7 +164,7 @@ export async function fetchStadspasBudgetTransactions(
budgetCode?: StadspasBudget['code'],
verifySessionId?: AuthProfileAndToken['profile']['sid']
) {
- return decryptAndFetch(
+ return stadspasDecryptAndFetch(
(administratienummer, pasnummer) =>
fetchGpassBudgetTransactions(
requestID,
@@ -166,6 +177,25 @@ export async function fetchStadspasBudgetTransactions(
);
}
+/** Block a stadspas with it's passNumber.
+ *
+ * The passNumber is encrypted inside the transactionsKeyEncrypted.
+ * The endpoint in use can also unblock cards, but we prevent this so its block only.
+ */
+export async function blockStadspas(
+ requestID: RequestID,
+ transactionsKeyEncrypted: string,
+ verifySessionId?: AuthProfileAndToken['profile']['sid']
+) {
+ return stadspasDecryptAndFetch(
+ (administratienummer, pasnummer) => {
+ return mutateGpassBlockPass(requestID, pasnummer, administratienummer);
+ },
+ transactionsKeyEncrypted,
+ verifySessionId
+ );
+}
+
export async function fetchStadspasNotifications(
requestID: RequestID,
authProfileAndToken: AuthProfileAndToken
@@ -179,5 +209,5 @@ export async function fetchStadspasNotifications(
export const forTesting = {
decryptEncryptedRouteParamAndValidateSessionIDStadspasTransactionsKey,
- decryptAndFetch,
+ decryptAndFetch: stadspasDecryptAndFetch,
};
diff --git a/src/server/services/my-locations.ts b/src/server/services/my-locations.ts
index c037dd82c6..225462a488 100644
--- a/src/server/services/my-locations.ts
+++ b/src/server/services/my-locations.ts
@@ -6,7 +6,7 @@ import {
DEFAULT_LNG,
} from '../../universal/config/myarea-datasets';
import {
- ApiResponse,
+ ApiResponse_DEPRECATED,
apiDependencyError,
apiErrorResult,
apiSuccessResult,
@@ -18,7 +18,7 @@ import { AuthProfileAndToken } from '../auth/auth-types';
async function fetchPrivate(
requestID: RequestID,
authProfileAndToken: AuthProfileAndToken
-): Promise> {
+): Promise> {
const BRP = await fetchBRP(requestID, authProfileAndToken);
if (BRP.status === 'OK') {
@@ -59,10 +59,10 @@ async function fetchPrivate(
async function fetchCommercial(
requestID: RequestID,
authProfileAndToken: AuthProfileAndToken
-): Promise> {
+): Promise> {
const KVK = await fetchKVK(requestID, authProfileAndToken);
- let MY_LOCATION: ApiResponse;
+ let MY_LOCATION: ApiResponse_DEPRECATED;
if (KVK.status === 'OK') {
const addresses: Adres[] = getKvkAddresses(KVK.content);
@@ -99,7 +99,7 @@ async function fetchCommercial(
export async function fetchMyLocation(
requestID: RequestID,
authProfileAndToken: AuthProfileAndToken
-): Promise> {
+): Promise> {
const commercialResponse = await fetchCommercial(
requestID,
authProfileAndToken
diff --git a/src/server/services/simple-connect/api-service.ts b/src/server/services/simple-connect/api-service.ts
index f89024541f..d99503586c 100644
--- a/src/server/services/simple-connect/api-service.ts
+++ b/src/server/services/simple-connect/api-service.ts
@@ -1,7 +1,10 @@
import { AxiosResponseTransformer } from 'axios';
import { Thema } from '../../../universal/config/thema';
-import { ApiResponse, apiSuccessResult } from '../../../universal/helpers/api';
+import {
+ ApiResponse_DEPRECATED,
+ apiSuccessResult,
+} from '../../../universal/helpers/api';
import { omit } from '../../../universal/helpers/utils';
import { MyNotification, MyTip } from '../../../universal/types';
import { AuthProfileAndToken } from '../../auth/auth-types';
@@ -15,7 +18,7 @@ export interface ApiPatternResponseA {
}
const transformApiResponseDefault: AxiosResponseTransformer = (
- response: ApiResponse | ApiPatternResponseA
+ response: ApiResponse_DEPRECATED | ApiPatternResponseA
) => {
if (
response !== null &&
@@ -33,7 +36,7 @@ export async function fetchService(
apiConfig: DataRequestConfig = {},
includeTipsAndNotifications: boolean = false,
authProfileAndToken?: AuthProfileAndToken
-): Promise> {
+): Promise> {
const transformResponse = [transformApiResponseDefault].concat(
apiConfig.transformResponse ?? []
);
@@ -87,7 +90,10 @@ export async function fetchTipsAndNotifications(
thema: Thema,
authProfileAndToken?: AuthProfileAndToken
): Promise<
- ApiResponse | null>
+ ApiResponse_DEPRECATED | null>
> {
const response = await fetchService(
requestID,
diff --git a/src/server/services/tips-and-notifications.ts b/src/server/services/tips-and-notifications.ts
index 8dd9a9db8c..4d4d5c42a6 100644
--- a/src/server/services/tips-and-notifications.ts
+++ b/src/server/services/tips-and-notifications.ts
@@ -3,7 +3,10 @@ import memoize from 'memoizee';
import { fetchAdoptableTrashContainers } from './adoptable-trash-containers';
import { FeatureToggle } from '../../universal/config/feature-toggles';
-import { ApiResponse, getSettledResult } from '../../universal/helpers/api';
+import {
+ ApiResponse_DEPRECATED,
+ getSettledResult,
+} from '../../universal/helpers/api';
import { dateSort } from '../../universal/helpers/date';
import type { MyNotification, MyTip } from '../../universal/types';
import { AuthProfileAndToken } from '../auth/auth-types';
@@ -89,7 +92,7 @@ export function sortNotifications(
}
export function getTipsAndNotificationsFromApiResults(
- responses: Array>
+ responses: Array>
): MyNotification[] {
const notifications: MyNotification[] = [];
const tips: MyTip[] = [];
@@ -146,7 +149,7 @@ export function getTipsAndNotificationsFromApiResults(
type FetchNotificationFunction = (
requestID: RequestID,
authProfileAndToken: AuthProfileAndToken
-) => Promise>;
+) => Promise>;
type NotificationServices = Record;
diff --git a/src/server/services/tips/predicates.test.ts b/src/server/services/tips/predicates.test.ts
index d9e9bdba05..f1ff85f819 100644
--- a/src/server/services/tips/predicates.test.ts
+++ b/src/server/services/tips/predicates.test.ts
@@ -26,7 +26,7 @@ import BRP from '../../../../mocks/fixtures/brp.json';
import WPI_AANVRAGEN from '../../../../mocks/fixtures/wpi-aanvragen.json';
import WPI_E from '../../../../mocks/fixtures/wpi-e-aanvragen.json';
import {
- ApiResponse,
+ ApiResponse_DEPRECATED,
ApiSuccessResponse,
} from '../../../universal/helpers/api';
import { AppState, BRPData, BRPDataFromSource } from '../../../universal/types';
@@ -77,7 +77,7 @@ describe('predicates', () => {
BRP as ApiSuccessResponse
),
status: 'OK',
- } as ApiResponse,
+ } as ApiResponse_DEPRECATED,
};
};
@@ -391,8 +391,10 @@ describe('predicates', () => {
return {
WPI_TOZO: TOZO as unknown as AppState['WPI_TOZO'],
- WPI_TONK: TONK as unknown as ApiResponse,
- WPI_AANVRAGEN: UITKERINGEN as unknown as ApiResponse<
+ WPI_TONK: TONK as unknown as ApiResponse_DEPRECATED<
+ WpiRequestProcess[] | null
+ >,
+ WPI_AANVRAGEN: UITKERINGEN as unknown as ApiResponse_DEPRECATED<
WpiRequestProcess[] | null
>,
};
@@ -474,7 +476,7 @@ describe('predicates', () => {
UITKERINGEN.content[0].datePublished = datePublished;
return {
- WPI_AANVRAGEN: UITKERINGEN as unknown as ApiResponse<
+ WPI_AANVRAGEN: UITKERINGEN as unknown as ApiResponse_DEPRECATED<
WpiRequestProcess[] | null
>,
};
@@ -498,7 +500,9 @@ describe('predicates', () => {
describe('hasTozo', () => {
it('should return true when there is some content', () => {
const appState = {
- WPI_TOZO: TOZO as unknown as ApiResponse,
+ WPI_TOZO: TOZO as unknown as ApiResponse_DEPRECATED<
+ WpiRequestProcess[] | null
+ >,
};
expect(hasTozo(appState)).toBe(true);
@@ -506,7 +510,7 @@ describe('predicates', () => {
it('should return false when no content', () => {
const appState = {
- WPI_TOZO: {} as ApiResponse,
+ WPI_TOZO: {} as ApiResponse_DEPRECATED,
};
expect(hasTozo(appState)).toBe(false);
});
diff --git a/src/server/services/tips/tip-types.ts b/src/server/services/tips/tip-types.ts
index ab9b1c0f26..65d3a41ae0 100644
--- a/src/server/services/tips/tip-types.ts
+++ b/src/server/services/tips/tip-types.ts
@@ -1,8 +1,10 @@
import { Thema } from '../../../universal/config/thema';
-import { ApiResponse } from '../../../universal/helpers/api';
+import { ApiResponse_DEPRECATED } from '../../../universal/helpers/api';
import { AppState, LinkProps } from '../../../universal/types';
-export type ServiceResults = { [serviceId: string]: ApiResponse };
+export type ServiceResults = {
+ [serviceId: string]: ApiResponse_DEPRECATED;
+};
export type Tip = {
id: string;
diff --git a/src/server/services/toeristische-verhuur/toeristische-verhuur-powerbrowser-bb-vergunning.ts b/src/server/services/toeristische-verhuur/toeristische-verhuur-powerbrowser-bb-vergunning.ts
index a6be9c89b6..5b88047593 100644
--- a/src/server/services/toeristische-verhuur/toeristische-verhuur-powerbrowser-bb-vergunning.ts
+++ b/src/server/services/toeristische-verhuur/toeristische-verhuur-powerbrowser-bb-vergunning.ts
@@ -20,7 +20,7 @@ import {
import { AppRoutes } from '../../../universal/config/routes';
import {
apiErrorResult,
- ApiResponse,
+ ApiResponse_DEPRECATED,
apiSuccessResult,
getSettledResult,
} from '../../../universal/helpers/api';
@@ -281,7 +281,7 @@ function transformZaakStatusResponse(
async function fetchZaakAdres(
requestID: RequestID,
zaakId: PBZaakRecord['id']
-): Promise> {
+): Promise> {
const addressResponse = await fetchPowerBrowserData(requestID, {
method: 'post',
formatUrl({ url }) {
@@ -315,7 +315,7 @@ async function fetchZaakAdres(
async function fetchZaakStatussen(
requestID: RequestID,
zaak: BBVergunning
-): Promise> {
+): Promise> {
const statusResponse = await fetchPowerBrowserData(
requestID,
{
@@ -490,7 +490,7 @@ async function fetchZakenByIds(
requestID: RequestID,
authProfile: AuthProfile,
zaakIds: string[]
-): Promise> {
+): Promise> {
const requestConfig: DataRequestConfig = {
method: 'get',
formatUrl({ url }) {
@@ -532,7 +532,7 @@ async function fetchZakenByIds(
export async function fetchBBVergunningen(
requestID: RequestID,
authProfile: AuthProfile
-): Promise> {
+): Promise> {
// Set-up the options for the PowerBrowser API request based on the profile type.
const optionsByProfileType: Record<
ProfileType,
@@ -682,7 +682,7 @@ export async function fetchBBDocumentsList(
requestID: RequestID,
authProfile: AuthProfile,
zaakId: BBVergunning['id']
-): Promise> {
+): Promise> {
const dataRequestConfig: DataRequestConfig = {
method: 'post',
formatUrl({ url }) {
diff --git a/src/server/services/vergunningen/vergunningen.ts b/src/server/services/vergunningen/vergunningen.ts
index 4852182876..fcffd9e7e4 100644
--- a/src/server/services/vergunningen/vergunningen.ts
+++ b/src/server/services/vergunningen/vergunningen.ts
@@ -10,7 +10,7 @@ import {
import { AppRoutes } from '../../../universal/config/routes';
import { Themas } from '../../../universal/config/thema';
import {
- ApiResponse,
+ ApiResponse_DEPRECATED,
apiDependencyError,
apiSuccessResult,
} from '../../../universal/helpers/api';
@@ -634,7 +634,9 @@ export async function fetchVergunningenDocumentsList(
{
url,
passthroughOIDCToken: true,
- transformResponse: (responseData: ApiResponse) => {
+ transformResponse: (
+ responseData: ApiResponse_DEPRECATED
+ ) => {
if (responseData.status === 'OK') {
const documents: GenericDocument[] = responseData.content.map(
(document) => {
diff --git a/src/server/services/wpi/api-service.ts b/src/server/services/wpi/api-service.ts
index 4f7360f87d..2635d6dea9 100644
--- a/src/server/services/wpi/api-service.ts
+++ b/src/server/services/wpi/api-service.ts
@@ -1,6 +1,6 @@
import { Themas } from '../../../universal/config/thema';
import {
- ApiResponse,
+ ApiResponse_DEPRECATED,
ApiSuccessResponse,
apiSuccessResult,
} from '../../../universal/helpers/api';
@@ -80,7 +80,7 @@ export async function fetchRequestProcess(
requestProcess: WpiRequestProcess
) => WpiRequestProcessLabels | undefined,
fetchConfig: FetchConfig
-): Promise> {
+): Promise> {
const apiConfig = getApiConfig(fetchConfig.apiConfigName, {
cacheKey: fetchConfig.requestCacheKey,
transformResponse: [
diff --git a/src/server/services/zorgned/zorgned-service.test.ts b/src/server/services/zorgned/zorgned-service.test.ts
index 5877537632..cf8da806f7 100644
--- a/src/server/services/zorgned/zorgned-service.test.ts
+++ b/src/server/services/zorgned/zorgned-service.test.ts
@@ -150,10 +150,14 @@ describe('zorgned-service', () => {
});
it('should fetch document successfully', async () => {
+ const filename = 'Naam documentje';
+ const mimetype = 'foo/bar';
+ const base64Data = 'Zm9vLWJhcg==';
+
remoteApi.post('/zorgned/document').reply(200, {
- inhoud: 'Zm9vLWJhcg==',
- omschrijving: 'Naam documentje',
- mimetype: 'foo/bar',
+ inhoud: base64Data,
+ omschrijving: filename,
+ mimetype,
});
const result = await fetchDocument(
@@ -165,6 +169,7 @@ describe('zorgned-service', () => {
expect(requestData).toHaveBeenCalledWith(
{
+ httpsAgent: expect.any(Object),
url: `${remoteApiHost}/zorgned/document`,
data: {
burgerservicenummer: mocks.mockAuthProfileAndToken.profile.id,
@@ -178,18 +183,16 @@ describe('zorgned-service', () => {
'Content-type': 'application/json; charset=utf-8',
'X-Mams-Api-User': 'JZD',
},
- httpsAgent: expect.any(Object),
},
- mocks.mockRequestID,
- mocks.mockAuthProfileAndToken as AuthProfileAndToken
+ mocks.mockRequestID
);
expect(result).toEqual({
status: 'OK',
content: {
- filename: 'Naam documentje',
- mimetype: 'foo/bar',
- data: Buffer.from('Zm9vLWJhcg==', 'base64'),
+ filename,
+ mimetype,
+ data: Buffer.from(base64Data, 'base64'),
},
});
});
diff --git a/src/server/services/zorgned/zorgned-service.ts b/src/server/services/zorgned/zorgned-service.ts
index 423b9e4e34..729a1510bd 100644
--- a/src/server/services/zorgned/zorgned-service.ts
+++ b/src/server/services/zorgned/zorgned-service.ts
@@ -282,8 +282,7 @@ export async function fetchDocument(
};
},
},
- requestID,
- authProfileAndToken
+ requestID
);
}
diff --git a/src/universal/config/routes.ts b/src/universal/config/routes.ts
index 1e54694670..425ed95ca1 100644
--- a/src/universal/config/routes.ts
+++ b/src/universal/config/routes.ts
@@ -10,7 +10,7 @@ export const AppRoutes = {
'ZORG/VOORZIENINGEN_LIST': '/zorg-en-ondersteuning/:kind/:page?',
HLI: '/regelingen-bij-laag-inkomen',
- 'HLI/STADSPAS': '/regelingen-bij-laag-inkomen/stadspas/:id',
+ 'HLI/STADSPAS': '/regelingen-bij-laag-inkomen/stadspas/:passNumber',
'HLI/REGELING': '/regelingen-bij-laag-inkomen/regeling/:regeling/:id',
'HLI/REGELINGEN_LIST': '/regelingen-bij-laag-inkomen/:kind/:page?',
diff --git a/src/universal/helpers/api.ts b/src/universal/helpers/api.ts
index 633b1819f5..5646b90c8b 100644
--- a/src/universal/helpers/api.ts
+++ b/src/universal/helpers/api.ts
@@ -48,14 +48,19 @@ export type ResponseStatus =
| 'POSTPONE'
| 'DEPENDENCY_ERROR';
-export type ApiResponse =
+export type ApiResponse_DEPRECATED =
| ApiErrorResponse
| ApiSuccessResponse
| ApiPristineResponse
| ApiPostponeResponse
| ApiDependencyErrorResponse;
-export function isLoading(apiResponseData: ApiResponse) {
+export type ApiResponse =
+ | ApiErrorResponse
+ | ApiSuccessResponse
+ | ApiPostponeResponse;
+
+export function isLoading(apiResponseData: ApiResponse_DEPRECATED) {
// If no responseData was found, assumes it's still loading
return (
(!apiResponseData && !isError(apiResponseData)) ||
@@ -63,12 +68,12 @@ export function isLoading(apiResponseData: ApiResponse) {
);
}
-export function isOk(apiResponseData: ApiResponse) {
+export function isOk(apiResponseData: ApiResponse_DEPRECATED) {
return apiResponseData?.status === 'OK';
}
export function isError(
- apiResponseData: ApiResponse,
+ apiResponseData: ApiResponse_DEPRECATED,
includeFailedDependencies: boolean = true
) {
return (
@@ -81,7 +86,7 @@ export function isError(
}
export function hasFailedDependency(
- apiResponseData: ApiResponse,
+ apiResponseData: ApiResponse_DEPRECATED,
dependencyKey: string
) {
return (
@@ -161,7 +166,7 @@ export function apiPostponeResult(content: T): ApiPostponeResponse {
}
export function apiDependencyError(
- apiResponses: Record>
+ apiResponses: Record>
): ApiDependencyErrorResponse {
return {
message: Object.entries(apiResponses).reduce((acc, [key, response]) => {
diff --git a/src/universal/types/App.types.ts b/src/universal/types/App.types.ts
index 910c3f5182..786a1738dc 100644
--- a/src/universal/types/App.types.ts
+++ b/src/universal/types/App.types.ts
@@ -2,12 +2,12 @@ import { FunctionComponent, ReactNode, SVGProps } from 'react';
import { ServiceID, ServicesType } from '../../server/services/controller';
import { Thema } from '../config/thema';
-import { ApiResponse } from '../helpers/api';
+import { ApiResponse_DEPRECATED } from '../helpers/api';
export type BagThema = `${Thema}_BAG`;
export type AppState = {
- [key in ServiceID]: ApiResponse<
+ [key in ServiceID]: ApiResponse_DEPRECATED<
ReturnTypeAsync['content']
>;
} & {