src/docusign/docusign.service.ts
Properties |
|
Methods |
|
constructor()
|
|
Defined in src/docusign/docusign.service.ts:54
|
| Async addDocusignStatusToOrdersRequest | ||||||
addDocusignStatusToOrdersRequest(orderRequests: LeanDocument
|
||||||
|
Defined in src/docusign/docusign.service.ts:309
|
||||||
|
Parameters :
|
| Async createAnEnvelope | |||
createAnEnvelope(undefined)
|
|||
|
Defined in src/docusign/docusign.service.ts:81
|
|||
|
Parameters :
Returns :
Promise<EnvelopeSummary>
|
| Async fetchCustomFields | ||||||
fetchCustomFields(orderId: string)
|
||||||
|
Defined in src/docusign/docusign.service.ts:410
|
||||||
|
Parameters :
Returns :
unknown
|
| Async fetchDocument | |||||||||
fetchDocument(orderId: string, user: UserJwtPayload)
|
|||||||||
|
Defined in src/docusign/docusign.service.ts:427
|
|||||||||
|
Parameters :
Returns :
unknown
|
| Async fetchEnvelop | |||||||||
fetchEnvelop(orderId: string, user: UserJwtPayload)
|
|||||||||
|
Defined in src/docusign/docusign.service.ts:382
|
|||||||||
|
Parameters :
Returns :
unknown
|
| Async generateJwtToken |
generateJwtToken()
|
|
Defined in src/docusign/docusign.service.ts:65
|
|
Returns :
Promise<AccessTokenResponse>
|
| Async getContracts | |||||||||
getContracts(userJwtPayload: UserJwtPayload, filterContractStatusDto: FilterContractStatusDto)
|
|||||||||
|
Defined in src/docusign/docusign.service.ts:351
|
|||||||||
|
Parameters :
Returns :
unknown
|
| Async getDealerSubmittedDocuments | |||||
getDealerSubmittedDocuments(undefined: IGetEnvelopesByCustomField)
|
|||||
|
Defined in src/docusign/docusign.service.ts:212
|
|||||
|
Parameters :
|
| Async getInvestorSubmittedDocuments | |||||
getInvestorSubmittedDocuments(undefined: IGetEnvelopesByReceiverEmail)
|
|||||
|
Defined in src/docusign/docusign.service.ts:261
|
|||||
|
Parameters :
|
| Async getValidToken |
getValidToken()
|
|
Defined in src/docusign/docusign.service.ts:58
|
|
Returns :
Promise<string>
|
| Private Readonly envelopesApi |
Type : EnvelopesApi
|
|
Defined in src/docusign/docusign.service.ts:54
|
| Private Readonly orderRequestService |
Type : OrderRequestService
|
Decorators :
@Inject(OrderRequestService)
|
|
Defined in src/docusign/docusign.service.ts:51
|
| token |
Type : AccessTokenResponse
|
|
Defined in src/docusign/docusign.service.ts:53
|
| Private Readonly userService |
Type : UserService
|
Decorators :
@Inject(UserService)
|
|
Defined in src/docusign/docusign.service.ts:49
|
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;
}
}