File

src/docusign/docusign.service.ts

Index

Properties
Methods

Constructor

constructor()

Methods

Async addDocusignStatusToOrdersRequest
addDocusignStatusToOrdersRequest(orderRequests: LeanDocument[])
Parameters :
Name Type Optional
orderRequests LeanDocument<OrderRequestDocument>[] No
Async createAnEnvelope
createAnEnvelope(undefined)
Parameters :
Name Optional
No
Returns : Promise<EnvelopeSummary>
Async fetchCustomFields
fetchCustomFields(orderId: string)
Parameters :
Name Type Optional
orderId string No
Returns : unknown
Async fetchDocument
fetchDocument(orderId: string, user: UserJwtPayload)
Parameters :
Name Type Optional
orderId string No
user UserJwtPayload No
Returns : unknown
Async fetchEnvelop
fetchEnvelop(orderId: string, user: UserJwtPayload)
Parameters :
Name Type Optional
orderId string No
user UserJwtPayload No
Returns : unknown
Async generateJwtToken
generateJwtToken()
Async getContracts
getContracts(userJwtPayload: UserJwtPayload, filterContractStatusDto: FilterContractStatusDto)
Parameters :
Name Type Optional
userJwtPayload UserJwtPayload No
filterContractStatusDto FilterContractStatusDto No
Returns : unknown
Async getDealerSubmittedDocuments
getDealerSubmittedDocuments(undefined: IGetEnvelopesByCustomField)
Parameters :
Name Type Optional
IGetEnvelopesByCustomField No
Async getInvestorSubmittedDocuments
getInvestorSubmittedDocuments(undefined: IGetEnvelopesByReceiverEmail)
Parameters :
Name Type Optional
IGetEnvelopesByReceiverEmail No
Async getValidToken
getValidToken()
Returns : Promise<string>

Properties

Private Readonly envelopesApi
Type : EnvelopesApi
Private Readonly orderRequestService
Type : OrderRequestService
Decorators :
@Inject(OrderRequestService)
token
Type : AccessTokenResponse
Private Readonly userService
Type : UserService
Decorators :
@Inject(UserService)
import fs from "fs";
import {
  EnvelopesApi,
  EnvelopeDefinition,
  EnvelopeSummary,
  Envelope,
} from "docusign-esign";

import {
  BadRequestException,
  Inject,
  Injectable,
  ServiceUnavailableException,
  UnauthorizedException,
} from "@nestjs/common";
import { DocuSignClient, START_QUERYING_FROM } from "./config/docusignClient";
import type {
  AccessTokenResponse,
  ContractStatus,
  IGetEnvelopesByCustomField,
  IGetEnvelopesByReceiverEmail,
  IOrderRequestWithDocusignStatus,
} from "./docusign.interface";
import { SendContractDto } from "./dtos/send-contract.dto";
import { UserService } from "src/user/user.service";
import { OrderRequestService } from "src/request-order/order-request.service";
import {
  EndUserRoles,
  IGetDealerIdAndStatusByOrderIdQuery,
  RequestStatus,
  UserJwtPayload,
} from "src/common/interfaces";
import { FilterContractStatusDto } from "./dtos/filter-status.dto";
import { decodeBytes32String } from "ethers";
import {
  apolloClient,
  getDealerIdAndStatusByOrderIdQuery,
} from "src/common/provider";
import { ApolloQueryResult } from "@apollo/client";
import { UserWithOnChainFields } from "src/user/user.interface";
import type { LeanDocument } from "mongoose";
import type { UserDocument } from "src/user/schemas/user.schema";
import type { OrderRequestDocument } from "src/request-order/schemas/orderRequest.schema";
import { oneHourToStringDate } from "src/common/utils/addOneHourToStringDate";

@Injectable()
export class DocuSignService {
  @Inject(UserService)
  private readonly userService: UserService;
  @Inject(OrderRequestService)
  private readonly orderRequestService: OrderRequestService;

  token: AccessTokenResponse;
  private readonly envelopesApi: EnvelopesApi;
  constructor() {
    this.envelopesApi = new EnvelopesApi(DocuSignClient);
  }
  async getValidToken(): Promise<string> {
    if (this.token && this.token.expires_in <= Date.now()) {
      return this.token.access_token;
    }
    return (await this.generateJwtToken()).access_token;
  }

