Skip to content

Commit fc700f0

Browse files
Merge pull request #41 from docusign/updates
Updates
2 parents cedc1fe + 037afa1 commit fc700f0

File tree

6 files changed

+456
-2
lines changed

6 files changed

+456
-2
lines changed

app/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@
115115
app.register_blueprint(esignature_views.eg043)
116116
app.register_blueprint(esignature_views.eg044)
117117
app.register_blueprint(esignature_views.eg045)
118+
app.register_blueprint(esignature_views.eg046)
118119

119120
app.register_blueprint(connect_views.cneg001)
120121

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
import base64
2+
from datetime import datetime as dt, timezone
3+
from os import path
4+
5+
from docusign_esign import (
6+
EnvelopesApi,
7+
EnvelopeDefinition,
8+
Document,
9+
Signer,
10+
CarbonCopy,
11+
SignHere,
12+
Tabs,
13+
Recipients,
14+
RecipientPhoneNumber,
15+
RecipientAdditionalNotification
16+
)
17+
18+
from flask import session, request
19+
20+
from ...consts import demo_docs_path, pattern
21+
from ...docusign import create_api_client
22+
from ...ds_config import DS_CONFIG
23+
24+
25+
class Eg046MultipleDeliveryController:
26+
@staticmethod
27+
def get_args():
28+
"""Get request and session arguments"""
29+
30+
# More data validation would be a good idea here
31+
# Strip anything other than characters listed
32+
signer_name = pattern.sub("", request.form.get("signer_name"))
33+
signer_email = pattern.sub("", request.form.get("signer_email"))
34+
cc_name = pattern.sub("", request.form.get("cc_name"))
35+
cc_email = pattern.sub("", request.form.get("cc_email"))
36+
signer_phone_number = request.form.get("signer_phone_number")
37+
signer_country_code = request.form.get("signer_country_code")
38+
cc_phone_number = request.form.get("cc_phone_number")
39+
cc_country_code = request.form.get("cc_country_code")
40+
delivery_method = request.form["delivery_method"]
41+
envelope_args = {
42+
"signer_name": signer_name,
43+
"signer_email": signer_email,
44+
"status": "sent",
45+
"cc_name": cc_name,
46+
"cc_email": cc_email,
47+
"signer_country_code": signer_country_code,
48+
"signer_phone_number": signer_phone_number,
49+
"cc_country_code" :cc_country_code,
50+
"cc_phone_number": cc_phone_number,
51+
"delivery_method": delivery_method
52+
}
53+
args = {
54+
"account_id": session["ds_account_id"],
55+
"base_path": session["ds_base_path"],
56+
"access_token": session["ds_access_token"],
57+
"envelope_args": envelope_args
58+
}
59+
return args
60+
61+
@classmethod
62+
def worker(cls, args):
63+
"""
64+
1. Create the envelope request object
65+
2. Send the envelope
66+
"""
67+
68+
#ds-snippet-start:eSign46Step3
69+
envelope_args = args["envelope_args"]
70+
# Create the envelope request object
71+
api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"])
72+
envelope_definition = cls.make_envelope(envelope_args)
73+
# Call Envelopes::create API method
74+
# Exceptions will be caught by the calling function
75+
envelopes_api = EnvelopesApi(api_client)
76+
(results, status, headers) = envelopes_api.create_envelope_with_http_info(account_id=args["account_id"], envelope_definition=envelope_definition)
77+
78+
remaining = headers.get("X-RateLimit-Remaining")
79+
reset = headers.get("X-RateLimit-Reset")
80+
81+
if remaining is not None and reset is not None:
82+
reset_date = dt.fromtimestamp(int(reset), tz=timezone.utc)
83+
print(f"API calls remaining: {remaining}")
84+
print(f"Next Reset: {reset_date}")
85+
86+
envelope_id = results.envelope_id
87+
88+
return {"envelope_id": envelope_id}
89+
#ds-snippet-end:eSign46Step3
90+
91+
#ds-snippet-start:eSign46Step2
92+
@classmethod
93+
def make_envelope(cls, args):
94+
"""
95+
Creates envelope:
96+
document 1 (HTML) has signHere anchor tag: **signature_1**
97+
document 2 (DOCX) has signHere anchor tag: /sn1/
98+
document 3 (PDF) has signHere anchor tag: /sn1/
99+
DocuSign will convert all of the documents to the PDF format.
100+
The recipient’s field tags are placed using anchor strings.
101+
The envelope has two recipients:
102+
recipient 1: signer
103+
recipient 2: cc
104+
The envelope will be sent first to the signer via SMS.
105+
After it is signed, a copy is sent to the cc recipient via SMS.
106+
"""
107+
# Create the envelope definition
108+
env = EnvelopeDefinition(
109+
email_subject="Please sign this document set"
110+
)
111+
doc1_b64 = base64.b64encode(bytes(cls.create_document1(args), "utf-8")).decode("ascii")
112+
# Read files 2 and 3 from a local folder
113+
# The reads could raise an exception if the file is not available!
114+
with open(path.join(demo_docs_path, DS_CONFIG["doc_docx"]), "rb") as file:
115+
doc2_docx_bytes = file.read()
116+
doc2_b64 = base64.b64encode(doc2_docx_bytes).decode("ascii")
117+
with open(path.join(demo_docs_path, DS_CONFIG["doc_pdf"]), "rb") as file:
118+
doc3_pdf_bytes = file.read()
119+
doc3_b64 = base64.b64encode(doc3_pdf_bytes).decode("ascii")
120+
121+
# Create the document models
122+
document1 = Document( # Create the DocuSign document object
123+
document_base64=doc1_b64,
124+
name="Order acknowledgement", # Can be different from actual file name
125+
file_extension="html", # Many different document types are accepted
126+
document_id="1" # A label used to reference the doc
127+
)
128+
document2 = Document( # Create the DocuSign document object
129+
document_base64=doc2_b64,
130+
name="Battle Plan", # Can be different from actual file name
131+
file_extension="docx", # Many different document types are accepted
132+
document_id="2" # A label used to reference the doc
133+
)
134+
document3 = Document( # Create the DocuSign document object
135+
document_base64=doc3_b64,
136+
name="Lorem Ipsum", # Can be different from actual file name
137+
file_extension="pdf", # Many different document types are accepted
138+
document_id="3" # A label used to reference the doc
139+
)
140+
# The order in the docs array determines the order in the envelope
141+
env.documents = [document1, document2, document3]
142+
143+
signer_phone_number = RecipientPhoneNumber(
144+
country_code=args["signer_country_code"],
145+
number=args["signer_phone_number"]
146+
)
147+
signer_additional_notification = RecipientAdditionalNotification(
148+
secondary_delivery_method=args["delivery_method"],
149+
phone_number=signer_phone_number
150+
)
151+
152+
# Create the signer recipient model
153+
signer1 = Signer(
154+
name=args["signer_name"],
155+
email=args["signer_email"],
156+
recipient_id="1",
157+
routing_order="1",
158+
delivery_method="Email",
159+
additional_notifications=[signer_additional_notification]
160+
)
161+
162+
# Create a RecipientPhoneNumber and add it to the additional SMS notification
163+
cc_phone_number = RecipientPhoneNumber(
164+
country_code=args["cc_country_code"],
165+
number=args["cc_phone_number"]
166+
)
167+
168+
cc_additional_notification = RecipientAdditionalNotification(
169+
secondary_delivery_method=args["delivery_method"],
170+
phone_number=cc_phone_number
171+
)
172+
173+
# Create a cc recipient to receive a copy of the documents
174+
cc1 = CarbonCopy(
175+
name=args["cc_name"],
176+
email=args["cc_email"],
177+
recipient_id="2",
178+
routing_order="2",
179+
delivery_method="Email",
180+
additional_notifications=[cc_additional_notification]
181+
)
182+
183+
# routingOrder (lower means earlier) determines the order of deliveries
184+
# to the recipients. Parallel routing order is supported by using the
185+
# same integer as the order for two or more recipients
186+
187+
# Create signHere fields (also known as tabs) on the documents
188+
# We're using anchor (autoPlace) positioning
189+
#
190+
# The DocuSign platform searches throughout your envelope"s
191+
# documents for matching anchor strings. So the
192+
# signHere2 tab will be used in both document 2 and 3 since they
193+
# use the same anchor string for their "signer 1" tabs
194+
sign_here1 = SignHere(
195+
anchor_string="**signature_1**",
196+
anchor_units="pixels",
197+
anchor_y_offset="10",
198+
anchor_x_offset="20"
199+
)
200+
201+
sign_here2 = SignHere(
202+
anchor_string="/sn1/",
203+
anchor_units="pixels",
204+
anchor_y_offset="10",
205+
anchor_x_offset="20"
206+
)
207+
208+
# Add the tabs model (including the SignHere tabs) to the signer
209+
# The Tabs object wants arrays of the different field/tab types
210+
signer1.tabs = Tabs(sign_here_tabs=[sign_here1, sign_here2])
211+
212+
# Add the recipients to the envelope object
213+
recipients = Recipients(signers=[signer1], carbon_copies=[cc1])
214+
env.recipients = recipients
215+
216+
# Request that the envelope be sent by setting status to "sent"
217+
# To request that the envelope be created as a draft, set to "created"
218+
env.status = args["status"]
219+
220+
return env
221+
222+
@classmethod
223+
def create_document1(cls, args):
224+
""" Creates document 1 -- an html document"""
225+
226+
return f"""
227+
<!DOCTYPE html>
228+
<html>
229+
<head>
230+
<meta charset="UTF-8">
231+
</head>
232+
<body style="font-family:sans-serif;margin-left:2em;">
233+
<h1 style="font-family: "Trebuchet MS", Helvetica, sans-serif;
234+
color: darkblue;margin-bottom: 0;">World Wide Corp</h1>
235+
<h2 style="font-family: "Trebuchet MS", Helvetica, sans-serif;
236+
margin-top: 0px;margin-bottom: 3.5em;font-size: 1em;
237+
color: darkblue;">Order Processing Division</h2>
238+
<h4>Ordered by {args["signer_name"]}</h4>
239+
<p style="margin-top:0em; margin-bottom:0em;">Phone Number: {args["signer_phone_number"]}</p>
240+
<p style="margin-top:0em; margin-bottom:0em;">Copy to: {args["cc_name"]}</p>
241+
<p style="margin-top:3em;">
242+
Candy bonbon pastry jujubes lollipop wafer biscuit biscuit. Topping brownie sesame snaps sweet roll pie.
243+
Croissant danish biscuit soufflé caramels jujubes jelly. Dragée danish caramels lemon drops dragée.
244+
Gummi bears cupcake biscuit tiramisu sugar plum pastry. Dragée gummies applicake pudding liquorice.
245+
Donut jujubes oat cake jelly-o.
246+
Dessert bear claw chocolate cake gummies lollipop sugar plum ice cream gummies cheesecake.
247+
</p>
248+
<!-- Note the anchor tag for the signature field is in white -->
249+
<h3 style="margin-top:3em;">Agreed: <span style="color:white;">**signature_1**/</span></h3>
250+
</body>
251+
</html>
252+
"""
253+
#ds-snippet-end:eSign46Step2

