Untitled

mail@pastecode.io avatar
unknown
plain_text
2 months ago
8.6 kB
1
Indexable
Never
import { APIGatewayEvent } from "aws-lambda";
import * as B from "fp-ts/boolean";
import * as C from "fp-ts/Console";
import { pipe } from "fp-ts/function";
import * as TE from "fp-ts/TaskEither";

import { readUrl } from "@lambdas/shared/configuration";
import { catchAndLogTE } from "@lambdas/shared/errors/CatchAndLog";
import * as Json from "@lambdas/shared/json";
import { EDDAMandate } from "@lambdas/shared/models/EDDAMandate";
import {
  LAMBDA_FUNCTION_TIMEOUT_DURATION,
  calculateSentryTimeoutWarningLimit,
} from "@lambdas/shared/monitoring/lambda";
import { decryptDbsNotificationMessage } from "@lambdas/shared/pgp";
import { EDDAMandateRepository } from "@lambdas/shared/repositories/EDDAMandateRepository";
import {
  getBankDetailsRepository,
  getEddaCollectFundsRequestRepo,
  getEddaMandateRepo,
} from "@lambdas/shared/repositoryFactory";
import { sendEddaAccountLinkFailEvent } from "@lambdas/shared/services/blueshift/sendEddaAccountLinkFailEvent";
import { sendEgiroLinkAbortedDuplicateAccountPendingCollection } from "@lambdas/shared/services/blueshift/sendEgiroLinkAbortedDuplicateAccountPendingCollection";
import {
  initializeSentry,
  sentryWrapHandler,
} from "@lambdas/shared/services/sentry";
import { genericApiHandler } from "@lambdas/shared/sharedApiHandler";
import { sendMessagesToQueue } from "@lambdas/shared/sqs/sendMessagesToQueue";
import { HandlerContext, noPayload } from "@lambdas/shared/types";

import * as creationCallbackPayloadParser from "./creationCallbackParser";
import { getRequestIdFromEvent } from "./getRequestIdFromEvent";
import { encodeError, encodeSuccess } from "./httpResponseEncoder";
import {
  isLinkedAccountExistError,
  linkedAccountExistError,
} from "./linkedAccountExistError";
import { logError, logSuccess } from "./logger";
import { duplicateAccountPendingCollectionError, isDuplicateAccountPendingCollectionError } from "./duplicateAccountPendingCollection";

initializeSentry();
export const handler = sentryWrapHandler(
  (event: APIGatewayEvent, context: HandlerContext) =>
    genericApiHandler(
      {
        event,
        name: "dbs-egiro-creation-callback",
        context,
        isEventBodyRequired: true,
      },
      (event: APIGatewayEvent) =>
        pipe(
          TE.Do,
          TE.bindW("requestId", () =>
            TE.fromEither(getRequestIdFromEvent(event))
          ),
          TE.bindW("payload", ({ requestId }) =>
            getRequestPayload(event, requestId)
          ),
          TE.bindW("eddaMandateRepo", getEddaMandateRepo),
          TE.bindW("eddaMandate", ({ eddaMandateRepo, payload }) =>
            eddaMandateRepo.findByTransactionIdWithBankAndUser(
              payload.boTransactionRefNo
            )
          ),
          TE.bindW("validBankAccount", ({ eddaMandate, payload }) =>
            validateBankAccount(
              eddaMandate.userId,
              payload.applicantBankCode!,
              payload.applicantBankAccNo!
            )
          ),
          TE.chainFirstW(({ eddaMandateRepo, payload, validBankAccount }) =>
            completeTokenReceipt(validBankAccount, eddaMandateRepo, payload)
          ),
          TE.bindW("oldEddaMandate", ({ eddaMandateRepo, payload }) => eddaMandateRepo.findCompleteAccountByAndBicAndBankAccountNumber(payload.applicantBankCode!, payload.applicantBankAccNo!)),
          TE.bindW("hasPendingRequest", ({ oldEddaMandate }) => checkPendingRequest(oldEddaMandate)),
          // TE.chainFirstW(({ payload, hasPendingRequest, eddaMandateRepo, oldEddaMandate }) =>
          //   !!payload.errorMessage && oldEddaMandate
          //     ? handleExistPendingRequest(eddaMandateRepo, oldEddaMandate, hasPendingRequest)
          //     : TE.of(undefined)
          // ),
          TE.chainFirstW(({ payload, hasPendingRequest }) =>
            !!payload.errorMessage
              ? catchAndLogTE(C)(
                "Send edda linking fail event error with reason"
              )(publishRejectEventToBlueshift(payload.boTransactionRefNo))
              : handleSendMessageToQueue(payload)
          ),
          TE.orElseFirstW((error) => {
            if (isDuplicateAccountPendingCollectionError(error)) {
              return sendEgiroLinkAbortedDuplicateAccountPendingCollection(error.userId);
            }
            return TE.right(undefined);
          }),
          TE.orElseFirstIOK(logError),
          TE.chainFirstIOK(logSuccess),
          TE.match(encodeError, ({ requestId }) => encodeSuccess(requestId))
        )()
    ),
  {
    timeoutWarningLimit: calculateSentryTimeoutWarningLimit(
      LAMBDA_FUNCTION_TIMEOUT_DURATION.dbsEgiroCreationToken
    ),
  }
);