  async generateJwtToken(): Promise<AccessTokenResponse> {
    try {
      // Just for the first time runing on your machine http://localhost:3001/?code=eyJ0eXAiOiJNVCIsImFsZyI6IlJTMjU2Iiwia2lkIjoiNjgxODVmZjEtNGU1MS00Y2U5LWFmMWMtNjg5ODEyMjAzMzE3In0.AQsAAAABAAYABwCAcnbVPmfbSAgAgP78HD9n20gCAHXmPyQsopNJjth05KX8gmQVAAEAAAAYAAIAAAAFAAAAHQAAAA0AJAAAAGIwOTcwNDBkLTQxMDgtNDBiNC04ZTU2LTUzOGFmYjkxMjE5YSIAJAAAAGIwOTcwNDBkLTQxMDgtNDBiNC04ZTU2LTUzOGFmYjkxMjE5YTAAgHJ21T5n20gSAAEAAAALAAAAaW50ZXJhY3RpdmU3AO4j2M7dFydMmLlBu7cNy2w.YpFDh3YXmylX6N59gJpO7rrfndy5ArBBujSh4lAClYnViDLyBuKeCo4vs0pgLMgWrgaNdIvhDmC6GfhNbb7jQavX2DbvMf4hAYUVC3qbvC7KR1tsRoRkMKZCwjUwSpgVcwGAKSZ8wrNuAXJCUSF0bsYB61YqYUUkea6hWCucuGW6hRal-8ug6AOvSsKH7kZnHZASgpWbqP7FlC0Jbuwg1vMJNL5CFAcvwWBMHd2b85y-5-Lsqj7oTUCf_Qqx38zfmQy_s_vwZ0SOaluJ__incOENH-J9ZJUTC9oQFSO99xSAUIy2cy8IbZEEPN3bXGeANjRnMBdfp7G2in_sL_yQVw
      const response = await DocuSignClient.requestJWTUserToken(
        process.env.DOCUSIGN_INTEGRATION_KEY,
        process.env.DOCUSIGN_USER_ID,
        ["signature"],
        fs.readFileSync("private.key"),
        3600,
      );
      return response.body;
    } catch (error) {
      throw new ServiceUnavailableException(error);
    }
  }

  async createAnEnvelope({
    emailSubject,
    receiverEmail,
    receiverName,
    fundId,
    dealerEmail,
    orderId,
    htmlString,
    isAtomic,
  }: SendContractDto & {
    dealerEmail: string;
    isAtomic?: boolean;
  }): Promise<EnvelopeSummary> {
    try {
      const [jwtToken, users, dealer, dealerIdSubgraph]: [
        string,
        LeanDocument<UserDocument>[] | UserWithOnChainFields[],
        LeanDocument<UserDocument>,
        ApolloQueryResult<IGetDealerIdAndStatusByOrderIdQuery>,
      ] = await Promise.all([
        this.getValidToken(),
        this.userService.findUsersByFilter({
          subscribedFunds: [fundId],
          endUserRole: EndUserRoles.fundAdmin,
        }),
        this.userService.findUserByProperty({
          email: dealerEmail,
        }),
        isAtomic
          ? undefined
          : apolloClient.query({
              query: getDealerIdAndStatusByOrderIdQuery,
              variables: {
                orderId,
              },
            }),
      ]);
      if (
        dealerIdSubgraph &&
        decodeBytes32String(dealerIdSubgraph.data.order.dealer.id) !==
          dealer._id.toString()
      ) {
        throw new UnauthorizedException(
          "The dealer is not assigned to this order",
        );
      }
      if (
        dealerIdSubgraph &&
        dealerIdSubgraph.data.order.status !== "Confirmed" &&
        dealerIdSubgraph.data.order.status !== "Created"
      ) {
        throw new BadRequestException("The order is not confirmed yet");
      }

      const ccFundAdminsAndDealer = users
        .filter((user) => !user.email.includes("test_"))
        .map(({ name, email }, index) => {
          return {
            recipientId: (index + 2).toString(),
            name,
            email,
            roleName: `cc${index}`,
          };
        });
      const dealerRecipientId = ccFundAdminsAndDealer.length
        ? Number(
            ccFundAdminsAndDealer[ccFundAdminsAndDealer.length - 1].recipientId,
          ).toString()
        : "0";
      ccFundAdminsAndDealer.push({
        recipientId: dealerRecipientId,
        name: dealer.name,
        email: dealer.email,
        roleName: `cc${ccFundAdminsAndDealer.length}`,
      });
      DocuSignClient.addDefaultHeader("Authorization", `Bearer ${jwtToken}`);
      const envelopeDefinition: EnvelopeDefinition = {
        emailSubject,
        templateId: process.env.TEMPLATE_ID,
        templateRoles: [
          {
            recipientId: "1",
            email: receiverEmail,
            name: receiverName,
            roleName: "signer",
          },
          ...ccFundAdminsAndDealer,
        ],
        documents: [
          {
            documentId: "1",
            name: "libre",
            htmlDefinition: {
              source: htmlString.replace(
                "[Sign here]",
                '<ds-signature data-ds-role="Signer"/>',
              ),
            },
          },
        ],
        status: "sent",
        customFields: {
          textCustomFields: [
            { name: "fundId", value: fundId },
            { name: "dealerEmail", value: dealerEmail }, // to rename to "dealerEmail" on the new deployment
            { name: "orderId", value: orderId },
          ],
        },
      };
      const envelope = await this.envelopesApi.createEnvelope(
        process.env.DOCUSIGN_ACCOUNT_ID,
        { envelopeDefinition },
      );
      await this.orderRequestService.create({
        envelopId: envelope.envelopeId,
        orderId,
        fundId,
        status: RequestStatus.underReview,
      });
      return envelope;
    } catch (error) {
      if (error.status === 401) {
        throw new UnauthorizedException(error);
      }
      if (error.status === 400) {
        throw new BadRequestException(error);
      }
      throw new ServiceUnavailableException(error);
    }
  }

