Skip to content

Commit

Permalink
[IOCOM-1914] Paid payment badge from message details (#6610)
Browse files Browse the repository at this point in the history
## Short description
This PR adds the PAID badge to a message in the messages home if the
related check happens on the message details.
<video
src="https://github.com/user-attachments/assets/38e2cacc-6beb-4996-bcab-8048d3a5f259"/>

## List of changes proposed in this pull request
- Paid badge reducer listens for the action that is dispatched by the
payment update on the message details
- Status is updated only if the information was not available yet, in
order not to overwrite more detailed data that may have come from a
complete payment flow

Be aware that a paid payment cannot change its status (so there is no
case where data have to be removed from the paid badge reducer)

## How to test
Using the io-dev-api-server, check that a paid payment generates the
PAID label, if detected from the message details.

---------

Co-authored-by: Martino Cesari Tomba <[email protected]>
  • Loading branch information
Vangaorth and forrest57 authored Jan 17, 2025
1 parent d76c8c9 commit eddd677
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 0 deletions.
94 changes: 94 additions & 0 deletions ts/store/reducers/entities/__tests__/payments.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { Detail_v2Enum } from "../../../../../definitions/backend/PaymentProblemJson";
import { ServiceId } from "../../../../../definitions/backend/ServiceId";
import {
updatePaymentForMessage,
UpdatePaymentForMessageFailure
} from "../../../../features/messages/store/actions";
import { UIMessageId } from "../../../../features/messages/types";
import { isPaidPaymentFromDetailV2Enum } from "../../../../utils/payment";
import { paymentByRptIdReducer, PaymentByRptIdState } from "../payments";

describe("payments", () => {
describe("paymentByRptIdReducer", () => {
const rptId1 = "01234567890012345678912345610";
const rptId2 = "01234567890012345678912345620";
const initialState: PaymentByRptIdState = {
[rptId1]: {
kind: "COMPLETED",
transactionId: 15
},
[rptId2]: {
kind: "DUPLICATED"
}
};
const payload: UpdatePaymentForMessageFailure = {
details: Detail_v2Enum.PAA_PAGAMENTO_DUPLICATO,
messageId: "5a15aba4-7cd6-490b-b3fe-cf766f731a2f" as UIMessageId,
paymentId: "01234567890012345678912345630",
serviceId: "75c046cf-77a7-4d33-9c3f-578e00379b55" as ServiceId
};

Object.values(Detail_v2Enum)
.filter(detailV2Enum => !isPaidPaymentFromDetailV2Enum(detailV2Enum))
.forEach(detailV2Enum => {
it(`should return the input state if the failure details are '${detailV2Enum}'`, () => {
const outputState = paymentByRptIdReducer(
initialState,
updatePaymentForMessage.failure({
...payload,
details: detailV2Enum
})
);
expect(outputState).toBe(initialState);
});
});
it(`should return the input state if failure details are 'PAA_PAGAMENTO_DUPLICATO' but there is already a record in the state for the input payment`, () => {
const outputState = paymentByRptIdReducer(
initialState,
updatePaymentForMessage.failure({
...payload,
paymentId: rptId1,
details: Detail_v2Enum.PAA_PAGAMENTO_DUPLICATO
})
);
expect(outputState).toBe(initialState);
});
it(`should return the input state if failure details are 'PPT_PAGAMENTO_DUPLICATO' but there is already a record in the state for the input payment`, () => {
const outputState = paymentByRptIdReducer(
initialState,
updatePaymentForMessage.failure({
...payload,
paymentId: rptId1,
details: Detail_v2Enum.PPT_PAGAMENTO_DUPLICATO
})
);
expect(outputState).toBe(initialState);
});
it(`should return the updated state if failure details are 'PAA_PAGAMENTO_DUPLICATO' and there is no record for the input payment`, () => {
const outputState = paymentByRptIdReducer(
initialState,
updatePaymentForMessage.failure({
...payload,
details: Detail_v2Enum.PAA_PAGAMENTO_DUPLICATO
})
);
expect(outputState).toEqual({
...initialState,
[payload.paymentId]: { kind: "DUPLICATED" }
});
});
it(`should return the updated state if failure details are 'PPT_PAGAMENTO_DUPLICATO' and there is no record for the input payment`, () => {
const outputState = paymentByRptIdReducer(
initialState,
updatePaymentForMessage.failure({
...payload,
details: Detail_v2Enum.PPT_PAGAMENTO_DUPLICATO
})
);
expect(outputState).toEqual({
...initialState,
[payload.paymentId]: { kind: "DUPLICATED" }
});
});
});
});
38 changes: 38 additions & 0 deletions ts/store/reducers/entities/payments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import { Action } from "../../actions/types";
import { paymentCompletedSuccess } from "../../../features/payments/checkout/store/actions/orchestration";
import { GlobalState } from "../types";
import { differentProfileLoggedIn } from "../../actions/crossSessions";
import {
updatePaymentForMessage,
UpdatePaymentForMessageFailure
} from "../../../features/messages/store/actions";
import { isPaidPaymentFromDetailV2Enum } from "../../../utils/payment";

export type PaidReason = Readonly<
| {
Expand Down Expand Up @@ -44,6 +49,14 @@ export const paymentByRptIdReducer = (
transactionId: undefined
}
};
// This action is dispatched by the payment status update saga that is triggered upon
// entering message details. Be aware that the status of a paid payment can never change,
// so there is no need to handle the removal of a no-more-paid payment from the state
case getType(updatePaymentForMessage.failure):
return paymentByRptIdStateFromUpdatePaymentForMessageFailure(
action.payload,
state
);
// clear state if the current profile is different from the previous one
case getType(differentProfileLoggedIn):
return INITIAL_STATE;
Expand All @@ -53,6 +66,31 @@ export const paymentByRptIdReducer = (
}
};

const paymentByRptIdStateFromUpdatePaymentForMessageFailure = (
payload: UpdatePaymentForMessageFailure,
state: PaymentByRptIdState
): PaymentByRptIdState => {
// Only paid payments are tracked from the reducer, ignore the others
const isPaidPayment = isPaidPaymentFromDetailV2Enum(payload.details);
if (!isPaidPayment) {
return state;
}
const rptId = payload.paymentId;
// Make sure not to overwrite any existing data (since it may
// have come from a payment flow, where data are more detailed)
const inMemoryPaymentData = state[rptId];
if (inMemoryPaymentData != null) {
return state;
}
// Paid payment was not tracked, add it to the reducer's state
return {
...state,
[rptId]: {
kind: "DUPLICATED"
}
};
};

// Selectors

export const paymentsByRptIdSelector = (
Expand Down

0 comments on commit eddd677

Please sign in to comment.