Skip to content

Commit 403ec2e

Browse files
added responsive signing
1 parent c7afab4 commit 403ec2e

File tree

6 files changed

+365
-1
lines changed

6 files changed

+365
-1
lines changed

index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ const {
2727
eg009, eg010, eg011, eg012, eg013, eg014, eg015,
2828
eg016, eg017, eg018, eg019, eg020, eg022, eg023,
2929
eg024, eg025, eg026, eg027, eg028, eg029, eg030,
30-
eg031, eg032, eg033, eg034, eg035, eg036, eg037
30+
eg031, eg032, eg033, eg034, eg035, eg036, eg037,
31+
eg038
3132
} = require("./lib/eSignature/controllers");
3233

3334
const {
@@ -223,6 +224,8 @@ if (examplesApi.examplesApi.isRoomsApi) {
223224
.post('/eg036', eg036.createController)
224225
.get('/eg037', eg037.getController)
225226
.post('/eg037', eg037.createController)
227+
.get('/eg038', eg038.getController)
228+
.post('/eg038', eg038.createController)
226229
}
227230

228231
function dsLoginCB1(req, res, next) { req.dsAuthCodeGrant.oauth_callback1(req, res, next) }
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* @file
3+
* Example 038: Signable HTML document
4+
* @author DocuSign
5+
*/
6+
7+
const path = require('path');
8+
const validator = require('validator');
9+
const dsConfig = require('../../../config/index.js').config;
10+
const { sendEnvelope } = require('../examples/responsiveSigning');
11+
12+
const eg038ResponsiveSigning = exports;
13+
const eg = 'eg038'; // This example reference.
14+
const mustAuthenticate = '/ds/mustAuthenticate';
15+
const minimumBufferMin = 3;
16+
const demoDocsPath = path.resolve(__dirname, '../../../demo_documents');
17+
const docFile = 'order_form.html';
18+
const dsReturnUrl = dsConfig.appUrl + '/ds-return';
19+
const dsPingUrl = dsConfig.appUrl + '/'; // Url that will be pinged by the DocuSign signing via Ajax
20+
21+
/**
22+
* Create the envelope
23+
* @param {object} req Request obj
24+
* @param {object} res Response obj
25+
*/
26+
eg038ResponsiveSigning.createController = async (req, res) => {
27+
// Step 1. Check the token
28+
// At this point we should have a good token. But we
29+
// double-check here to enable a better UX to the user.
30+
const tokenOK = req.dsAuth.checkToken(minimumBufferMin);
31+
if (! tokenOK) {
32+
req.flash('info', 'Sorry, you need to re-authenticate.');
33+
// Save the current operation so it will be resumed after authentication
34+
req.dsAuth.setEg(req, eg);
35+
res.redirect(mustAuthenticate);
36+
}
37+
38+
// Step 2. Call the worker method
39+
const { body } = req;
40+
const envelopeArgs = {
41+
signerEmail: validator.escape(body.signerEmail),
42+
signerName: validator.escape(body.signerName),
43+
ccEmail: validator.escape(body.ccEmail),
44+
ccName: validator.escape(body.ccName),
45+
status: 'sent',
46+
docFile: path.resolve(demoDocsPath, docFile),
47+
dsReturnUrl: dsReturnUrl,
48+
dsPingUrl: dsPingUrl
49+
};
50+
const args = {
51+
accessToken: req.user.accessToken,
52+
basePath: req.session.basePath,
53+
accountId: req.session.accountId,
54+
envelopeArgs: envelopeArgs
55+
};
56+
let results = null;
57+
58+
try {
59+
results = await sendEnvelope(args);
60+
}
61+
catch (error) {
62+
const errorBody = error && error.response && error.response.body;
63+
// we can pull the DocuSign error code and message from the response body
64+
const errorCode = errorBody && errorBody.errorCode;
65+
const errorMessage = errorBody && errorBody.message;
66+
// In production, may want to provide customized error messages and
67+
// remediation advice to the user.
68+
res.render('pages/error', {err: error, errorCode, errorMessage});
69+
}
70+
if (results) {
71+
res.redirect(results.redirectUrl);
72+
}
73+
}
74+
75+
/**
76+
* Form page for this application
77+
*/
78+
eg038ResponsiveSigning.getController = (req, res) => {
79+
// Check that the authentication token is ok with a long buffer time.
80+
// If needed, now is the best time to ask the user to authenticate
81+
// since they have not yet entered any information into the form.
82+
const tokenOK = req.dsAuth.checkToken();
83+
if (tokenOK) {
84+
sourceFile = (path.basename(__filename))[5].toLowerCase() + (path.basename(__filename)).substr(6);
85+
res.render('pages/examples/eg038ResponsiveSigning', {
86+
eg: eg, csrfToken: req.csrfToken(),
87+
title: "Signable HTML document",
88+
sourceFile: sourceFile,
89+
sourceUrl: dsConfig.githubExampleUrl + 'eSignature/examples/' + sourceFile,
90+
documentation: dsConfig.documentation + eg,
91+
showDoc: dsConfig.documentation
92+
});
93+
} else {
94+
// Save the current operation so it will be resumed after authentication
95+
req.dsAuth.setEg(req, eg);
96+
res.redirect(mustAuthenticate);
97+
}
98+
}

lib/eSignature/controllers/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,4 @@ module.exports.eg034 = require('./eg034UseConditionalRecipients');
3434
module.exports.eg035 = require('./eg035ScheduledSending');
3535
module.exports.eg036 = require('./eg036DelayedRouting');
3636
module.exports.eg037 = require('./eg037SmsDelivery');
37+
module.exports.eg038 = require('./eg038ResponsiveSigning');
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
/**
2+
* @file
3+
* Example 038: Signable HTML document
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 in
12+
* draft mode and returning a URL for the sender's view
13+
* @param {object} args object
14+
*/
15+
const sendEnvelope = async (args) => {
16+
// Data for this method
17+
// args.basePath
18+
// args.accessToken
19+
// args.accountId
20+
21+
let dsApiClient = new docusign.ApiClient();
22+
dsApiClient.setBasePath(args.basePath);
23+
dsApiClient.addDefaultHeader("Authorization", "Bearer " + args.accessToken);
24+
let envelopesApi = new docusign.EnvelopesApi(dsApiClient);
25+
26+
// Step 1. Make the envelope body
27+
let envelope = makeEnvelope(args.envelopeArgs);
28+
29+
// Step 2. Send the envelope
30+
let results = await envelopesApi.createEnvelope(args.accountId, {
31+
envelopeDefinition: envelope,
32+
});
33+
let envelopeId = results.envelopeId;
34+
35+
// Step 3. Create the recipient view
36+
let viewRequest = makeRecipientViewRequest(args.envelopeArgs);
37+
38+
// Call the CreateRecipientView API
39+
// Exceptions will be caught by the calling function
40+
results = await envelopesApi.createRecipientView(args.accountId, envelopeId, {
41+
recipientViewRequest: viewRequest,
42+
});
43+
44+
return { envelopeId: envelopeId, redirectUrl: results.url };
45+
};
46+
47+
/**
48+
* Creates envelope
49+
* @function
50+
* @param {Object} args parameters for the envelope
51+
* @returns {Envelope} An envelope definition
52+
* @private
53+
*/
54+
function makeEnvelope(args) {
55+
// Data for this method
56+
// args.signerEmail
57+
// args.signerName
58+
// args.ccEmail
59+
// args.ccName
60+
// args.status
61+
62+
// document (html) has tag **signature_1**
63+
//
64+
// The envelope has two recipients.
65+
// recipient 1 - signer
66+
// recipient 2 - cc
67+
// The envelope will be sent first to the signer.
68+
// After it is signed, a copy is sent to the cc person.
69+
70+
// create the envelope definition
71+
let env = new docusign.EnvelopeDefinition();
72+
env.emailSubject = "Example Signing Document";
73+
74+
// add the document
75+
76+
let htmlDefinition = new docusign.DocumentHtmlDefinition();
77+
htmlDefinition.source = getHTMLDocument(args);
78+
79+
let document = new docusign.Document();
80+
document.name = "doc1.html"; // can be different from actual file name
81+
document.documentId = "1"; // a label used to reference the doc
82+
document.htmlDefinition = htmlDefinition;
83+
84+
// The order in the docs array determines the order in the envelope
85+
env.documents = [document];
86+
87+
// create a signer recipient to sign the document, identified by name and email
88+
// We're setting the parameters via the object constructor
89+
let signer = docusign.Signer.constructFromObject({
90+
email: args.signerEmail,
91+
name: args.signerName,
92+
recipientId: "1",
93+
routingOrder: "1",
94+
});
95+
// routingOrder (lower means earlier) determines the order of deliveries
96+
// to the recipients. Parallel routing order is supported by using the
97+
// same integer as the order for two or more recipients.
98+
99+
// create a cc recipient to receive a copy of the documents, identified by name and email
100+
// We're setting the parameters via setters
101+
let cc = new docusign.CarbonCopy();
102+
cc.email = args.ccEmail;
103+
cc.name = args.ccName;
104+
cc.routingOrder = "2";
105+
cc.recipientId = "2";
106+
107+
// Create signHere fields (also known as tabs) on the documents
108+
let signHere = docusign.SignHere.constructFromObject({
109+
stampType: "signature",
110+
name: "SignHere",
111+
tabLabel: "signatureTab",
112+
scaleValue: "1",
113+
optional: "false",
114+
documentId: "1",
115+
recipientId: "1",
116+
pageNumber: "1",
117+
xPosition: "143",
118+
yPosition: "440"
119+
});
120+
121+
// Tabs are set per recipient / signer
122+
let signerTabs = docusign.Tabs.constructFromObject({
123+
signHereTabs: [signHere],
124+
});
125+
signer.tabs = signerTabs;
126+
127+
// Add the recipients to the envelope object
128+
let recipients = docusign.Recipients.constructFromObject({
129+
signers: [signer],
130+
carbonCopies: [cc],
131+
});
132+
env.recipients = recipients;
133+
134+
env.status = args.status;
135+
136+
return env;
137+
}
138+
139+
/**
140+
* Gets the HTML document
141+
* @function
142+
* @private
143+
* @param {Object} args parameters for the envelope
144+
* @returns {string} A document in HTML format
145+
*/
146+
147+
function getHTMLDocument(args) {
148+
// Data for this method
149+
// args.signerEmail
150+
// args.signerName
151+
// args.ccEmail
152+
// args.ccName
153+
154+
let docHTMLContent = fs.readFileSync(args.docFile, { encoding: "utf8" });
155+
156+
// Substitute values into the HTML
157+
// Substitute for: {signerName}, {signerEmail}, {ccName}, {ccEmail}
158+
return docHTMLContent
159+
.replace("{signerName}", args.signerName)
160+
.replace("{signerEmail}", args.signerEmail)
161+
.replace("{ccName}", args.ccName)
162+
.replace("{ccEmail}", args.ccEmail);
163+
}
164+
165+
function makeRecipientViewRequest(args) {
166+
// Data for this method
167+
// args.dsReturnUrl
168+
// args.signerEmail
169+
// args.signerName
170+
// args.signerClientId
171+
// args.dsPingUrl
172+
173+
let viewRequest = new docusign.RecipientViewRequest();
174+
175+
// Set the url where you want the recipient to go once they are done signing
176+
// should typically be a callback route somewhere in your app.
177+
// The query parameter is included as an example of how
178+
// to save/recover state information during the redirect to
179+
// the DocuSign signing. It's usually better to use
180+
// the session mechanism of your web framework. Query parameters
181+
// can be changed/spoofed very easily.
182+
viewRequest.returnUrl = args.dsReturnUrl + "?state=123";
183+
184+
// How has your app authenticated the user? In addition to your app's
185+
// authentication, you can include authenticate steps from DocuSign.
186+
// Eg, SMS authentication
187+
viewRequest.authenticationMethod = "none";
188+
189+
// Recipient information must match embedded recipient info
190+
// we used to create the envelope.
191+
viewRequest.email = args.signerEmail;
192+
viewRequest.userName = args.signerName;
193+
viewRequest.clientUserId = args.signerClientId;
194+
195+
// DocuSign recommends that you redirect to DocuSign for the
196+
// embedded signing. There are multiple ways to save state.
197+
// To maintain your application's session, use the pingUrl
198+
// parameter. It causes the DocuSign signing web page
199+
// (not the DocuSign server) to send pings via AJAX to your
200+
// app,
201+
viewRequest.pingFrequency = 600; // seconds
202+
// NOTE: The pings will only be sent if the pingUrl is an https address
203+
viewRequest.pingUrl = args.dsPingUrl; // optional setting
204+
205+
return viewRequest;
206+
}
207+
208+
module.exports = { sendEnvelope };
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<% include ../../partials/examplesHead %>
2+
3+
<h4>38. Signable HTML document</h4>
4+
<p>This example demonstrates how to create an HTML document for responsive signing.</p>
5+
6+
<% include ../../partials/docBody %>
7+
8+
<p>API method used:
9+
<a target ='_blank' href="https://developers.docusign.com/docs/esign-rest-api/reference/envelopes/envelopes/create/">Envelopes::create</a> and
10+
<a target ='_blank' href="https://developers.docusign.com/docs/esign-rest-api/reference/envelopes/envelopeviews/createrecipient/">EnvelopeViews::createRecipient</a>.
11+
</p>
12+
13+
<% include ../../partials/gitSource %>
14+
<form class="eg" action="" method="post" data-busy="form">
15+
<div class="form-group">
16+
<label for="signerEmail">Signer Email</label>
17+
<input type="email" class="form-control" id="signerEmail" name="signerEmail"
18+
aria-describedby="emailHelp" placeholder="pat@example.com" required
19+
value="<%= locals.dsConfig.signerEmail %>">
20+
<small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.</small>
21+
</div>
22+
<div class="form-group">
23+
<label for="signerName">Signer Name</label>
24+
<input type="text" class="form-control" id="signerName" placeholder="Pat Johnson" name="signerName"
25+
value="<%= locals.dsConfig.signerName %>" required>
26+
</div>
27+
<div class="form-group">
28+
<label for="ccEmail">CC Email</label>
29+
<input type="email" class="form-control" id="ccEmail" name="ccEmail"
30+
aria-describedby="emailHelp" placeholder="pat@example.com" required
31+
<small id="emailHelp" class="form-text text-muted">The email for the cc recipient must be different from the signer's email.</small>
32+
</div>
33+
<div class="form-group">
34+
<label for="ccName">CC Name</label>
35+
<input type="text" class="form-control" id="ccName" placeholder="Pat Johnson" name="ccName"
36+
required>
37+
</div>
38+
<input type="hidden" name="_csrf" value="<%- csrfToken %>">
39+
<button type="submit" class="btn btn-docu">Submit</button>
40+
</form>
41+
42+
<% include ../../partials/examplesFoot %>

views/pages/index_esignature_examples.ejs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,3 +301,15 @@ then applies the brand to it. <a target='_blank'
301301
API method used:
302302
<a target="_blank" href="https://developers.docusign.com/docs/esign-rest-api/reference/envelopes/envelopes/create/">Envelopes::create</a>.
303303
</p>
304+
305+
<h2>Responsive signing</h2>
306+
307+
<h4 id="example038">38. <a href="eg038">Signable HTML document</a></h4>
308+
<p>
309+
Demonstrates how to create an HTML document for responsive signing.
310+
<p>
311+
API methods used:
312+
<a target="_blank" href="https://developers.docusign.com/docs/esign-rest-api/reference/envelopes/envelopes/create/">Envelopes::create</a>,
313+
<a target='_blank'
314+
href="https://developers.docusign.com/docs/esign-rest-api/reference/envelopes/envelopeviews/createrecipient/">EnvelopeViews::createRecipient</a>.
315+
</p>

0 commit comments

Comments
 (0)