  async getDealerSubmittedDocuments({
    customFieldName,
    customFieldValue,
    status,
    docuSignStatus,
    orderFromTime,
    orderToTime,
  }: IGetEnvelopesByCustomField): Promise<IOrderRequestWithDocusignStatus[]> {
    try {
      const jwtToken = await this.getValidToken();
      DocuSignClient.addDefaultHeader("Authorization", `Bearer ${jwtToken}`);

      const envelopesApi = new EnvelopesApi(DocuSignClient);

      const customFieldQuery = `${customFieldName}=${customFieldValue}`;

      const envelopesInformation = await envelopesApi.listStatusChanges(
        process.env.DOCUSIGN_ACCOUNT_ID,
        {
          fromDate: orderFromTime || START_QUERYING_FROM,
          toDate: orderToTime,
          status: docuSignStatus,
          customField: customFieldQuery,
        },
      );
      const envelopById: Record<string, Envelope> = {};
      if (!envelopesInformation.envelopes) {
        return [];
      }
      const requestedOrders = await this.orderRequestService.getRequestedOrders(
        status ? { status } : {},
        "envelopId",
        envelopesInformation.envelopes.map((envelop) => {
          envelopById[envelop.envelopeId] = envelop;
          return envelop.envelopeId;
        }),
      );
      return requestedOrders.map((requestOrder) => {
        return {
          ...requestOrder,
          docuSignStatus: envelopById[requestOrder.envelopId]
            .status as ContractStatus,
        };
      });
    } catch (error) {
      console.error("Error fetching envelopes:", error);
      throw new ServiceUnavailableException(error);
    }
  }
  async getInvestorSubmittedDocuments({
    receiverEmail,
    status,
    docuSignStatus,
    orderFromTime,
    orderToTime,
  }: IGetEnvelopesByReceiverEmail): Promise<IOrderRequestWithDocusignStatus[]> {
    try {
      const jwtToken = await this.getValidToken();
      DocuSignClient.addDefaultHeader("Authorization", `Bearer ${jwtToken}`);

      const envelopesApi = new EnvelopesApi(DocuSignClient);

      const envelopesInformation = await envelopesApi.listStatusChanges(
        process.env.DOCUSIGN_ACCOUNT_ID,
        {
          fromDate: orderFromTime || START_QUERYING_FROM,
          toDate: orderToTime,
          status: docuSignStatus,
          email: receiverEmail,
        },
      );

      const envelopById: Record<string, Envelope> = {};
      if (!envelopesInformation.envelopes) {
        return [];
      }
      const requestedOrders = await this.orderRequestService.getRequestedOrders(
        status ? { status } : {},
        "envelopId",
        envelopesInformation.envelopes.map((envelop) => {
          envelopById[envelop.envelopeId] = envelop;
          return envelop.envelopeId;
        }),
      );
      return requestedOrders.map((requestOrder) => {
        return {
          ...requestOrder,
          docuSignStatus: envelopById[requestOrder.envelopId]
            .status as ContractStatus,
        };
      });
    } catch (error) {
      console.error("Error fetching envelopes:", error);
      throw new ServiceUnavailableException(error);
    }
  }

