"use server"; import docusign from "docusign-esign"; import fs from "fs"; import path from "path"; import jwtConfig from "./jwtConfig/jwtConfig.json"; import dayjs from "dayjs"; import { ContractInfo, ContractorInfo } from "./types"; const SCOPES = ["signature", "impersonation"]; //DocuSign 인증 정보 async function authenticate(): Promise< | undefined | { accessToken: string; apiAccountId: string; basePath: string; } > { const jwtLifeSec = 10 * 60; const dsApi = new docusign.ApiClient(); dsApi.setOAuthBasePath(jwtConfig.dsOauthServer.replace("https://", "")); const privateKeyPath = path.resolve( process.cwd(), jwtConfig.privateKeyLocation ); let rsaKey: Buffer = fs.readFileSync(privateKeyPath); try { const results = await dsApi.requestJWTUserToken( jwtConfig.dsJWTClientId, jwtConfig.impersonatedUserGuid, SCOPES, rsaKey, jwtLifeSec ); const accessToken = results.body.access_token; const userInfoResults = await dsApi.getUserInfo(accessToken); let userInfo = userInfoResults.accounts.find( (account: Partial<{ isDefault: string }>) => account.isDefault === "true" ); return { accessToken: results.body.access_token, apiAccountId: userInfo.accountId, basePath: `${userInfo.baseUri}/restapi`, }; } catch (e) { console.error("❌ 인증 실패:", e); } } async function getSignerId( basePath: string, accountId: string, accessToken: string, envelopeId: string, roleName: string ): Promise { const apiClient = new docusign.ApiClient(); apiClient.setBasePath(basePath); apiClient.addDefaultHeader("Authorization", "Bearer " + accessToken); const envelopesApi = new docusign.EnvelopesApi(apiClient); try { const recipients = await envelopesApi.listRecipients(accountId, envelopeId); const singers = recipients?.signers ?? []; // 🔹 특정 서명자(Role Name 기준)의 Recipient ID 찾기 const signer = singers.find((s) => s.roleName === roleName); if (!signer) { console.error("❌ 해당 Role Name을 가진 서명자를 찾을 수 없습니다."); return null; } return signer.recipientId as string; } catch (error) { console.error("❌ 서명자 ID 조회 실패:", error); return null; } } //계약서 서명 요청 export async function requestContractSign( contractTemplateId: string, contractInfo: ContractInfo[], subcontractorinfo: ContractorInfo, contractorInfo: ContractorInfo, ccInfo: ContractorInfo[], brandId: string | undefined = undefined ): Promise< Partial<{ result: boolean; envelopeId: string; error: any; }> > { let accountInfo = await authenticate(); if (accountInfo) { const { accessToken, basePath, apiAccountId } = accountInfo; const { email: subEmail, name: subConName, roleName: subRoleName, } = subcontractorinfo; const { email: conEmail, name: conName, roleName: roleName, } = contractorInfo; const apiClient = new docusign.ApiClient(); apiClient.setBasePath(basePath); apiClient.addDefaultHeader("Authorization", "Bearer " + accessToken); const envelopesApi = new docusign.EnvelopesApi(apiClient); const signer1: docusign.TemplateRole = { email: subEmail, name: subConName, roleName: subRoleName, }; const signer1Tabs: docusign.Tabs = { textTabs: [ ...contractInfo.map((c): docusign.Text => { const textField: docusign.Text = { tabLabel: c.tabLabel, value: c.value, locked: "true", }; return textField; }), ], }; const signer2: docusign.TemplateRole = { email: conEmail, name: conName, roleName: roleName, }; const signer2Tabs: docusign.Tabs = { dateSignedTabs: [ { tabLabel: "contract_complete_date", }, ], }; signer1.tabs = signer1Tabs; signer2.tabs = signer2Tabs; const envelopeDefinition: docusign.EnvelopeDefinition = { templateId: contractTemplateId, templateRoles: [signer1, signer2, ...ccInfo], // 두 명의 서명자 추가 status: "sent", // 즉시 발송 }; if (brandId) { envelopeDefinition.brandId = brandId; } try { let envelopeSummary = await envelopesApi.createEnvelope(apiAccountId, { envelopeDefinition, }); // console.log("✅ 서명 요청 완료, Envelope ID:", envelopeSummary); return { result: true, envelopeId: envelopeSummary.envelopeId, }; } catch (error) { console.dir(error); return { result: false, error, }; } } else { return { result: false, }; } } //서명된 계약서 다운로드 export async function downloadContractFile(envelopeId: string): Promise< Partial<{ result: boolean; fileName: string; buffer: Buffer; envelopeId: string; error: any; }> > { let accountInfo = await authenticate(); if (accountInfo) { const { accessToken, apiAccountId, basePath } = accountInfo; const apiClient = new docusign.ApiClient(); apiClient.setBasePath(basePath); apiClient.addDefaultHeader("Authorization", "Bearer " + accessToken); const envelopesApi = new docusign.EnvelopesApi(apiClient); try { //Document ID 등 파일 정보를 호출 const response = await envelopesApi.listDocuments( apiAccountId, envelopeId, null ); const { envelopeDocuments } = response || { envelopeDocuments: [] }; if (Array.isArray(envelopeDocuments) && envelopeDocuments.length > 0) { const { documentId, name } = envelopeDocuments[0] as { documentId: string; name: string; }; //Document Buffer 호출 const downloadFile = await envelopesApi.getDocument( apiAccountId, envelopeId, documentId, {} ); if (documentId && documentId !== "certificate") { const bufferData: Buffer = downloadFile as unknown as Buffer; return { result: true, fileName: name, buffer: bufferData, envelopeId, }; } } return { result: false, }; } catch (error) { return { result: false, error, }; } } else { return { result: false, }; } } //최종 서명 날짜 찾기 export async function findContractCompleteTime( envelopeId: string, lastSignerRoleName: string ): Promise<{ completedDateTime: string; year: string; month: string; day: string; time: string; } | null> { let accountInfo = await authenticate(); if (!accountInfo) { console.error("❌ 인증 실패: API 요청을 중단합니다."); return null; } const { accessToken, apiAccountId: accountId, basePath } = accountInfo; const apiClient = new docusign.ApiClient(); apiClient.setBasePath(basePath); apiClient.addDefaultHeader("Authorization", "Bearer " + accessToken); const envelopesApi = new docusign.EnvelopesApi(apiClient); try { const envelope = await envelopesApi.getEnvelope(accountId, envelopeId); if (!envelope.completedDateTime) { console.error("❌ 서명 완료 날짜가 없습니다."); return null; } // 🔹 `SIGNER_ID` 가져오기 const signerId = await getSignerId( basePath, accountId, accessToken, envelopeId, lastSignerRoleName ); if (!signerId) { console.error("❌ 서명자 ID를 찾을 수 없습니다."); return null; } const completedDate = dayjs(envelope.completedDateTime); const year = completedDate.format("YYYY").toString(); const month = completedDate.format("MM").toString(); const day = completedDate.format("DD").toString(); const time = completedDate.format("HH:mm").toString(); return { completedDateTime: envelope.completedDateTime, year, month, day, time, }; } catch (error) { console.error("❌ 서명 완료 후 날짜 추가 실패:", error); return null; } } export async function getRecipients( envelopeId: string, recipientId: string ): Promise<{ result: boolean; message?: string }> { try { let accountInfo = await authenticate(); if (!accountInfo) { console.error("❌ 인증 실패: API 요청을 중단합니다."); return { result: false, message: "인증 실패: API 요청을 중단합니다.", }; } const { accessToken, apiAccountId: accountId, basePath } = accountInfo; const apiClient = new docusign.ApiClient(); apiClient.setBasePath(basePath); apiClient.addDefaultHeader("Authorization", "Bearer " + accessToken); const envelopesApi = new docusign.EnvelopesApi(apiClient); const response = await envelopesApi.listRecipients(accountId, envelopeId); const singers: { [key: string]: any }[] = response?.signers ?? []; // 🔹 특정 서명자(Role Name 기준)의 Recipient ID 찾기 const signer = singers.find((s) => s.recipientId === recipientId); if (!signer) { console.error("❌ 해당 Role Name을 가진 서명자를 찾을 수 없습니다."); return { result: false, message: "해당 Recipient id를 가진 서명자를 찾을 수 없습니다.", }; } const { autoRespondedReason, status } = signer; if (autoRespondedReason || status === "status") { return { result: false, message: autoRespondedReason, }; } return { result: true, }; } catch (error) { console.error("Error retrieving recipients:", error); return { result: false, message: (error as Error).message }; } }