app/eSignature/views/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,4 @@
4242
from .eg043_shared_access import eg043
4343
from .eg044_focused_view import eg044
4444
from .eg045_delete_restore_envelope import eg045
45+
from .eg046_multiple_delivery import eg046
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
""" Example 046: Request a signature bt multiple delivery channels """
2+
3+
import json
4+
from os import path
5+
6+
from docusign_esign.client.api_exception import ApiException
7+
from flask import redirect, render_template, session, Blueprint, url_for
8+
9+
from ..examples.eg046_multiple_delivery import Eg046MultipleDeliveryController
10+
from ...docusign import authenticate, ensure_manifest, get_example_by_number
11+
from ...docusign.utils import is_cfr
12+
from ...ds_config import DS_CONFIG
13+
from ...error_handlers import process_error
14+
from ...consts import API_TYPE
15+
16+
example_number = 46
17+
api = API_TYPE["ESIGNATURE"]
18+
eg = f"eg0{example_number}" # reference (and url) for this example
19+
eg046 = Blueprint(eg, __name__)
20+
21+
22+
@eg046.route(f"/{eg}", methods=["POST"])
23+
@ensure_manifest(manifest_url=DS_CONFIG["example_manifest_url"])
24+
@authenticate(eg=eg, api=api)
25+
def send_by_multiple_channels():
26+
"""
27+
1. Get required arguments
28+
2. Call the worker method
29+
3. Render success response with envelopeId
30+
"""
31+
example = get_example_by_number(session["manifest"], example_number, api)
32+
33+
# 1. Get required arguments
34+
args = Eg046MultipleDeliveryController.get_args()
35+
try:
36+
# 1. Call the worker method
37+
results = Eg046MultipleDeliveryController.worker(args)
38+
except ApiException as err:
39+
error_body_json = err and hasattr(err, "body") and err.body
40+
# we can pull the DocuSign error code and message from the response body
41+
try:
42+
error_body = json.loads(error_body_json)
43+
except json.decoder.JSONDecodeError:
44+
error_body = {}
45+
error_code = error_body and "errorCode" in error_body and error_body["errorCode"]
46+
47+
# check for specific error
48+
if "ACCOUNT_LACKS_PERMISSIONS" in error_code:
49+
error_message = example["CustomErrorTexts"][0]["ErrorMessage"]
50+
return render_template(
51+
"error.html",
52+
error_code=error_code,
53+
error_message=error_message
54+
)
55+
56+
return process_error(err)
57+
58+
session["envelope_id"] = results["envelope_id"] # Save for use by other examples which need an envelopeId
59+
60+
# 2. Render success response with envelopeId
61+
return render_template(
62+
"example_done.html",
63+
title=example["ExampleName"],
64+
message=f"The envelope has been created and sent!<br/>Envelope ID {results['envelope_id']}."
65+
)
66+
67+
68+
@eg046.route(f"/{eg}", methods=["GET"])
69+
@ensure_manifest(manifest_url=DS_CONFIG["example_manifest_url"])
70+
@authenticate(eg=eg, api=api)
71+
def get_view():
72+
"""responds with the form for the example"""
73+
example = get_example_by_number(session["manifest"], example_number, api)
74+
75+
cfr_status = is_cfr(session["ds_access_token"], session["ds_account_id"], session["ds_base_path"])
76+
if cfr_status == "enabled":
77+
if DS_CONFIG["quickstart"] == "true":
78+
return redirect(url_for("eg041.get_view"))
79+
else:
80+
return render_template("cfr_error.html", title="Error")
81+
82+
return render_template(
83+
"eSignature/eg046_multiple_delivery.html",
84+
title=example["ExampleName"],
85+
example=example,
86+
source_file= "eg046_multiple_delivery.py",
87+
source_url=DS_CONFIG["github_example_url"] + "eg046_multiple_delivery.py",
88+
documentation=DS_CONFIG["documentation"] + eg,
89+
show_doc=DS_CONFIG["documentation"],
90+
signer_name=DS_CONFIG["signer_name"],
91+
signer_email=DS_CONFIG["signer_email"]
92+
)

0 commit comments

Comments
 (0)