Skip to content
4 changes: 2 additions & 2 deletions scripts/generate-api-models.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ declare -a noParams=(
"./generated/definitions/pagopa/walletv2 https://raw.githubusercontent.com/pagopa/io-services-metadata/$IO_SERVICES_METADATA_VERSION/bonus/specs/bpd/pm/walletv2.json"
"./generated/definitions/pagopa/walletv3 https://raw.githubusercontent.com/pagopa/pagopa-infra/refs/tags/v1.745.1/src/domains/pay-wallet-app/api/io-payment-wallet/v1/_openapi.json.tpl"
"./generated/definitions/pagopa/ecommerce https://raw.githubusercontent.com/pagopa/pagopa-infra/v1.724.0/src/domains/ecommerce-app/api/ecommerce-io/v2/_openapi.json.tpl"
"./generated/definitions/pagopa/transactions https://raw.githubusercontent.com/pagopa/pagopa-biz-events-service/refs/tags/0.1.87/openapi/openapi_lap_jwt.json"
"./generated/definitions/pagopa/transactions https://raw.githubusercontent.com/pagopa/pagopa-biz-events-service/refs/tags/0.3.5/openapi/openapi_lap_jwt.json"
"./generated/definitions/pagopa/platform https://raw.githubusercontent.com/pagopa/pagopa-infra/v1.64.0/src/domains/shared-app/api/session-wallet/v1/_openapi.json.tpl"
"./generated/definitions/pagopa https://raw.githubusercontent.com/pagopa/io-app/master/assets/paymentManager/spec.json"
"./generated/definitions/cgn https://raw.githubusercontent.com/pagopa/io-backend/$IO_BACKEND_VERSION/api_cgn.yaml"
Expand All @@ -28,7 +28,7 @@ declare -a noParams=(

declare -a noStrict=(
"./generated/definitions/fci https://raw.githubusercontent.com/pagopa/io-backend/$IO_BACKEND_VERSION/api_io_sign.yaml"
"./generated/definitions/idpay https://raw.githubusercontent.com/pagopa/cstar-securehub-infra-api-spec/refs/tags/v2.47.6/src/idpay/apim/api/idpay_appio_full/openapi.appio.full.yml"
"./generated/definitions/idpay https://raw.githubusercontent.com/pagopa/cstar-securehub-infra-api-spec/refs/tags/v3.10.2/src/idpay/apim/api/idpay_appio_full/openapi.appio.full.yml"
"./generated/definitions/services https://raw.githubusercontent.com/pagopa/io-backend/$IO_BACKEND_VERSION/api_services_app_backend.yaml"
)

Expand Down
10 changes: 10 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,16 @@ const defaultConfig: IoDevServerConfig = {
numberOfTransactions: 12,
hideReceiptResponseCode: 400
},
receipts: {
pdfNotFoundResponse: undefined
// {
// title: "Attachment not found",
// status: 400,
// detail:
// "Attachment of 321 for bizEvent with id 123 is still generating",
// code: "AT_404_002"
// }
},
bonus: {
cgn: {
isCgnEligible: true,
Expand Down
168 changes: 136 additions & 32 deletions src/features/payments/persistence/notices.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,37 @@
import { fakerIT as faker } from "@faker-js/faker";
import { pipe } from "fp-ts/lib/function";
import * as O from "fp-ts/lib/Option";
import { NoticeListItem } from "../../../../generated/definitions/pagopa/transactions/NoticeListItem";
import { NoticeDetailResponse } from "../../../../generated/definitions/pagopa/transactions/NoticeDetailResponse";
import { TransactionInfo } from "../../../../generated/definitions/pagopa/ecommerce/TransactionInfo";
import { NoticeDetailResponse } from "../../../../generated/definitions/pagopa/transactions/NoticeDetailResponse";
import { NoticeListItem } from "../../../../generated/definitions/pagopa/transactions/NoticeListItem";
import { generateRandomInfoNotice } from "../utils/transactions";
import { ioDevServerConfig } from "../../../config";

const mockedTaxCodes = ["1199250158", "13756881002", "262700362", "31500945"];

const receiptSubjects = [
"Tassa sui rifiuti",
"Imposta comunale immobiliare",
"Bollo auto",
"Canone RAI",
"Imposta ipotecaria",
"Diritti di segreteria",
"Sanzione amministrativa",
"Contributi sociali",
"Tassa di concessione governativa",
"Imposta di registro",
"Contributo provinciale",
"Tassa provinciale per i rifiuti",
"Diritti di istruttoria",
"Sanzione traffico",
"Multa amministrativa"
];

type EventId = NoticeListItem["eventId"];

const userNotices = new Map<EventId, NoticeListItem>();
const userNotices: NoticeListItem[] = [];
const notices = new Map<EventId, NoticeDetailResponse>();

const getUserNotices = () =>
Array.from(userNotices.size > 0 ? userNotices.values() : []);
const getUserNotices = () => [...userNotices];

const getNoticeDetails = (eventId: EventId) =>
pipe(
Expand All @@ -25,11 +41,18 @@ const getNoticeDetails = (eventId: EventId) =>
);

const addUserNotice = (transaction: NoticeListItem) => {
userNotices.set(transaction.eventId, transaction);
// eslint-disable-next-line functional/immutable-data
userNotices.push(transaction);
};

const removeUserNotice = (eventId: EventId) => {
userNotices.delete(eventId);
// Remove all notices with the given eventId
// eslint-disable-next-line functional/immutable-data
userNotices.splice(
0,
userNotices.length,
...userNotices.filter(n => n.eventId !== eventId)
);
removeNoticeDetails(eventId);
};

Expand All @@ -44,16 +67,16 @@ const removeNoticeDetails = (eventId: EventId) => {
const generateUserNotice = (
eventId: EventId,
idx: number,
additionalTransactionInfo: Partial<TransactionInfo> = {}
additionalTransactionInfo: Partial<TransactionInfo> = {},
notice?: NoticeListItem
) => {
const payeeTaxCode =
mockedTaxCodes[
faker.number.int({ min: 0, max: mockedTaxCodes.length - 1 })
];
const randomNotice: NoticeListItem = {
const noticeToAdd = notice || {
eventId,
payeeName: faker.company.name(),
payeeTaxCode,
payeeTaxCode:
mockedTaxCodes[
faker.number.int({ min: 0, max: mockedTaxCodes.length - 1 })
],
isDebtor: false,
isPayer: true,
amount: additionalTransactionInfo.payments?.[0]?.amount.toString() || "",
Expand All @@ -66,11 +89,14 @@ const generateUserNotice = (
const cartList = Array.from(
{ length: faker.number.int({ min: 1, max: 2 }) },
() => ({
subject: faker.lorem.sentence(faker.number.int({ min: 2, max: 4 })),
subject:
receiptSubjects[
faker.number.int({ min: 0, max: receiptSubjects.length - 1 })
],
amount: faker.finance.amount({ min: 1, max: 1000 }),
payee: {
name: randomNotice.payeeName,
taxCode: randomNotice.payeeTaxCode
name: noticeToAdd.payeeName,
taxCode: noticeToAdd.payeeTaxCode
},
debtor: {
name: faker.person.fullName(),
Expand All @@ -82,33 +108,111 @@ const generateUserNotice = (
.toString()
})
);
const updatedNotice = {
...randomNotice,
isCart: cartList.length > 1,
amount: cartList
.reduce((acc, item) => acc + Number(item.amount), 0)
.toString()
};

const updatedNotice = notice
? noticeToAdd
: {
...noticeToAdd,
isCart: cartList.length > 1,
amount: cartList
.reduce((acc, item) => acc + Number(item.amount), 0)
.toString()
};

addUserNotice(updatedNotice);

const randomNoticeDetails: NoticeDetailResponse = {
const noticeDetails: NoticeDetailResponse = {
infoNotice: generateRandomInfoNotice(cartList),
carts: cartList
};
addNoticeDetails(eventId, randomNoticeDetails);
addNoticeDetails(eventId, noticeDetails);
return updatedNotice;
};

const generateUserNoticeData = () => {
for (const i of Array(
ioDevServerConfig.features.payments.numberOfTransactions
).keys()) {
const addMockedCartNotices = () => {
const cartConfig = [
{
count: 5,
payeeName: "Ministero delle infrastrutture e dei trasporti",
payeeTaxCode: "97532760580"
},
{
count: 2,
payeeName: "EC_TE",
payeeTaxCode: "77777777777"
}
];

cartConfig.forEach(config => {
const cartId = faker.string.uuid();
const cartPrefix = `test-ricevute-carrello-${cartId}-0_CART_`;
const cartItems = [];

// eslint-disable-next-line functional/no-let
for (let i = 0; i < config.count; i += 1) {
const amount = faker.finance.amount({ min: 10, max: 150 });
const notice: NoticeListItem = {
eventId: cartPrefix,
payeeName: config.payeeName,
payeeTaxCode: config.payeeTaxCode,
amount,
noticeDate: new Date(
new Date().setDate(new Date().getDate() - i)
).toISOString(),
isCart: true,
isPayer: true,
isDebtor: false
};

// Build cart items for the detail response
// eslint-disable-next-line functional/immutable-data
cartItems.push({
subject:
receiptSubjects[
faker.number.int({ min: 0, max: receiptSubjects.length - 1 })
],
amount,
payee: {
name: config.payeeName,
taxCode: config.payeeTaxCode
},
debtor: {
name: faker.person.fullName(),
taxCode: faker.string.alphanumeric(16).toUpperCase()
},
refNumberType: "IBAN",
refNumberValue: faker.number
.int({ min: 100000000000, max: 999999999999 })
.toString()
});

addUserNotice(notice);
}

// Add notice details once for the entire cart with all items
const noticeDetails: NoticeDetailResponse = {
infoNotice: generateRandomInfoNotice(cartItems),
carts: cartItems
};
addNoticeDetails(cartPrefix, noticeDetails);
});

// Add some regular (non-cart) notices
// eslint-disable-next-line functional/no-let
for (let i = 0; i < 5; i += 1) {
generateUserNotice(faker.string.uuid(), i);
}
};

const shuffleNotices = () => {
// eslint-disable-next-line functional/immutable-data
userNotices.sort(() => faker.number.int({ min: -1, max: 1 }));
};

// At server startup
generateUserNoticeData();
addMockedCartNotices();
// Comment out to keep notices in order addedMockedCartNotices();
shuffleNotices();

export default {
addUserNotice,
Expand Down
24 changes: 19 additions & 5 deletions src/features/payments/routers/notices.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import * as O from "fp-ts/lib/Option";
import { pipe } from "fp-ts/lib/function";
import { sendFileFromRootPath } from "../../../utils/file";
import NoticesDB from "../persistence/notices";

import { NoticeListWrapResponse } from "../../../../generated/definitions/pagopa/transactions/NoticeListWrapResponse";
import { ioDevServerConfig } from "../../../config";
import { sendFileFromRootPath } from "../../../utils/file";
import NoticesDB from "../persistence/notices";
import { addNoticesHandler } from "./router";

const CONTINUATION_TOKEN_HEADER = "x-continuation-token";
const DEFAULT_SIZE = 10;
const { hideReceiptResponseCode } = ioDevServerConfig.features.payments;
const { pdfNotFoundResponse } = ioDevServerConfig.features.receipts;

addNoticesHandler("get", "/paids", (req, res) => {
const size = req.query.size ? Number(req.query.size) : DEFAULT_SIZE;
Expand Down Expand Up @@ -71,11 +71,23 @@ addNoticesHandler("get", "/paids/:eventId/pdf", (req, res) => {
O.fold(
() => res.sendStatus(400),
eventId => {
if (pdfNotFoundResponse) {
return res
.status(pdfNotFoundResponse.status)
.json(pdfNotFoundResponse);
}
const transaction = NoticesDB.getNoticeDetails(eventId);
return pipe(
transaction,
O.fold(
() => res.sendStatus(404),
() =>
res.status(404).json({
title: "Attachment not found",
status: 404,
detail:
"Attachment of 321 for bizEvent with id 123 is still generating",
code: "AT_404_002"
}),
_ => {
sendFileFromRootPath(
"assets/payments/receipts/loremIpsum.pdf",
Expand All @@ -97,7 +109,9 @@ addNoticesHandler("post", "/paids/:eventId/disable", (req, res) => {
O.fold(
() => res.sendStatus(400),
eventId => {
NoticesDB.removeUserNotice(eventId);
if (hideReceiptResponseCode === 200) {
NoticesDB.removeUserNotice(eventId);
}
return res.sendStatus(hideReceiptResponseCode);
}
)
Expand Down
12 changes: 12 additions & 0 deletions src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,18 @@ export const IoDevServerConfig = t.interface({
// the response code when hiding a receipt
hideReceiptResponseCode: HttpResponseCode
}),
receipts: t.interface({
// if defined, the PDF endpoint returns this 404 response instead of the file
pdfNotFoundResponse: t.union([
t.interface({
title: t.string,
status: t.number,
detail: t.string,
code: t.string
}),
t.undefined
])
}),
bonus: t.interface({
// defines the special configuration for cgn eligibility
cgn: t.intersection([
Expand Down
Loading