const getRequestPayload = (event: APIGatewayEvent, requestId: string) =>
  pipe(
    event.body,
    TE.fromNullable(noPayload()),
    TE.chainW(decryptDbsNotificationMessage),
    TE.chainEitherKW(Json.decode),
    TE.chainEitherKW((payload) =>
      creationCallbackPayloadParser.parse(payload, requestId)
    )
  );

const composeMessage = (
  payload: creationCallbackPayloadParser.CreationCallbackPayload
) => ({
  Id: payload.boTransactionRefNo,
  MessageGroupId: payload.boTransactionRefNo,
  MessageBody: JSON.stringify({
    applicantBankAccNo: payload.applicantBankAccNo,
    applicantBankCode: payload.applicantBankCode,
    boDDARefNo: payload.boDDARefNo,
    boTransactionRefNo: payload.boTransactionRefNo,
    bankToken: payload.bankToken,
    maxAmount: payload.maxAmount,
    fromDate: payload.fromDate?.toFormat("dd-MM-yyyy"),
    toDate: payload.toDate?.toFormat("dd-MM-yyyy"),
  }),
});

const handleSendMessageToQueue = (
  payload: creationCallbackPayloadParser.CreationCallbackPayload,
) =>
  pipe(
    readUrl("QUEUE_URL"),
    TE.fromEither,
    TE.chainW((queueUrl) =>
      sendMessagesToQueue(queueUrl, [composeMessage(payload)])
    ),
    TE.map(() => undefined)
  );

const publishRejectEventToBlueshift = (transactionId: string) =>
  pipe(
    getEddaMandateRepo(),
    TE.chainW((repo) => repo.findByTransactionIdWithBankAndUser(transactionId)),
    TE.chainW(sendEddaAccountLinkFailEvent)
  );

const validateBankAccount = (
  userId: string,
  bic: string,
  accountNumber: string
) =>
  pipe(
    getBankDetailsRepository(),
    TE.chainW((repo) => repo.findByBicAndAccountNumber(bic, accountNumber)),
    TE.chainW(
      TE.fromPredicate(
        (bankDetail) => !bankDetail || bankDetail.userId === userId,
        linkedAccountExistError
      )
    ),
    TE.chainW(() => TE.of(true)),
    TE.orElseW((error) => {
      if (isLinkedAccountExistError(error)) {
        console.error("The requested bank account was already registered");

        return TE.right(false);
      }

      return TE.left(error);
    })
  );

const completeTokenReceipt = (
  validBankAccount: boolean,
  eddaMandateRepo: EDDAMandateRepository,
  payload: creationCallbackPayloadParser.CreationCallbackPayload
) =>
  pipe(
    validBankAccount,
    B.foldW(
      () =>
        eddaMandateRepo.completeTokenReceipt(
          payload.boTransactionRefNo,
          payload.boDDARefNo,
          false,
          undefined,
          `Complete token receipt: The requested bank account was already registered`
        ),
      () =>
        eddaMandateRepo.completeTokenReceipt(
          payload.boTransactionRefNo,
          payload.boDDARefNo,
          !payload.errorMessage,
          payload.applicantBankAccNo,
          payload.errorMessage &&
          `Complete token receipt: ${payload.errorMessage}`,
          payload.toDate
        )
    )
  );

const checkPendingRequest = (
  oldEddaMandate: EDDAMandate | null,
) =>
  pipe(
    oldEddaMandate === null,
    B.foldW(
      () => pipe(
        getEddaCollectFundsRequestRepo(),
        TE.chainW((repo) => repo.findPendingRequestsByMandateTransactionId(oldEddaMandate!.transactionId)),
        TE.chainW(
          TE.fromPredicate(
            (pendingRequest) => pendingRequest.length === 0,
            () => duplicateAccountPendingCollectionError(oldEddaMandate!.userId)
          )
        )
      ),
      () => TE.of(undefined)
    )
  )

const handleExistPendingRequest = (eddaMandateRepo: EDDAMandateRepository, oldEddaMandate: EDDAMandate, hasPendingRequest: boolean) =>
  pipe(
    hasPendingRequest,
    B.foldW(
      () => eddaMandateRepo.deleteByTransactionId(oldEddaMandate.transactionId),
      // () => TE.right(undefined),
      () => sendEgiroLinkAbortedDuplicateAccountPendingCollection(oldEddaMandate.userId),
      // () => pipe(
      //   TE.Do,
      //   TE.chainW(() => sendEgiroLinkAbortedDuplicateAccountPendingCollection(oldEddaMandate.userId))
      // )
    )
  )
Leave a Comment