  async addDocusignStatusToOrdersRequest(
    orderRequests: LeanDocument<OrderRequestDocument>[],
  ): Promise<IOrderRequestWithDocusignStatus[]> {
    if (!orderRequests.length) {
      return [];
    }
    const jwtToken = await this.getValidToken();
    DocuSignClient.addDefaultHeader("Authorization", `Bearer ${jwtToken}`);

    const envelopesApi = new EnvelopesApi(DocuSignClient);
    const envelopById: Record<string, Envelope> = {};
    const lastOrderIndex = orderRequests.length - 1;
    const orderDirection =
      new Date(orderRequests[0].created).getTime() <
      new Date(orderRequests[lastOrderIndex].created).getTime(); // true mean ascending
    const { envelopes } = await envelopesApi.listStatusChanges(
      process.env.DOCUSIGN_ACCOUNT_ID,
      {
        fromDate: orderDirection
          ? oneHourToStringDate(orderRequests[0].created, true)
          : oneHourToStringDate(orderRequests[lastOrderIndex].created, true),
        toDate: orderDirection
          ? oneHourToStringDate(orderRequests[lastOrderIndex].updated, false)
          : oneHourToStringDate(orderRequests[0].updated, false),
      },
    );

    envelopes.forEach((envelop) => {
      envelopById[envelop.envelopeId] = envelop;
      return envelop.envelopeId;
    });
    return orderRequests
      .filter((orderRequest) => envelopById[orderRequest.envelopId])
      .map((orderRequest) => {
        return {
          ...orderRequest,
          docuSignStatus: envelopById[orderRequest.envelopId]
            .status as ContractStatus,
        };
      });
  }

  async getContracts(
    userJwtPayload: UserJwtPayload,
    filterContractStatusDto: FilterContractStatusDto,
  ) {
    try {
      const orderRequests: LeanDocument<OrderRequestDocument>[] = [];
      if (userJwtPayload.role === EndUserRoles.fundAdmin) {
        const user = await this.userService.findUserByProperty({
          email: userJwtPayload.email,
        });
        orderRequests.push(
          ...(await this.orderRequestService.getRequestedOrders(
            filterContractStatusDto,
            "fundId",
            user.subscribedFunds.map((fund) => fund.toString()),
          )),
        );
      } else {
        orderRequests.push(
          ...(await this.orderRequestService.getRequestedOrders(
            filterContractStatusDto,
          )),
        );
      }
      return this.addDocusignStatusToOrdersRequest(orderRequests);
    } catch (error) {
      console.error(error);
      throw new ServiceUnavailableException(error);
    }
  }

  async fetchEnvelop(orderId: string, user: UserJwtPayload) {
    const [jwtToken, requestOrders] = await Promise.all([
      this.getValidToken(),
      this.orderRequestService.getRequestedOrders({ orderId }),
    ]);
    if (!requestOrders && !requestOrders.length) {
      return {};
    }
    if (user.role === EndUserRoles.dealer) {
      await this.orderRequestService.checkIfUserIsAllowedToAccessOrder(
        user.email,
        orderId,
      );
    }
    await this.userService.checkIfUserIsAllowedToFund(
      user,
      requestOrders[0].fundId,
    );

    const [{ envelopId }] = requestOrders;
    DocuSignClient.addDefaultHeader("Authorization", `Bearer ${jwtToken}`);
    return await this.envelopesApi.getEnvelope(
      process.env.DOCUSIGN_ACCOUNT_ID,
      envelopId,
      null,
    );
  }

  async fetchCustomFields(orderId: string) {
    const [jwtToken, requestOrders] = await Promise.all([
      this.getValidToken(),
      this.orderRequestService.getRequestedOrders({ orderId }),
    ]);
    if (!requestOrders && !requestOrders.length) {
      return {};
    }
    const [{ envelopId }] = requestOrders;
    DocuSignClient.addDefaultHeader("Authorization", `Bearer ${jwtToken}`);
    return await this.envelopesApi.listCustomFields(
      process.env.DOCUSIGN_ACCOUNT_ID,
      envelopId,
      null,
    );
  }

  async fetchDocument(orderId: string, user: UserJwtPayload) {
    const [jwtToken, requestOrders] = await Promise.all([
      this.getValidToken(),
      this.orderRequestService.getRequestedOrders({ orderId }),
    ]);
    if (!requestOrders && !requestOrders.length) {
      return {};
    }
    if (user.role === EndUserRoles.dealer) {
      await this.orderRequestService.checkIfUserIsAllowedToAccessOrder(
        user.email,
        orderId,
      );
    }
    await this.userService.checkIfUserIsAllowedToFund(
      user,
      requestOrders[0].fundId,
    );

    const [{ envelopId }] = requestOrders;
    DocuSignClient.addDefaultHeader("Authorization", `Bearer ${jwtToken}`);
    const result = await this.envelopesApi.getDocument(
      process.env.DOCUSIGN_ACCOUNT_ID,
      envelopId,
      "1",
      null,
    );
    const documentBase64 = Buffer.from(result, "binary").toString("base64");
    return documentBase64;
  }
}

results matching ""

    No results matching ""