Skip to content

Commit 6e9dd1f

Browse files
add code example (#231)
1 parent 356c112 commit 6e9dd1f

File tree

6 files changed

+467
-100
lines changed

6 files changed

+467
-100
lines changed

index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const {
3030
eg024, eg025, eg026, eg027, eg028, eg029, eg030,
3131
eg031, eg032, eg033, eg034, eg035, eg036, eg037,
3232
eg038, eg039, eg040, eg041, eg042, eg043, eg044,
33-
eg045
33+
eg045, eg046
3434
} = require('./lib/eSignature/controllers');
3535

3636
const {
@@ -288,7 +288,9 @@ app.get('/eg001', eg001.getController)
288288
.get('/eg045', eg045.getDeleteController)
289289
.post('/eg045', eg045.deleteController)
290290
.get('/eg045restore', eg045.getRestoreController)
291-
.post('/eg045restore', eg045.restoreController);
291+
.post('/eg045restore', eg045.restoreController)
292+
.get('/eg046', eg046.getController)
293+
.post('/eg046', eg046.createController);
292294

293295
app.get('/cneg001', eg001connect.getController)
294296
.post('/cneg001', eg001connect.createController);
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/**
2+
* @file
3+
* Example 046: Request a signature bt multiple delivery channels
4+
* @author DocuSign
5+
*/
6+
7+
const path = require('path');
8+
const { sendByMultipleChannels } = require('../examples/multipleDelivery');
9+
const validator = require('validator');
10+
const { getExampleByNumber } = require('../../manifestService');
11+
const dsConfig = require('../../../config/index.js').config;
12+
const { formatString, API_TYPES, isCFR } = require('../../utils.js');
13+
14+
const eg046MultipleDelivery = exports;
15+
const exampleNumber = 46;
16+
const eg = `eg0${exampleNumber}`; // This example reference.
17+
const api = API_TYPES.ESIGNATURE;
18+
const mustAuthenticate = '/ds/mustAuthenticate';
19+
const minimumBufferMin = 3;
20+
const demoDocsPath = path.resolve(__dirname, '../../../demo_documents');
21+
const doc2File = 'World_Wide_Corp_Battle_Plan_Trafalgar.docx';
22+
const doc3File = 'World_Wide_Corp_lorem.pdf';
23+
24+
25+
/**
26+
* Create the envelope
27+
* @param {object} req Request obj
28+
* @param {object} res Response obj
29+
*/
30+
eg046MultipleDelivery.createController = async (req, res) => {
31+
// Step 1. Check the token
32+
// At this point we should have a good token. But we
33+
// double-check here to enable a better UX to the user.
34+
const isTokenOK = req.dsAuth.checkToken(minimumBufferMin);
35+
if (!isTokenOK) {
36+
req.flash('info', 'Sorry, you need to re-authenticate.');
37+
// Save the current operation so it will be resumed after authentication
38+
req.dsAuth.setEg(req, eg);
39+
return res.redirect(mustAuthenticate);
40+
}
41+
42+
// Step 2. Call the worker method
43+
const { body } = req;
44+
const envelopeArgs = {
45+
deliveryMethod: validator.escape(body.deliveryMethod),
46+
signerName: validator.escape(body.signerName),
47+
signerEmail: validator.escape(body.signerEmail),
48+
countryCode: validator.escape(body.countryCode),
49+
phoneNumber: validator.escape(body.phoneNumber),
50+
ccName: validator.escape(body.ccName),
51+
ccEmail: validator.escape(body.ccEmail),
52+
ccCountryCode: validator.escape(body.ccCountryCode),
53+
ccPhoneNumber: validator.escape(body.ccPhoneNumber),
54+
status: 'sent',
55+
doc2File: path.resolve(demoDocsPath, doc2File),
56+
doc3File: path.resolve(demoDocsPath, doc3File)
57+
};
58+
const args = {
59+
accessToken: req.user.accessToken,
60+
basePath: req.session.basePath,
61+
accountId: req.session.accountId,
62+
envelopeArgs: envelopeArgs
63+
};
64+
let results = null;
65+
66+
try {
67+
results = await sendByMultipleChannels(args);
68+
} catch (error) {
69+
const errorBody = error?.body || error?.response?.body;
70+
// we can pull the DocuSign error code and message from the response body
71+
const errorCode = errorBody?.errorCode || errorBody?.error;
72+
let errorMessage = errorBody?.message || errorBody?.error_description;
73+
74+
if (errorMessage.includes('ACCOUNT_LACKS_PERMISSIONS')) {
75+
errorMessage = example.CustomErrorTexts[0].ErrorMessage;
76+
}
77+
// In production, may want to provide customized error messages and
78+
// remediation advice to the user.
79+
res.render('pages/error', {err: error, errorCode, errorMessage});
80+
}
81+
82+
if (results) {
83+
req.session.envelopeId = results.envelopeId; // Save for use by other examples
84+
// which need an envelopeId
85+
const example = getExampleByNumber(res.locals.manifest, exampleNumber, api);
86+
res.render('pages/example_done', {
87+
title: example.ExampleName,
88+
message: formatString(example.ResultsPageText, results.envelopeId)
89+
});
90+
}
91+
};
92+
93+
/**
94+
* Form page for this application
95+
*/
96+
eg046MultipleDelivery.getController = async (req, res) => {
97+
// Check that the authentication token is ok with a long buffer time.
98+
// If needed, now is the best time to ask the user to authenticate
99+
// since they have not yet entered any information into the form.
100+
const isTokenOK = req.dsAuth.checkToken();
101+
if (!isTokenOK) {
102+
// Save the current operation so it will be resumed after authentication
103+
req.dsAuth.setEg(req, eg);
104+
return res.redirect(mustAuthenticate);
105+
}
106+
107+
let enableCFR = await isCFR(req.user.accessToken, req.session.accountId, req.session.basePath);
108+
if (enableCFR === 'enabled'){
109+
res.locals.statusCFR = 'enabled';
110+
}
111+
112+
const example = getExampleByNumber(res.locals.manifest, exampleNumber, api);
113+
const sourceFile = (path.basename(__filename))[5].toLowerCase() + (path.basename(__filename)).substr(6);
114+
if (res.locals.statusCFR === 'enabled') {
115+
res.render('pages/invalid_with_cfr', {
116+
title: 'Not CFR Part 11 compatible'
117+
});
118+
} else {
119+
res.render('pages/examples/eg046MultipleDelivery', {
120+
eg: eg, csrfToken: req.csrfToken(),
121+
example: example,
122+
sourceFile: sourceFile,
123+
sourceUrl: dsConfig.githubExampleUrl + 'eSignature/examples/' + sourceFile,
124+
documentation: dsConfig.documentation + eg,
125+
showDoc: dsConfig.documentation
126+
});
127+
}
128+
};

lib/eSignature/controllers/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,4 @@ module.exports.eg042 = require('./eg042DocumentGeneration');
4242
module.exports.eg043 = require('./eg043SharedAccess');
4343
module.exports.eg044 = require('./eg044FocusedView');
4444
module.exports.eg045 = require('./eg045DeleteRestoreEnvelope');
45+
module.exports.eg046 = require('./eg046MultipleDelivery');
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
/**
2+
* @file
3+
* Example 046: Request a signature bt multiple delivery channels
4+
* @author DocuSign
5+
*/
6+
7+
const fs = require('fs-extra');
8+
const docusign = require('docusign-esign');
9+
10+
/**
11+
* This function does the work of creating the envelope and sending it by multiple channels
12+
*/
13+
const sendByMultipleChannels = async (args) => {
14+
// Data for this method
15+
// args.basePath
16+
// args.accessToken
17+
// args.accountId
18+
19+
const dsApiClient = new docusign.ApiClient();
20+
dsApiClient.setBasePath(args.basePath);
21+
dsApiClient.addDefaultHeader('Authorization', 'Bearer ' + args.accessToken);
22+
const envelopesApi = new docusign.EnvelopesApi(dsApiClient);
23+
let results = null;
24+
25+
// Make the envelope request body
26+
const envelope = makeEnvelope(args.envelopeArgs);
27+
28+
// call Envelopes::create API method
29+
// Exceptions will be caught by the calling function
30+
//ds-snippet-start:eSign46Step3
31+
results = await envelopesApi.createEnvelope(args.accountId, {
32+
envelopeDefinition: envelope,
33+
});
34+
//ds-snippet-end:eSign46Step3
35+
const envelopeId = results.envelopeId;
36+
37+
console.log(`Envelope was created. EnvelopeId ${envelopeId}`);
38+
return { envelopeId: envelopeId };
39+
};
40+
41+
/**
42+
* Creates envelope
43+
* @function
44+
* @param {Object} args parameters for the envelope
45+
* @returns {Envelope} An envelope definition
46+
* @private
47+
*/
48+
//ds-snippet-start:eSign46Step2
49+
function makeEnvelope(args) {
50+
// Data for this method
51+
// args.signerName
52+
// args.phoneNumber
53+
// args.countryCode
54+
// args.ccName
55+
// args.ccPhoneNumber
56+
// args.ccCountryCode
57+
// args.status
58+
// doc2File
59+
// doc3File
60+
61+
// document 1 (html) has tag **signature_1**
62+
// document 2 (docx) has tag /sn1/
63+
// document 3 (pdf) has tag /sn1/
64+
//
65+
// The envelope has two recipients.
66+
// recipient 1 - signer
67+
// recipient 2 - cc
68+
// The envelope will be sent first to the signer.
69+
// After it is signed, a copy is sent to the cc person.
70+
71+
let doc2DocxBytes, doc3PdfBytes;
72+
// read files from a local directory
73+
// The reads could raise an exception if the file is not available!
74+
doc2DocxBytes = fs.readFileSync(args.doc2File);
75+
doc3PdfBytes = fs.readFileSync(args.doc3File);
76+
77+
// create the envelope definition
78+
const env = new docusign.EnvelopeDefinition();
79+
env.emailSubject = 'Please sign this document set';
80+
81+
// add the documents
82+
const doc1 = new docusign.Document();
83+
const doc1b64 = Buffer.from(document1(args)).toString('base64');
84+
const doc2b64 = Buffer.from(doc2DocxBytes).toString('base64');
85+
const doc3b64 = Buffer.from(doc3PdfBytes).toString('base64');
86+
doc1.documentBase64 = doc1b64;
87+
doc1.name = 'Order acknowledgement'; // can be different from actual file name
88+
doc1.fileExtension = 'html'; // Source data format. Signed docs are always pdf.
89+
doc1.documentId = '1'; // a label used to reference the doc
90+
91+
// Alternate pattern: using constructors for docs 2 and 3...
92+
const doc2 = new docusign.Document.constructFromObject({
93+
documentBase64: doc2b64,
94+
name: 'Battle Plan', // can be different from actual file name
95+
fileExtension: 'docx',
96+
documentId: '2',
97+
});
98+
99+
const doc3 = new docusign.Document.constructFromObject({
100+
documentBase64: doc3b64,
101+
name: 'Lorem Ipsum', // can be different from actual file name
102+
fileExtension: 'pdf',
103+
documentId: '3',
104+
});
105+
106+
// The order in the docs array determines the order in the envelope
107+
env.documents = [doc1, doc2, doc3];
108+
109+
// Create a RecipientPhoneNumber object for the signer's phone number
110+
const signerPhoneNumber = docusign.RecipientPhoneNumber.constructFromObject({
111+
countryCode: args.countryCode,
112+
number: args.phoneNumber,
113+
});
114+
115+
const signerAdditionalNotification = docusign.RecipientAdditionalNotification.constructFromObject({
116+
secondaryDeliveryMethod: args.deliveryMethod,
117+
phoneNumber: signerPhoneNumber
118+
});
119+
120+
// Create a signer recipient to sign the document, identified by name and phone number
121+
// We're setting the parameters via the object constructor
122+
const signer = docusign.Signer.constructFromObject({
123+
name: args.signerName,
124+
email: args.signerEmail,
125+
deliveryMethod: 'Email',
126+
additionalNotifications: [signerAdditionalNotification],
127+
recipientId: '1',
128+
routingOrder: '1',
129+
});
130+
131+
// routingOrder (lower means earlier) determines the order of deliveries
132+
// to the recipients. Parallel routing order is supported by using the
133+
// same integer as the order for two or more recipients.
134+
135+
// Create a RecipientPhoneNumber object for the signer's phone number
136+
const ccPhoneNumber = docusign.RecipientPhoneNumber.constructFromObject({
137+
countryCode: args.ccCountryCode,
138+
number: args.ccPhoneNumber,
139+
});
140+
141+
const ccAdditionalNotification = docusign.RecipientAdditionalNotification.constructFromObject({
142+
secondaryDeliveryMethod: args.deliveryMethod,
143+
phoneNumber: ccPhoneNumber
144+
});
145+
146+
// Create a cc recipient to receive a copy of the documents, identified by name and phone number
147+
// We're setting the parameters via setters
148+
const cc = new docusign.CarbonCopy.constructFromObject({
149+
name: args.ccName,
150+
email: args.ccEmail,
151+
routingOrder: '2',
152+
recipientId: '2',
153+
deliveryMethod: 'Email',
154+
additionalNotifications: [ccAdditionalNotification],
155+
});
156+
157+
// Create signHere fields (also known as tabs) on the documents,
158+
// We're using anchor (autoPlace) positioning
159+
//
160+
// The DocuSign platform searches throughout your envelope's
161+
// documents for matching anchor strings. So the
162+
// signHere2 tab will be used in both document 2 and 3 since they
163+
// use the same anchor string for their "signer 1" tabs.
164+
const signHere1 = docusign.SignHere.constructFromObject({
165+
anchorString: '**signature_1**',
166+
anchorYOffset: '10',
167+
anchorUnits: 'pixels',
168+
anchorXOffset: '20',
169+
});
170+
const signHere2 = docusign.SignHere.constructFromObject({
171+
anchorString: '/sn1/',
172+
anchorYOffset: '10',
173+
anchorUnits: 'pixels',
174+
anchorXOffset: '20',
175+
});
176+
// Tabs are set per recipient / signer
177+
const signerTabs = docusign.Tabs.constructFromObject({
178+
signHereTabs: [signHere1, signHere2],
179+
});
180+
signer.tabs = signerTabs;
181+
182+
// Add the recipients to the envelope object
183+
const recipients = docusign.Recipients.constructFromObject({
184+
signers: [signer],
185+
carbonCopies: [cc],
186+
});
187+
env.recipients = recipients;
188+
189+
// Request that the envelope be sent by setting |status| to "sent".
190+
// To request that the envelope be created as a draft, set to "created"
191+
env.status = args.status;
192+
193+
return env;
194+
}
195+
196+
/**
197+
* Creates document 1
198+
* @function
199+
* @private
200+
* @param {Object} args parameters for the envelope
201+
* @returns {string} A document in HTML format
202+
*/
203+
204+
function document1(args) {
205+
// Data for this method
206+
// args.signerName
207+
// args.ccName
208+
209+
return `
210+
<!DOCTYPE html>
211+
<html>
212+
<head>
213+
<meta charset="UTF-8">
214+
</head>
215+
<body style="font-family:sans-serif;margin-left:2em;">
216+
<h1 style="font-family: 'Trebuchet MS', Helvetica, sans-serif;
217+
color: darkblue;margin-bottom: 0;">World Wide Corp</h1>
218+
<h2 style="font-family: 'Trebuchet MS', Helvetica, sans-serif;
219+
margin-top: 0px;margin-bottom: 3.5em;font-size: 1em;
220+
color: darkblue;">Order Processing Division</h2>
221+
<h4>Ordered by ${args.signerName}</h4>
222+
<p style="margin-top:0em; margin-bottom:0em;">Phone number: ${args.phoneNumber}</p>
223+
<p style="margin-top:0em; margin-bottom:0em;">Copy to: ${args.ccName}, ${args.ccPhoneNumber}</p>
224+
<p style="margin-top:3em;">
225+
Candy bonbon pastry jujubes lollipop wafer biscuit biscuit. Topping brownie sesame snaps sweet roll pie. Croissant danish biscuit soufflé caramels jujubes jelly. Dragée danish caramels lemon drops dragée. Gummi bears cupcake biscuit tiramisu sugar plum pastry. Dragée gummies applicake pudding liquorice. Donut jujubes oat cake jelly-o. Dessert bear claw chocolate cake gummies lollipop sugar plum ice cream gummies cheesecake.
226+
</p>
227+
<!-- Note the anchor tag for the signature field is in white. -->
228+
<h3 style="margin-top:3em;">Agreed: <span style="color:white;">**signature_1**/</span></h3>
229+
</body>
230+
</html>
231+
`;
232+
}
233+
//ds-snippet-end:eSign46Step2
234+
module.exports = { sendByMultipleChannels };

0 commit comments

Comments
 (0)