diff --git a/README.md b/README.md index ee5bacb5..75a25417 100644 --- a/README.md +++ b/README.md @@ -8,81 +8,116 @@ This repo is a Python 3 application that demonstrates: [Authorization Code Grant flow](https://developers.docusign.com/esign-rest-api/guides/authentication/oauth2-code-grant). When the token expires, the user is asked to re-authenticate. The **refresh token** is not used in this example. +* Authentication with DocuSign via the [JSON Web Token (JWT) Grant](https://developers.docusign.com/esign-rest-api/guides/authentication/oauth2-jsonwebtoken). +When the token expires, it updates automatically. 1. **Embedded Signing Ceremony.** - [Source.](./app/eg001_embedded_signing.py) + [Source.](./app/examples/eg001_embedded_signing/controller.py) This example sends an envelope, and then uses an embedded signing ceremony for the first signer. With embedded signing, the DocuSign signing ceremony is initiated from your website. 1. **Send an envelope with a remote (email) signer and cc recipient.** - [Source.](./app/eg002_signing_via_email.py) + [Source.](./app/examples/eg002_signing_via_email/controller.py) The envelope includes a pdf, Word, and HTML document. Anchor text ([AutoPlace](https://support.docusign.com/en/guides/AutoPlace-New-DocuSign-Experience)) is used to position the signing fields in the documents. 1. **List envelopes in the user's account.** - [Source.](./app/eg003_list_envelopes.py) + [Source.](./app/examples/eg003_list_envelopes/controller.py) The envelopes' current status is included. 1. **Get an envelope's basic information.** - [Source.](./app/eg004_envelope_info.py) + [Source.](./app/examples/eg004_envelope_info/controller.py) The example lists the basic information about an envelope, including its overall status. 1. **List an envelope's recipients** - [Source.](./app/eg005_envelope_recipients.py) + [Source.](./app/examples/eg005_envelope_recipients/controller.py) Includes current recipient status. 1. **List an envelope's documents.** - [Source.](./app/eg006_envelope_docs.py) + [Source.](./app/examples/eg006_envelope_docs/controller.py) 1. **Download an envelope's documents.** - [Source.](./app/eg007_envelope_get_doc.py) + [Source.](./app/examples/eg007_envelope_get_doc/controller.py) The example can download individual documents, the documents concatenated together, or a zip file of the documents. 1. **Programmatically create a template.** - [Source.](./app/eg008_create_template.py) + [Source.](./app/examples/eg008_create_template/controller.py) 1. **Send an envelope using a template.** - [Source.](./app/eg009_use_template.py) -1. **Send an envelope and upload its documents with multpart binary transfer.** - [Source.](./app/eg010_send_binary_docs.py) + [Source.](./app/examples/eg009_use_template/controller.py) +1. **Send an envelope and upload its documents with multipart binary transfer.** + [Source.](./app/examples/eg010_send_binary_docs/controller.py) Binary transfer is 33% more efficient than using Base64 encoding. 1. **Embedded sending.** - [Source.](./app/eg011_embedded_sending.py) + [Source.](./app/examples/eg011_embedded_sending/controller.py) Embeds the DocuSign web tool (NDSE) in your web app to finalize or update the envelope and documents before they are sent. 1. **Embedded DocuSign web tool (NDSE).** - [Source.](./app/eg012_embedded_console.py) + [Source.](./app/examples/eg012_embedded_console/controller.py) 1. **Embedded Signing Ceremony from a template with an added document.** - [Source.](./app/eg013_add_doc_to_template.py) + [Source.](./app/examples/eg013_add_doc_to_template/controller.py) This example sends an envelope based on a template. In addition to the template's document(s), the example adds an additional document to the envelope by using the [Composite Templates](https://developers.docusign.com/esign-rest-api/guides/features/templates#composite-templates) feature. 1. **Payments example: an order form, with online payment by credit card.** - [Source.](./app/eg014_collect_payment.py) + [Source.](./app/examples/eg014_collect_payment/controller.py) 1. **Get the envelope tab data.** Retrieve the tab (field) values for all of the envelope's recipients. - [Source.](./app/eg015_envelope_tab_data.py) + [Source.](./app/examples/eg015_envelope_tab_data/controller.py) 1. **Set envelope tab values.** The example creates an envelope and sets the initial values for its tabs (fields). Some of the tabs are set to be read-only, others can be updated by the recipient. The example also stores metadata with the envelope. - [Source.](./app/eg016_set_tab_values.py) + [Source.](./app/examples/eg016_set_tab_values/controller.py) 1. **Set template tab values.** The example creates an envelope using a template and sets the initial values for its tabs (fields). The example also stores metadata with the envelope. - [Source.](./app/eg017_set_template_tab_values.py) + [Source.](./app/examples/eg017_set_template_tab_values/controller.py) 1. **Get the envelope custom field data (metadata).** The example retrieves the custom metadata (custom data fields) stored with the envelope. - [Source.](./app/eg018_envelope_custom_field_data.py) + [Source.](./app/examples/eg018_envelope_custom_field_data/controller.py) 1. **Requiring an Access Code for a Recipient** - [Source.](./app/eg019_access_code_authentication.py) + [Source.](./app/examples/eg019_access_code_authentication/controller.py) This example sends an envelope that requires an access-code for the purpose of multi-factor authentication. 1. **Requiring SMS authentication for a recipient** - [Source.](./app/eg020_sms_authentication.py) + [Source.](./app/examples/eg020_sms_authentication/controller.py) This example sends an envelope that requires entering in a six digit code from an text message for the purpose of multi-factor authentication. 1. **Requiring Phone authentication for a recipient** - [Source.](./app/eg021_phone_authentication.py) + [Source.](./app/examples/eg021_phone_authentication/controller.py) This example sends an envelope that requires entering in a voice-based response code for the purpose of multi-factor authentication. 1. **Requiring Knowledge-Based Authentication (KBA) for a Recipient** - [Source.](./app/eg022_kba_authentication.py) + [Source.](./app/examples/eg022_kba_authentication/controller.py) This example sends an envelope that requires passing a Public records check to validate identity for the purpose of multi-factor authentication. 1. **Requiring ID Verification (IDV) for a recipient** - [Source.](./app/eg023_idv_authentication.py) - This example sends an envelope that requires submitting a photo of a government issued id for the purpose of multi-factor authentication. + [Source.](./app/examples/eg023_idv_authentication/controller.py) + This example sends an envelope that requires submitting a photo of a government issued id for the purpose of multi-factor authentication. +1. **Creating a permission profile** + [Source.](./app/examples/eg024_permissions_creating/controller.py) + This code example demonstrates how to create a permission profile using the [Create Permission Profile](https://developers.docusign.com/esign-rest-api/reference/Accounts/AccountPermissionProfiles/create) method. +1. **Setting a permission profile** + [Source.](./app/examples/eg025_permissions_set_user_group/controller.py) + This code example demonstrates how to set a user group’s permission profile using the [Update Group](https://developers.docusign.com/esign-rest-api/reference/UserGroups/Groups/update) method. + You must have already created the permissions profile and the group of users. +1. **Updating individual permission settings** + [Source.](./app/examples/eg026_permissions_change_single_setting/controller.py) + This code example demonstrates how to edit individual permission settings on a permissions profile using the [Update Permission Profile](https://developers.docusign.com/esign-rest-api/reference/Accounts/AccountPermissionProfiles/update) method. +1. **Deleting a permission profile** + [Source.](./app/examples/eg027_permissions_delete/controller.py) + This code example demonstrates how to delete a permission profile using the [Delete Permission Profile](https://developers.docusign.com/esign-rest-api/reference/Accounts/AccountPermissionProfiles/create) method. +1. **Creating a brand** + [Source.](./app/examples/eg028_brand_creating/controller.py) + This example creates brand profile for an account using the [Create Brand](https://developers.docusign.com/esign-rest-api/reference/Accounts/AccountBrands/create) method. +1. **Applying a brand to an envelope** + [Source.](./app/examples/eg029_brands_apply_to_envelope/controller.py) + This code example demonstrates how to apply a brand you've created to an envelope using the [Create Envelope](https://developers.docusign.com/esign-rest-api/reference/Envelopes/Envelopes/create) method. + First, creates the envelope and then applies the brand to it. + Anchor text ([AutoPlace](https://support.docusign.com/en/guides/AutoPlace-New-DocuSign-Experience)) is used to position the signing fields in the documents. +1. **Applying a brand to a template** + [Source.](./app/examples/eg030_brands_apply_to_template/controller.py) + This code example demonstrates how to apply a brand you've created to a template using using the [Create Envelope](https://developers.docusign.com/esign-rest-api/reference/Envelopes/Envelopes/create) method. + You must have already created the template and the brand. + Anchor text ([AutoPlace](https://support.docusign.com/en/guides/AutoPlace-New-DocuSign-Experience)) is used to position the signing fields in the documents. +1. **Bulk sending envelopes to multiple recipients** + [Source.](./app/examples/eg031_bulk_send/controller.py) + This code example demonstrates how to send envelopes in bulk to multiple recipients using these methods: + [Create Bulk Send List](https://developers.docusign.com/esign-rest-api/reference/BulkEnvelopes/BulkSend/createBulkSendList), + [Create Bulk Send Request](https://developers.docusign.com/esign-rest-api/reference/BulkEnvelopes/BulkSend/createBulkSendRequest). + Firstly, creates a bulk send recipients list, and then creates an envelope. + After that, initiates bulk envelope sending. ## Installation @@ -147,9 +182,3 @@ This repository uses the MIT License. See the LICENSE file for more information. ### Pull Requests Pull requests are welcomed. Pull requests will only be considered if their content uses the MIT License. - -### Additional Resources -* [DocuSign Developer Center](https://developers.docusign.com) -* [DocuSign API on Twitter](https://twitter.com/docusignapi) -* [DocuSign For Developers on LinkedIn](https://www.linkedin.com/showcase/docusign-for-developers/) -* [DocuSign For Developers on YouTube](https://www.youtube.com/channel/UCJSJ2kMs_qeQotmw4-lX2NQ) diff --git a/app/__init__.py b/app/__init__.py index 13862483..4348fdf2 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,22 +1,61 @@ import os -from app import ds_config + from flask import Flask from flask_wtf.csrf import CSRFProtect +from . import examples +from .docusign.views import ds +from .views import core session_path = "/tmp/python_recipe_sessions" app = Flask(__name__) app.config.from_pyfile("config.py") -app.secret_key = ds_config.DS_CONFIG["session_secret"] -csrf = CSRFProtect(app) # See https://flask-wtf.readthedocs.io/en/stable/csrf.html +# See https://flask-wtf.readthedocs.io/en/stable/csrf.html +csrf = CSRFProtect(app) + +# Register home page +app.register_blueprint(core) +# Register OAuth +app.register_blueprint(ds) +# Register examples +app.register_blueprint(examples.eg001) +app.register_blueprint(examples.eg002) +app.register_blueprint(examples.eg003) +app.register_blueprint(examples.eg004) +app.register_blueprint(examples.eg005) +app.register_blueprint(examples.eg006) +app.register_blueprint(examples.eg007) +app.register_blueprint(examples.eg008) +app.register_blueprint(examples.eg009) +app.register_blueprint(examples.eg010) +app.register_blueprint(examples.eg011) +app.register_blueprint(examples.eg012) +app.register_blueprint(examples.eg013) +app.register_blueprint(examples.eg014) +app.register_blueprint(examples.eg015) +app.register_blueprint(examples.eg016) +app.register_blueprint(examples.eg017) +app.register_blueprint(examples.eg018) +app.register_blueprint(examples.eg019) +app.register_blueprint(examples.eg020) +app.register_blueprint(examples.eg021) +app.register_blueprint(examples.eg022) +app.register_blueprint(examples.eg023) +app.register_blueprint(examples.eg024) +app.register_blueprint(examples.eg025) +app.register_blueprint(examples.eg026) +app.register_blueprint(examples.eg027) +app.register_blueprint(examples.eg028) +app.register_blueprint(examples.eg029) +app.register_blueprint(examples.eg030) +app.register_blueprint(examples.eg031) if "DYNO" in os.environ: # On Heroku? import logging + stream_handler = logging.StreamHandler() app.logger.addHandler(stream_handler) app.logger.setLevel(logging.INFO) app.logger.info("Recipe example startup") - app.config.update(dict(PREFERRED_URL_SCHEME = "https")) - -from app import views + app.config.update(dict(PREFERRED_URL_SCHEME="https")) diff --git a/app/config.py b/app/config.py index 1f5ae642..4e822478 100644 --- a/app/config.py +++ b/app/config.py @@ -2,3 +2,7 @@ # For interactive debugging of Flask apps on Heroku see # http://stackoverflow.com/a/13946679/64904 DEBUG = True + +# Secret for encrypting session cookie content +# Use any random string of characters +SECRET_KEY = "{SESSION_SECRET}" diff --git a/app/consts.py b/app/consts.py new file mode 100644 index 00000000..d9a8847d --- /dev/null +++ b/app/consts.py @@ -0,0 +1,105 @@ +from os import path +import re + +demo_docs_path = path.abspath(path.join(path.dirname(path.realpath(__file__)), "static/demo_documents")) + +# Pattern for validating signer name and email +pattern = re.compile("([^\w \-\@\.\,])+") + +signer_client_id = 1000 # Used to indicate that the signer will use an embedded +# Signing Ceremony. Represents the signer"s userId within +# your application. + +authentication_method = "None" # How is this application authenticating + +minimum_buffer_min = 3 + +# Template name for create template example +template_name = "Example Signer and CC template" + +# Name of static doc file +doc_file = "World_Wide_Corp_fields.pdf" +# Name of static pdf file +pdf_file = "World_Wide_Corp_lorem.pdf" + +# Base uri for callback function +base_uri_suffix = "/restapi" + + +# Default languages for brand +languages = { + "Arabic": "ar", + "Armenian": "hy", + "Bahasa Indonesia": "id", + "Bahasa Malay": "ms", + "Bulgarian": "bg", + "Chinese Simplified": "zh_CN", + "Chinese Traditional": "zh_TW", + "Croatian": "hr", + "Czech": "cs", + "Danish": "da", + "Dutch": "nl", + "English UK": "en_GB", + "English US": "en", + "Estonian": "et", + "Farsi": "fa", + "Finnish": "fi", + "French": "fr", + "French Canada": "fr_CA", + "German": "de", + "Greek": "el", + "Hebrew": "he", + "Hindi": "hi", + "Hungarian": "hu", + "Italian": "it", + "Japanese": "ja", + "Korean": "ko", + "Latvian": "lv", + "Lithuanian": "lt", + "Norwegian": "no", + "Polish": "pl", + "Portuguese": "pt", + "Portuguese Brasil": "pt_BR", + "Romanian": "ro", + "Russian": "ru", + "Serbian": "sr", + "Slovak": "sk", + "Slovenian": "sl", + "Spanish": "es", + "Spanish Latin America": "es_MX", + "Swedish": "sv", + "Thai": "th", + "Turkish": "tr", + "Ukrainian": "uk", + "Vietnamese": "vi" +} + +# Default settings for updating and creating permissions +settings = { + "useNewDocuSignExperienceInterface": "optional", + "allowBulkSending": "true", + "allowEnvelopeSending": "true", + "allowSignerAttachments": "true", + "allowTaggingInSendAndCorrect": "true", + "allowWetSigningOverride": "true", + "allowedAddressBookAccess": "personalAndShared", + "allowedTemplateAccess": "share", + "enableRecipientViewingNotifications": "true", + "enableSequentialSigningInterface": "true", + "receiveCompletedSelfSignedDocumentsAsEmailLinks": "false", + "signingUiVersion": "v2", + "useNewSendingInterface": "true", + "allowApiAccess": "true", + "allowApiAccessToAccount": "true", + "allowApiSendingOnBehalfOfOthers": "true", + "allowApiSequentialSigning": "true", + "enableApiRequestLogging": "true", + "allowDocuSignDesktopClient": "false", + "allowSendersToSetRecipientEmailLanguage": "true", + "allowVaulting": "false", + "allowedToBeEnvelopeTransferRecipient": "true", + "enableTransactionPointIntegration": "false", + "powerFormRole": "admin", + "vaultingMode": "none" +} + diff --git a/app/docusign/__init__.py b/app/docusign/__init__.py new file mode 100644 index 00000000..6898cf89 --- /dev/null +++ b/app/docusign/__init__.py @@ -0,0 +1,2 @@ +from .ds_client import DSClient +from .utils import ds_token_ok, create_api_client, authenticate diff --git a/app/docusign/ds_client.py b/app/docusign/ds_client.py new file mode 100644 index 00000000..2edc3058 --- /dev/null +++ b/app/docusign/ds_client.py @@ -0,0 +1,145 @@ +import uuid +from os import path + + +import requests +from flask import current_app as app, url_for, redirect, render_template, request +from flask_oauthlib.client import OAuth +from docusign_esign import ApiClient +from docusign_esign.client.api_exception import ApiException + +from ..ds_config import DS_CONFIG, DS_JWT +from ..error_handlers import process_error + + +class DSClient: + ds_app = None + + @classmethod + def _init(cls, auth_type): + if auth_type == "code_grant": + cls._auth_code_grant() + elif auth_type == "jwt": + cls._jwt_auth() + + @classmethod + def _auth_code_grant(cls): + """Authorize with the Authorization Code Grant - OAuth 2.0 flow""" + oauth = OAuth(app) + request_token_params = { + "scope": "signature", + "state": lambda: uuid.uuid4().hex.upper() + } + if not DS_CONFIG["allow_silent_authentication"]: + request_token_params["prompt"] = "login" + cls.ds_app = oauth.remote_app( + "docusign", + consumer_key=DS_CONFIG["ds_client_id"], + consumer_secret=DS_CONFIG["ds_client_secret"], + access_token_url=DS_CONFIG["authorization_server"] + "/oauth/token", + authorize_url=DS_CONFIG["authorization_server"] + "/oauth/auth", + request_token_params=request_token_params, + base_url=None, + request_token_url=None, + access_token_method="POST" + ) + + @classmethod + def _jwt_auth(cls): + """JSON Web Token authorization""" + api_client = ApiClient() + api_client.set_base_path(DS_JWT["authorization_server"]) + + # Catch IO error + try: + private_key = cls._get_private_key().encode("ascii").decode("utf-8") + except (OSError, IOError) as err: + return render_template( + "error.html", + err=err + ) + + try: + cls.ds_app = api_client.request_jwt_user_token( + client_id=DS_JWT["ds_client_id"], + user_id=DS_JWT["ds_impersonated_user_id"], + oauth_host_name=DS_JWT["authorization_server"], + private_key_bytes=private_key, + expires_in=3600 + ) + + return redirect(url_for("ds.ds_callback")) + + except ApiException as err: + body = err.body.decode('utf8') + + # Grand explicit consent for the application + if "consent_required" in body: + consent_scopes = "signature%20impersonation" + redirect_uri = DS_CONFIG["app_url"] + url_for("ds.ds_callback") + consent_url = f"{DS_CONFIG['authorization_server']}/oauth/auth?response_type=code&" \ + f"scope={consent_scopes}&client_id={DS_JWT['ds_client_id']}&redirect_uri={redirect_uri}" + return redirect(consent_url) + else: + process_error(err) + + @classmethod + def destroy(cls): + cls.ds_app = None + + @staticmethod + def _get_private_key(): + """ + Check that the private key present in the file and if it is, get it from the file. + In the opposite way get it from config variable. + """ + private_key_file = path.abspath(DS_JWT["private_key_file"]) + + if path.isfile(private_key_file): + with open(private_key_file) as private_key_file: + private_key = private_key_file.read() + else: + private_key = DS_JWT["private_key_file"] + + return private_key + + @classmethod + def login(cls, auth_type): + if auth_type == "code_grant": + return cls.get(auth_type).authorize(callback=url_for("ds.ds_callback", _external=True)) + elif auth_type == "jwt": + return cls._jwt_auth() + + @classmethod + def get_token(cls, auth_type): + resp = None + if auth_type == "code_grant": + resp = cls.get(auth_type).authorized_response() + elif auth_type == "jwt": + resp = cls.get(auth_type).to_dict() + + if resp is None or resp.get("access_token") is None: + return "Access denied: reason=%s error=%s resp=%s" % ( + request.args["error"], + request.args["error_description"], + resp + ) + + return resp + + @classmethod + def get_user(cls, access_token): + """Make request to the API to get the user information""" + # Determine user, account_id, base_url by calling OAuth::getUserInfo + # See https://developers.docusign.com/esign-rest-api/guides/authentication/user-info-endpoints + url = DS_CONFIG["authorization_server"] + "/oauth/userinfo" + auth = {"Authorization": "Bearer " + access_token} + response = requests.get(url, headers=auth).json() + + return response + + @classmethod + def get(cls, auth_type): + if not cls.ds_app: + cls._init(auth_type) + return cls.ds_app diff --git a/app/docusign/utils.py b/app/docusign/utils.py new file mode 100644 index 00000000..934995af --- /dev/null +++ b/app/docusign/utils.py @@ -0,0 +1,71 @@ +from datetime import timedelta, datetime +from functools import wraps + +from docusign_esign import ApiClient +from flask import session, flash, url_for, redirect + +from .ds_client import DSClient +from ..consts import minimum_buffer_min + + +def ds_logout_internal(): + # remove the keys and their values from the session + session.pop("ds_access_token", None) + session.pop("ds_refresh_token", None) + session.pop("ds_user_email", None) + session.pop("ds_user_name", None) + session.pop("ds_expiration", None) + session.pop("ds_account_id", None) + session.pop("ds_account_name", None) + session.pop("ds_base_path", None) + session.pop("envelope_id", None) + session.pop("eg", None) + session.pop("envelope_documents", None) + session.pop("template_id", None) + session.pop("auth_type", None) + DSClient.destroy() + + +def create_api_client(base_path, access_token): + """Create api client and construct API headers""" + api_client = ApiClient() + api_client.host = base_path + api_client.set_default_header(header_name="Authorization", header_value=f"Bearer {access_token}") + + return api_client + + +def ds_token_ok(buffer_min=60): + """ + :param buffer_min: buffer time needed in minutes + :return: true iff the user has an access token that will be good for another buffer min + """ + + ok = "ds_access_token" in session and "ds_expiration" in session + ok = ok and (session["ds_expiration"] - timedelta(minutes=buffer_min)) > datetime.utcnow() + + return ok + + +def authenticate(eg): + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + if ds_token_ok(minimum_buffer_min): + return func(*args, **kwargs) + else: + # We could store the parameters of the requested operation + # so it could be restarted automatically. + # But since it should be rare to have a token issue here, + # we"ll make the user re-enter the form data after + # authentication. + session["eg"] = url_for(eg + ".get_view") + if session.get("auth_type"): + flash("Token has been updated") + return redirect(url_for("ds.ds_login")) + else: + return redirect(url_for("ds.ds_must_authenticate")) + + return wrapper + + return decorator diff --git a/app/docusign/views.py b/app/docusign/views.py new file mode 100644 index 00000000..72c0420c --- /dev/null +++ b/app/docusign/views.py @@ -0,0 +1,89 @@ +from datetime import datetime, timedelta + +from flask import redirect, request, url_for, flash, render_template, Blueprint, session + +from .ds_client import DSClient +from .utils import ds_logout_internal +from ..consts import base_uri_suffix +from ..ds_config import DS_CONFIG + +ds = Blueprint("ds", __name__, url_prefix="/ds") + + +@ds.route("/login", methods=["GET", "POST"]) +def ds_login(): + if not session.get("auth_type"): + session["auth_type"] = request.form.get("auth_type") + return DSClient.login(session["auth_type"]) + + +@ds.route("/logout") +def ds_logout(): + ds_logout_internal() + flash("You have logged out from DocuSign.") + return redirect(url_for("core.index")) + + +@ds.route("/callback") +def ds_callback(): + """ + Save the token information in session. + Call api to get user's information if it doesn't present + """ + + # Save the redirect eg if present + redirect_url = session.pop("eg", None) + resp = DSClient.get_token(session["auth_type"]) + + # app.logger.info("Authenticated with DocuSign.") + session["ds_access_token"] = resp["access_token"] + session["ds_refresh_token"] = resp["refresh_token"] + session["ds_expiration"] = datetime.utcnow() + timedelta(seconds=int(resp["expires_in"])) + + if not session.get("ds_account_id"): + flash("You have authenticated with DocuSign.") + # Request to API to get the user information + response = DSClient.get_user(session["ds_access_token"]) + session["ds_user_name"] = response["name"] + session["ds_user_email"] = response["email"] + accounts = response["accounts"] + # Find the account... + target_account_id = DS_CONFIG["target_account_id"] + if target_account_id: + account = next((a for a in accounts if a["account_id"] == target_account_id), None) + if not account: + # Panic! The user does not have the targeted account. They should not log in! + raise Exception("No access to target account") + else: # get the default account + account = next((a for a in accounts if a["is_default"]), None) + if not account: + # Panic! Every user should always have a default account + raise Exception("No default account") + + # Save the account information + session["ds_account_id"] = account["account_id"] + session["ds_account_name"] = account["account_name"] + session["ds_base_path"] = account["base_uri"] + base_uri_suffix + + if not redirect_url: + redirect_url = url_for("core.index") + return redirect(redirect_url) + + +@ds.route("/must_authenticate") +def ds_must_authenticate(): + return render_template("must_authenticate.html", title="Must authenticate") + + +@ds.route("/ds_return") +def ds_return(): + event = request.args.get("event") + state = request.args.get("state") + envelope_id = request.args.get("envelopeId") + return render_template( + "ds_return.html", + title="Return from DocuSign", + event=event, + envelope_id=envelope_id, + state=state + ) diff --git a/app/ds_config.py b/app/ds_config.py index a9a72a20..c8a294a9 100644 --- a/app/ds_config.py +++ b/app/ds_config.py @@ -1,31 +1,35 @@ # ds_config.py # # DocuSign configuration settings -import os DS_CONFIG = { - "ds_client_id": "{CLIENT_ID}", # The app's DocuSign integration key - "ds_client_secret": "{CLIENT_SECRET}", # The app's DocuSign integration key's secret + "ds_client_id": "{CLIENT_ID}", # The app's DocuSign integration key + "ds_client_secret": "{CLIENT_SECRET}", # The app's DocuSign integration key's secret "signer_email": "{USER_EMAIL}", "signer_name": "{USER_FULLNAME}", - "app_url": "{APP_URL}", # The url of the application. Eg http://localhost:5000 + "app_url": "{APP_URL}", # The url of the application. Eg http://localhost:5000 # NOTE: You must add a Redirect URI of appUrl/ds/callback to your Integration Key. # Example: http:#localhost:5000/ds/callback "authorization_server": "https://account-d.docusign.com", - "session_secret": "{SESSION_SECRET}", # Secret for encrypting session cookie content - # Use any random string of characters - "allow_silent_authentication": True, # a user can be silently authenticated if they have an + "allow_silent_authentication": True, # a user can be silently authenticated if they have an # active login session on another tab of the same browser - "target_account_id": None, # Set if you want a specific DocuSign AccountId, - # If None, the user's default account will be used. + "target_account_id": None, # Set if you want a specific DocuSign AccountId, + # If None, the user's default account will be used. "demo_doc_path": "demo_documents", "doc_salary_docx": "World_Wide_Corp_salary.docx", "doc_docx": "World_Wide_Corp_Battle_Plan_Trafalgar.docx", - "doc_pdf": "World_Wide_Corp_lorem.pdf", + "doc_pdf": "World_Wide_Corp_lorem.pdf", # Payment gateway information is optional "gateway_account_id": "{DS_PAYMENT_GATEWAY_ID}", "gateway_name": "stripe", "gateway_display_name": "Stripe", "github_example_url": "https://github.com/docusign/eg-03-python-auth-code-grant/tree/master/app/", - "documentation": "" # Use an empty string to indicate no documentation path. + "documentation": "" # Use an empty string to indicate no documentation path. +} + +DS_JWT = { + "ds_client_id": "{CLIENT_ID}", + "ds_impersonated_user_id": "{IMPERSONATED_USER_ID}", # the id of the user + "private_key_file": "private.key", # path to file which hold private key or private key itself + "authorization_server": "account-d.docusign.com", } diff --git a/app/eg001_embedded_signing.py b/app/eg001_embedded_signing.py deleted file mode 100644 index 2c9a8ed6..00000000 --- a/app/eg001_embedded_signing.py +++ /dev/null @@ -1,205 +0,0 @@ -"""Example 001: Embedded Signing Ceremony""" - -from flask import render_template, url_for, redirect, session, flash, request -from os import path -import json -from app import app, ds_config, views -import base64 -import re -from docusign_esign import * -from docusign_esign.client.api_exception import ApiException - -eg = "eg001" # reference (and url) for this example -signer_client_id = 1000 # Used to indicate that the signer will use an embedded - # Signing Ceremony. Represents the signer's userId within - # your application. -authentication_method = "None" # How is this application authenticating - # the signer? See the 'authenticationMethod' definition - # https://developers.docusign.com/esign-rest-api/reference/Envelopes/EnvelopeViews/createRecipient - -demo_docs_path = path.abspath(path.join(path.dirname(path.realpath(__file__)), "static/demo_documents")) - - -def controller(): - """Controller router using the HTTP method""" - if request.method == "GET": - return get_controller() - elif request.method == "POST": - return create_controller() - else: - return render_template("404.html"), 404 - - -def create_controller(): - """ - 1. Check the token - 2. Call the worker method - 3. Redirect the user to the signing ceremony - """ - minimum_buffer_min = 3 - if views.ds_token_ok(minimum_buffer_min): - # 2. Call the worker method - # More data validation would be a good idea here - # Strip anything other than characters listed - pattern = re.compile("([^\w \-\@\.\,])+") - signer_email = pattern.sub("", request.form.get("signer_email")) - signer_name = pattern.sub("", request.form.get("signer_name")) - envelope_args = { - "signer_email": signer_email, - "signer_name": signer_name, - "signer_client_id": signer_client_id, - "ds_return_url": url_for("ds_return", _external=True), - } - args = { - "account_id": session["ds_account_id"], - "base_path": session["ds_base_path"], - "ds_access_token": session["ds_access_token"], - "envelope_args": envelope_args - } - - try: - results = worker(args) - except ApiException as err: - error_body_json = err and hasattr(err, "body") and err.body - # we can pull the DocuSign error code and message from the response body - error_body = json.loads(error_body_json) - error_code = error_body and "errorCode" in error_body and error_body["errorCode"] - error_message = error_body and "message" in error_body and error_body["message"] - # In production, may want to provide customized error messages and - # remediation advice to the user. - return render_template("error.html", - err=err, - error_code=error_code, - error_message=error_message - ) - if results: - # Redirect the user to the Signing Ceremony - # Don"t use an iFrame! - # State can be stored/recovered using the framework's session or a - # query parameter on the returnUrl (see the makeRecipientViewRequest method) - return redirect(results["redirect_url"]) - - else: - flash("Sorry, you need to re-authenticate.") - # We could store the parameters of the requested operation - # so it could be restarted automatically. - # But since it should be rare to have a token issue here, - # we'll make the user re-enter the form data after - # authentication. - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - - -# ***DS.snippet.0.start -def worker(args): - """ - 1. Create the envelope request object - 2. Send the envelope - 3. Create the Recipient View request object - 4. Obtain the recipient_view_url for the signing ceremony - """ - envelope_args = args["envelope_args"] - # 1. Create the envelope request object - envelope_definition = make_envelope(envelope_args) - - # 2. call Envelopes::create API method - # Exceptions will be caught by the calling function - api_client = ApiClient() - api_client.host = args["base_path"] - api_client.set_default_header("Authorization", "Bearer " + args["ds_access_token"]) - - envelope_api = EnvelopesApi(api_client) - results = envelope_api.create_envelope(args["account_id"], envelope_definition=envelope_definition) - - envelope_id = results.envelope_id - app.logger.info(f"Envelope was created. EnvelopeId {envelope_id}") - - # 3. Create the Recipient View request object - recipient_view_request = RecipientViewRequest( - authentication_method = authentication_method, - client_user_id = envelope_args["signer_client_id"], - recipient_id = "1", - return_url = envelope_args["ds_return_url"], - user_name = envelope_args["signer_name"], email = envelope_args["signer_email"] - ) - # 4. Obtain the recipient_view_url for the signing ceremony - # Exceptions will be caught by the calling function - results = envelope_api.create_recipient_view(args["account_id"], envelope_id, - recipient_view_request = recipient_view_request) - - return {"envelope_id": envelope_id, "redirect_url": results.url} - - -def make_envelope(args): - """ - Creates envelope - args -- parameters for the envelope: - signer_email, signer_name, signer_client_id - returns an envelope definition - """ - - # document 1 (pdf) has tag /sn1/ - # - # The envelope has one recipient. - # recipient 1 - signer - with open(path.join(demo_docs_path, ds_config.DS_CONFIG["doc_pdf"]), "rb") as file: - content_bytes = file.read() - base64_file_content = base64.b64encode(content_bytes).decode("ascii") - - # Create the document model - document = Document( # create the DocuSign document object - document_base64 = base64_file_content, - name = "Example document", # can be different from actual file name - file_extension = "pdf", # many different document types are accepted - document_id = 1 # a label used to reference the doc - ) - - # Create the signer recipient model - signer = Signer( # The signer - email = args["signer_email"], name = args["signer_name"], - recipient_id = "1", routing_order = "1", - # Setting the client_user_id marks the signer as embedded - client_user_id = args["signer_client_id"] - ) - - # Create a sign_here tab (field on the document) - sign_here = SignHere( # DocuSign SignHere field/tab - anchor_string = "/sn1/", anchor_units = "pixels", - anchor_y_offset = "10", anchor_x_offset = "20" - ) - - # Add the tabs model (including the sign_here tab) to the signer - # The Tabs object wants arrays of the different field/tab types - signer.tabs = Tabs(sign_here_tabs = [sign_here]) - - # Next, create the top level envelope definition and populate it. - envelope_definition = EnvelopeDefinition( - email_subject = "Please sign this document sent from the Python SDK", - documents = [document], - # The Recipients object wants arrays for each recipient type - recipients = Recipients(signers = [signer]), - status = "sent" # requests that the envelope be created and sent. - ) - - return envelope_definition -# ***DS.snippet.0.end - - -def get_controller(): - """responds with the form for the example""" - - if views.ds_token_ok(): - return render_template("eg001_embedded_signing.html", - title="Embedded Signing Ceremony", - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - signer_name=ds_config.DS_CONFIG["signer_name"], - signer_email=ds_config.DS_CONFIG["signer_email"] - ) - else: - # Save the current operation so it will be resumed after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - diff --git a/app/eg002_signing_via_email.py b/app/eg002_signing_via_email.py deleted file mode 100644 index a534a782..00000000 --- a/app/eg002_signing_via_email.py +++ /dev/null @@ -1,263 +0,0 @@ -""" Example 002: Remote signer, cc, envelope has three documents """ - -from flask import render_template, url_for, redirect, session, flash, request -from os import path -from app import app, ds_config, views -import base64 -import re -import json -from docusign_esign import * -from docusign_esign.client.api_exception import ApiException - -eg = "eg002" # reference (and url) for this example -demo_docs_path = path.abspath(path.join(path.dirname(path.realpath(__file__)), "static/demo_documents")) - - -def controller(): - """Controller router using the HTTP method""" - if request.method == "GET": - return get_controller() - elif request.method == "POST": - return create_controller() - else: - return render_template("404.html"), 404 - - -def create_controller(): - """ - 1. Check the token - 2. Call the worker method - """ - minimum_buffer_min = 3 - if views.ds_token_ok(minimum_buffer_min): - # 2. Call the worker method - # More data validation would be a good idea here - # Strip anything other than characters listed - pattern = re.compile("([^\w \-\@\.\,])+") - signer_email = pattern.sub("", request.form.get("signer_email")) - signer_name = pattern.sub("", request.form.get("signer_name")) - cc_email = pattern.sub("", request.form.get("cc_email")) - cc_name = pattern.sub("", request.form.get("cc_name")) - envelope_args = { - "signer_email": signer_email, - "signer_name": signer_name, - "cc_email": cc_email, - "cc_name": cc_name, - "status": "sent", - } - args = { - "account_id": session["ds_account_id"], - "base_path": session["ds_base_path"], - "ds_access_token": session["ds_access_token"], - "envelope_args": envelope_args - } - - try: - results = worker(args) - except ApiException as err: - error_body_json = err and hasattr(err, "body") and err.body - # we can pull the DocuSign error code and message from the response body - error_body = json.loads(error_body_json) - error_code = error_body and "errorCode" in error_body and error_body["errorCode"] - error_message = error_body and "message" in error_body and error_body["message"] - # In production, may want to provide customized error messages and - # remediation advice to the user. - return render_template("error.html", - err=err, - error_code=error_code, - error_message=error_message - ) - if results: - session["envelope_id"] = results["envelope_id"] # Save for use by other examples - # which need an envelopeId - return render_template("example_done.html", - title="Envelope sent", - h1="Envelope sent", - message=f"""The envelope has been created and sent!
- Envelope ID {results["envelope_id"]}.""" - ) - - else: - flash("Sorry, you need to re-authenticate.") - # We could store the parameters of the requested operation - # so it could be restarted automatically. - # But since it should be rare to have a token issue here, - # we'll make the user re-enter the form data after - # authentication. - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - - -# ***DS.snippet.0.start -def worker(args): - """ - 1. Create the envelope request object - 2. Send the envelope - """ - envelope_args = args["envelope_args"] - # 1. Create the envelope request object - envelope_definition = make_envelope(envelope_args) - - # 2. call Envelopes::create API method - # Exceptions will be caught by the calling function - api_client = ApiClient() - api_client.host = args["base_path"] - api_client.set_default_header("Authorization", "Bearer " + args["ds_access_token"]) - - envelopes_api = EnvelopesApi(api_client) - results = envelopes_api.create_envelope(args["account_id"], envelope_definition=envelope_definition) - - envelope_id = results.envelope_id - app.logger.info(f"Envelope was created. EnvelopeId {envelope_id}") - - return {"envelope_id": envelope_id} - - -def make_envelope(args): - """ - Creates envelope - Document 1: An HTML document. - Document 2: A Word .docx document. - Document 3: A PDF document. - DocuSign will convert all of the documents to the PDF format. - The recipients" field tags are placed using anchor strings. - """ - - # document 1 (html) has sign here anchor tag **signature_1** - # document 2 (docx) has sign here anchor tag /sn1/ - # document 3 (pdf) has sign here anchor tag /sn1/ - # - # The envelope has two recipients. - # recipient 1 - signer - # recipient 2 - cc - # The envelope will be sent first to the signer. - # After it is signed, a copy is sent to the cc person. - - # create the envelope definition - env = EnvelopeDefinition( - email_subject="Please sign this document set" - ) - doc1_b64 = base64.b64encode(bytes(create_document1(args), "utf-8")).decode("ascii") - # read files 2 and 3 from a local directory - # The reads could raise an exception if the file is not available! - with open(path.join(demo_docs_path, ds_config.DS_CONFIG["doc_docx"]), "rb") as file: - doc2_docx_bytes = file.read() - doc2_b64 = base64.b64encode(doc2_docx_bytes).decode("ascii") - with open(path.join(demo_docs_path, ds_config.DS_CONFIG["doc_pdf"]), "rb") as file: - doc3_pdf_bytes = file.read() - doc3_b64 = base64.b64encode(doc3_pdf_bytes).decode("ascii") - - # Create the document models - document1 = Document( # create the DocuSign document object - document_base64=doc1_b64, - name="Order acknowledgement", # can be different from actual file name - file_extension="html", # many different document types are accepted - document_id="1" # a label used to reference the doc - ) - document2 = Document( # create the DocuSign document object - document_base64=doc2_b64, - name="Battle Plan", # can be different from actual file name - file_extension="docx", # many different document types are accepted - document_id="2" # a label used to reference the doc - ) - document3 = Document( # create the DocuSign document object - document_base64=doc3_b64, - name="Lorem Ipsum", # can be different from actual file name - file_extension="pdf", # many different document types are accepted - document_id="3" # a label used to reference the doc - ) - # The order in the docs array determines the order in the envelope - env.documents = [document1, document2, document3] - - - # Create the signer recipient model - signer1 = Signer( - email=args["signer_email"], name=args["signer_name"], - recipient_id="1", routing_order="1" - ) - # routingOrder (lower means earlier) determines the order of deliveries - # to the recipients. Parallel routing order is supported by using the - # same integer as the order for two or more recipients. - - # create a cc recipient to receive a copy of the documents - cc1 = CarbonCopy( - email=args["cc_email"], name=args["cc_name"], - recipient_id="2", routing_order="2") - - # Create signHere fields (also known as tabs) on the documents, - # We're using anchor (autoPlace) positioning - # - # The DocuSign platform searches throughout your envelope"s - # documents for matching anchor strings. So the - # signHere2 tab will be used in both document 2 and 3 since they - # use the same anchor string for their "signer 1" tabs. - sign_here1 = SignHere( - anchor_string = "**signature_1**", anchor_units = "pixels", - anchor_y_offset = "10", anchor_x_offset = "20") - sign_here2 = SignHere( - anchor_string = "/sn1/", anchor_units = "pixels", - anchor_y_offset = "10", anchor_x_offset = "20") - - # Add the tabs model (including the sign_here tabs) to the signer - # The Tabs object wants arrays of the different field/tab types - signer1.tabs = Tabs(sign_here_tabs=[sign_here1, sign_here2]) - - # Add the recipients to the envelope object - recipients = Recipients(signers=[signer1], carbon_copies=[cc1]) - env.recipients = recipients - - # Request that the envelope be sent by setting |status| to "sent". - # To request that the envelope be created as a draft, set to "created" - env.status = args["status"] - - return env - - -def create_document1(args): - """ Creates document 1 -- an html document""" - - return f""" - - - - - - -

World Wide Corp

-

Order Processing Division

-

Ordered by {args["signer_name"]}

-

Email: {args["signer_email"]}

-

Copy to: {args["cc_name"]}, {args["cc_email"]}

-

- 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. -

- -

Agreed: **signature_1**/

- - - """ -# ***DS.snippet.0.end - - -def get_controller(): - """responds with the form for the example""" - - if views.ds_token_ok(): - return render_template("eg002_signing_via_email.html", - title="Signing via email", - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - signer_name=ds_config.DS_CONFIG["signer_name"], - signer_email=ds_config.DS_CONFIG["signer_email"] - ) - else: - # Save the current operation so it will be resumed after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - - diff --git a/app/eg003_list_envelopes.py b/app/eg003_list_envelopes.py deleted file mode 100644 index dfda9760..00000000 --- a/app/eg003_list_envelopes.py +++ /dev/null @@ -1,112 +0,0 @@ -"""Example 003: List envelopes in the user's account""" - -from flask import render_template, url_for, redirect, session, flash, request -from os import path -import json -from app import app, ds_config, views -from datetime import datetime, timedelta -from docusign_esign import * -from docusign_esign.client.api_exception import ApiException - -eg = "eg003" # reference (and url) for this example - -def controller(): - """Controller router using the HTTP method""" - if request.method == "GET": - return get_controller() - elif request.method == "POST": - return create_controller() - else: - return render_template("404.html"), 404 - - -def create_controller(): - """ - 1. Check the token - 2. Call the worker method - 3. Show results - """ - minimum_buffer_min = 3 - if views.ds_token_ok(minimum_buffer_min): - # 2. Call the worker method - args = { - "account_id": session["ds_account_id"], - "base_path": session["ds_base_path"], - "ds_access_token": session["ds_access_token"], - } - - try: - results = worker(args) - except ApiException as err: - error_body_json = err and hasattr(err, "body") and err.body - # we can pull the DocuSign error code and message from the response body - error_body = json.loads(error_body_json) - error_code = error_body and "errorCode" in error_body and error_body["errorCode"] - error_message = error_body and "message" in error_body and error_body["message"] - # In production, may want to provide customized error messages and - # remediation advice to the user. - return render_template("error.html", - err=err, - error_code=error_code, - error_message=error_message - ) - return render_template("example_done.html", - title="List envelopes results", - h1="List envelopes results", - message="Results from the Envelopes::listStatusChanges method:", - json=json.dumps(json.dumps(results.to_dict())) - ) - else: - flash("Sorry, you need to re-authenticate.") - # We could store the parameters of the requested operation - # so it could be restarted automatically. - # But since it should be rare to have a token issue here, - # we"ll make the user re-enter the form data after - # authentication. - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - - -# ***DS.snippet.0.start -def worker(args): - """ - 1. Call the envelope status change method to list the envelopes - that have changed in the last 10 days - """ - - # Exceptions will be caught by the calling function - api_client = ApiClient() - api_client.host = args["base_path"] - api_client.set_default_header("Authorization", "Bearer " + args["ds_access_token"]) - envelope_api = EnvelopesApi(api_client) - - # The Envelopes::listStatusChanges method has many options - # See https://developers.docusign.com/esign-rest-api/reference/Envelopes/Envelopes/listStatusChanges - - # The list status changes call requires at least a from_date OR - # a set of envelopeIds. Here we filter using a from_date. - # Here we set the from_date to filter envelopes for the last month - # Use ISO 8601 date format - from_date = (datetime.utcnow() - timedelta(days=10)).isoformat() - results = envelope_api.list_status_changes(args["account_id"], from_date = from_date) - - return results -# ***DS.snippet.0.end - - -def get_controller(): - """responds with the form for the example""" - - if views.ds_token_ok(): - return render_template("eg003_list_envelopes.html", - title="List changed envelopes", - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - ) - else: - # Save the current operation so it will be resumed after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - diff --git a/app/eg004_envelope_info.py b/app/eg004_envelope_info.py deleted file mode 100644 index 43bf566b..00000000 --- a/app/eg004_envelope_info.py +++ /dev/null @@ -1,113 +0,0 @@ -"""004: Get an envelope's basic information and status""" - -from flask import render_template, url_for, redirect, session, flash, request -from os import path -import json -from app import app, ds_config, views -from docusign_esign import * -from docusign_esign.client.api_exception import ApiException - -eg = "eg004" # reference (and url) for this example - -def controller(): - """Controller router using the HTTP method""" - if request.method == "GET": - return get_controller() - elif request.method == "POST": - return create_controller() - else: - return render_template("404.html"), 404 - - -def create_controller(): - """ - 1. Check the token - 2. Call the worker method - 3. Show results - """ - minimum_buffer_min = 3 - token_ok = views.ds_token_ok(minimum_buffer_min) - if token_ok and "envelope_id" in session: - # 2. Call the worker method - args = { - "account_id": session["ds_account_id"], - "envelope_id": session["envelope_id"], - "base_path": session["ds_base_path"], - "ds_access_token": session["ds_access_token"], - } - - try: - results = worker(args) - except ApiException as err: - error_body_json = err and hasattr(err, "body") and err.body - # we can pull the DocuSign error code and message from the response body - error_body = json.loads(error_body_json) - error_code = error_body and "errorCode" in error_body and error_body["errorCode"] - error_message = error_body and "message" in error_body and error_body["message"] - # In production, may want to provide customized error messages and - # remediation advice to the user. - return render_template("error.html", - err=err, - error_code=error_code, - error_message=error_message - ) - return render_template("example_done.html", - title="Get envelope status results", - h1="Get envelope status results", - message="Results from the Envelopes::get method:", - json=json.dumps(json.dumps(results.to_dict())) - ) - elif not token_ok: - flash("Sorry, you need to re-authenticate.") - # We could store the parameters of the requested operation - # so it could be restarted automatically. - # But since it should be rare to have a token issue here, - # we"ll make the user re-enter the form data after - # authentication. - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - elif not "envelope_id" in session: - return render_template("eg004_envelope_info.html", - title="Envelope information", - envelope_ok=False, - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - ) - - -# ***DS.snippet.0.start -def worker(args): - """ - 1. Call the envelope get method - """ - - # Exceptions will be caught by the calling function - api_client = ApiClient() - api_client.host = args["base_path"] - api_client.set_default_header("Authorization", "Bearer " + args["ds_access_token"]) - envelope_api = EnvelopesApi(api_client) - results = envelope_api.get_envelope(args["account_id"], args["envelope_id"]) - - return results -# ***DS.snippet.0.end - - -def get_controller(): - """responds with the form for the example""" - - if views.ds_token_ok(): - return render_template("eg004_envelope_info.html", - title="Envelope information", - envelope_ok="envelope_id" in session, - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - ) - else: - # Save the current operation so it will be resumed after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - diff --git a/app/eg005_envelope_recipients.py b/app/eg005_envelope_recipients.py deleted file mode 100644 index 31e585d8..00000000 --- a/app/eg005_envelope_recipients.py +++ /dev/null @@ -1,113 +0,0 @@ -"""005: List an envelope's recipients and status""" - -from flask import render_template, url_for, redirect, session, flash, request -from os import path -import json -from app import ds_config, views -from docusign_esign import * -from docusign_esign.client.api_exception import ApiException - -eg = "eg005" # reference (and url) for this example - -def controller(): - """Controller router using the HTTP method""" - if request.method == "GET": - return get_controller() - elif request.method == "POST": - return create_controller() - else: - return render_template("404.html"), 404 - - -def create_controller(): - """ - 1. Check the token - 2. Call the worker method - 3. Show results - """ - minimum_buffer_min = 3 - token_ok = views.ds_token_ok(minimum_buffer_min) - if token_ok and "envelope_id" in session: - # 2. Call the worker method - args = { - "account_id": session["ds_account_id"], - "envelope_id": session["envelope_id"], - "base_path": session["ds_base_path"], - "ds_access_token": session["ds_access_token"], - } - - try: - results = worker(args) - except ApiException as err: - error_body_json = err and hasattr(err, "body") and err.body - # we can pull the DocuSign error code and message from the response body - error_body = json.loads(error_body_json) - error_code = error_body and "errorCode" in error_body and error_body["errorCode"] - error_message = error_body and "message" in error_body and error_body["message"] - # In production, may want to provide customized error messages and - # remediation advice to the user. - return render_template("error.html", - err=err, - error_code=error_code, - error_message=error_message - ) - return render_template("example_done.html", - title="Envelope recipients results", - h1="List the envelope's recipients and their status", - message="Results from the EnvelopesRecipients::list method:", - json=json.dumps(json.dumps(results.to_dict())) - ) - elif not token_ok: - flash("Sorry, you need to re-authenticate.") - # We could store the parameters of the requested operation - # so it could be restarted automatically. - # But since it should be rare to have a token issue here, - # we"ll make the user re-enter the form data after - # authentication. - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - elif not "envelope_id" in session: - return render_template("eg005_envelope_recipients.html", - title="Envelope recipient information", - envelope_ok=False, - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - ) - - -# ***DS.snippet.0.start -def worker(args): - """ - 1. Call the envelope recipients list method - """ - - # Exceptions will be caught by the calling function - api_client = ApiClient() - api_client.host = args["base_path"] - api_client.set_default_header("Authorization", "Bearer " + args["ds_access_token"]) - envelope_api = EnvelopesApi(api_client) - results = envelope_api.list_recipients(args["account_id"], args["envelope_id"]) - - return results -# ***DS.snippet.0.end - - -def get_controller(): - """responds with the form for the example""" - - if views.ds_token_ok(): - return render_template("eg005_envelope_recipients.html", - title="Envelope recipient information", - envelope_ok="envelope_id" in session, - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - ) - else: - # Save the current operation so it will be resumed after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - diff --git a/app/eg006_envelope_docs.py b/app/eg006_envelope_docs.py deleted file mode 100644 index 973e56f4..00000000 --- a/app/eg006_envelope_docs.py +++ /dev/null @@ -1,132 +0,0 @@ -"""006: List an envelope's documents""" - -from flask import render_template, url_for, redirect, session, flash, request -from os import path -import json -from app import app, ds_config, views -from docusign_esign import * -from docusign_esign.client.api_exception import ApiException - -eg = "eg006" # reference (and url) for this example - -def controller(): - """Controller router using the HTTP method""" - if request.method == "GET": - return get_controller() - elif request.method == "POST": - return create_controller() - else: - return render_template("404.html"), 404 - - -def create_controller(): - """ - 1. Check the token - 2. Call the worker method - 3. Show results - """ - minimum_buffer_min = 3 - token_ok = views.ds_token_ok(minimum_buffer_min) - if token_ok and "envelope_id" in session: - # 2. Call the worker method - args = { - "account_id": session["ds_account_id"], - "envelope_id": session["envelope_id"], - "base_path": session["ds_base_path"], - "ds_access_token": session["ds_access_token"], - } - - try: - results = worker(args) - except ApiException as err: - error_body_json = err and hasattr(err, "body") and err.body - # we can pull the DocuSign error code and message from the response body - error_body = json.loads(error_body_json) - error_code = error_body and "errorCode" in error_body and error_body["errorCode"] - error_message = error_body and "message" in error_body and error_body["message"] - # In production, may want to provide customized error messages and - # remediation advice to the user. - return render_template("error.html", - err=err, - error_code=error_code, - error_message=error_message - ) - - # Save the envelopeId and its list of documents in the session so - # they can be used in example 7 (download a document) - standard_doc_items = [ - {"name": "Combined" , "type": "content", "document_id": "combined"}, - {"name": "Zip archive", "type": "zip" , "document_id": "archive"}] - # The certificate of completion is named "summary". - # We give it a better name below. - envelope_doc_items = list(map(lambda doc : - ({"document_id": doc.document_id, "name": "Certificate of completion", "type": doc.type}) - if (doc.document_id == "certificate") else - ({"document_id": doc.document_id, "name": doc.name , "type": doc.type}), - results.envelope_documents)) - envelope_documents = {"envelope_id": session["envelope_id"], - "documents": standard_doc_items + envelope_doc_items} # See https://stackoverflow.com/a/6005217/64904 - - session["envelope_documents"] = envelope_documents # Save - - - return render_template("example_done.html", - title="List an envelope's documents", - h1="List an envelope's documents", - message="Results from the EnvelopeDocuments::list method:", - json=json.dumps(json.dumps(results.to_dict())) - ) - elif not token_ok: - flash("Sorry, you need to re-authenticate.") - # We could store the parameters of the requested operation - # so it could be restarted automatically. - # But since it should be rare to have a token issue here, - # we"ll make the user re-enter the form data after - # authentication. - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - elif not "envelope_id" in session: - return render_template("eg006_envelope_docs.html", - title="Envelope documents", - envelope_ok=False, - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - ) - - -# ***DS.snippet.0.start -def worker(args): - """ - 1. Call the EnvelopeDocuments::list method - """ - - # Exceptions will be caught by the calling function - api_client = ApiClient() - api_client.host = args["base_path"] - api_client.set_default_header("Authorization", "Bearer " + args["ds_access_token"]) - envelope_api = EnvelopesApi(api_client) - results = envelope_api.list_documents(args["account_id"], args["envelope_id"]) - - return results -# ***DS.snippet.0.end - - -def get_controller(): - """responds with the form for the example""" - - if views.ds_token_ok(): - return render_template("eg006_envelope_docs.html", - title="Envelope documents", - envelope_ok="envelope_id" in session, - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - ) - else: - # Save the current operation so it will be resumed after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - diff --git a/app/eg007_envelope_get_doc.py b/app/eg007_envelope_get_doc.py deleted file mode 100644 index 3ca0f3d5..00000000 --- a/app/eg007_envelope_get_doc.py +++ /dev/null @@ -1,157 +0,0 @@ -"""007: Get an envelope's document""" - -from flask import render_template, url_for, redirect, session, flash, request, send_file -from os import path -import json -import re -import io -from app import app, ds_config, views -from docusign_esign import * -from docusign_esign.client.api_exception import ApiException - -eg = "eg007" # reference (and url) for this example - -def controller(): - """Controller router using the HTTP method""" - if request.method == "GET": - return get_controller() - elif request.method == "POST": - return create_controller() - else: - return render_template("404.html"), 404 - - -def create_controller(): - """ - 1. Check the token - 2. Call the worker method - 3. Show results - """ - minimum_buffer_min = 3 - token_ok = views.ds_token_ok(minimum_buffer_min) - if token_ok and "envelope_id" in session and "envelope_documents" in session: - # 2. Call the worker method - # More data validation would be a good idea here - # Strip anything other than characters listed - pattern = re.compile("([^\w \-\@\.\,])+") - document_id = pattern.sub("", request.form.get("document_id")) - - args = { - "account_id": session["ds_account_id"], - "envelope_id": session["envelope_id"], - "base_path": session["ds_base_path"], - "ds_access_token": session["ds_access_token"], - "document_id": document_id, - "envelope_documents": session["envelope_documents"] - } - - try: - results = worker(args) - except ApiException as err: - error_body_json = err and hasattr(err, "body") and err.body - # we can pull the DocuSign error code and message from the response body - error_body = json.loads(error_body_json) - error_code = error_body and "errorCode" in error_body and error_body["errorCode"] - error_message = error_body and "message" in error_body and error_body["message"] - # In production, may want to provide customized error messages and - # remediation advice to the user. - return render_template("error.html", - err=err, - error_code=error_code, - error_message=error_message - ) - - return send_file( - results["data"], - mimetype=results["mimetype"], - as_attachment=True, - attachment_filename=results["doc_name"] - ) - - elif not token_ok: - flash("Sorry, you need to re-authenticate.") - # We could store the parameters of the requested operation - # so it could be restarted automatically. - # But since it should be rare to have a token issue here, - # we'll make the user re-enter the form data after - # authentication. - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - elif not "envelope_id" in session or not "envelope_documents" in session: - return render_template("eg007_envelope_get_doc.html", - title="Download an Envelope's Document", - envelope_ok=False, - documents_ok=False, - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - ) - - -# ***DS.snippet.0.start -def worker(args): - """ - 1. Call the envelope get method - """ - - # Exceptions will be caught by the calling function - api_client = ApiClient() - api_client.host = args["base_path"] - api_client.set_default_header("Authorization", "Bearer " + args["ds_access_token"]) - envelope_api = EnvelopesApi(api_client) - document_id = args["document_id"] - - # The SDK always stores the received file as a temp file - temp_file = envelope_api.get_document(args["account_id"], document_id, args["envelope_id"]) - doc_item = next(item for item in args["envelope_documents"]["documents"] if item["document_id"] == document_id) - doc_name = doc_item["name"] - has_pdf_suffix = doc_name[-4:].upper() == ".PDF" - pdf_file = has_pdf_suffix - # Add .pdf if it's a content or summary doc and doesn"t already end in .pdf - if (doc_item["type"] == "content" or doc_item["type"] == "summary") and not has_pdf_suffix: - doc_name += ".pdf" - pdf_file = True - # Add .zip as appropriate - if doc_item["type"] == "zip": - doc_name += ".zip" - - # Return the file information - if pdf_file: - mimetype = "application/pdf" - elif doc_item["type"] == "zip": - mimetype = "application/zip" - else: - mimetype = "application/octet-stream" - - return {"mimetype": mimetype, "doc_name": doc_name, "data": temp_file} -# ***DS.snippet.0.end - -def get_controller(): - """responds with the form for the example""" - - if views.ds_token_ok(): - documents_ok = "envelope_documents" in session - document_options = [] - if documents_ok: - # Prepare the select items - envelope_documents = session["envelope_documents"] - document_options = map( lambda item : - {"text": item["name"], "document_id": item["document_id"]} - , envelope_documents["documents"]) - - return render_template("eg007_envelope_get_doc.html", - title="Download an Envelope's Document", - envelope_ok="envelope_id" in session, - documents_ok=documents_ok, - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - document_options=document_options - ) - else: - # Save the current operation so it will be resumed after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - diff --git a/app/eg008_create_template.py b/app/eg008_create_template.py deleted file mode 100644 index f19b1539..00000000 --- a/app/eg008_create_template.py +++ /dev/null @@ -1,216 +0,0 @@ -""" Example 008: create a template if it doesn't already exist """ - -from flask import render_template, url_for, redirect, session, flash, request -from os import path -from app import app, ds_config, views -import base64 -import re -import json -from docusign_esign import * -from docusign_esign.client.api_exception import ApiException - -eg = "eg008" # reference (and url) for this example -demo_docs_path = path.abspath(path.join(path.dirname(path.realpath(__file__)), "static/demo_documents")) -template_name = "Example Signer and CC template" -doc_file = "World_Wide_Corp_fields.pdf" - - -def controller(): - """Controller router using the HTTP method""" - if request.method == "GET": - return get_controller() - elif request.method == "POST": - return create_controller() - else: - return render_template("404.html"), 404 - - -def create_controller(): - """ - 1. Check the token - 2. Call the worker method - """ - minimum_buffer_min = 3 - if views.ds_token_ok(minimum_buffer_min): - # 2. Call the worker method - args = { - "account_id": session["ds_account_id"], - "base_path": session["ds_base_path"], - "ds_access_token": session["ds_access_token"] - } - - try: - results = worker(args) - except ApiException as err: - error_body_json = err and hasattr(err, "body") and err.body - # we can pull the DocuSign error code and message from the response body - error_body = json.loads(error_body_json) - error_code = error_body and "errorCode" in error_body and error_body["errorCode"] - error_message = error_body and "message" in error_body and error_body["message"] - # In production, may want to provide customized error messages and - # remediation advice to the user. - return render_template("error.html", - err=err, - error_code=error_code, - error_message=error_message - ) - if results: - # Save the templateId in the session so they can be used in future examples - session["template_id"] = results["template_id"] - session["template_ok"] = True - - msg = "The template has been created!" if results["created_new_template"] else \ - "Done. The template already existed in your account." - return render_template("example_done.html", - title="Template results", - h1="Template results", - message=f"""{msg}
Template name: {results["template_name"]}, - ID {results["template_id"]}.""" - ) - else: - flash("Sorry, you need to re-authenticate.") - # We could store the parameters of the requested operation - # so it could be restarted automatically. - # But since it should be rare to have a token issue here, - # we'll make the user re-enter the form data after - # authentication. - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - - -# ***DS.snippet.0.start -def worker(args): - """ - 1. Check to see if the template already exists - 2. If not, create the template - """ - # 1. call Templates::list API method - # Exceptions will be caught by the calling function - api_client = ApiClient() - api_client.host = args["base_path"] - api_client.set_default_header("Authorization", "Bearer " + args["ds_access_token"]) - templates_api = TemplatesApi(api_client) - results = templates_api.list_templates(args["account_id"], search_text=template_name) - results_template_name = None - created_new_template = False - if int(results.result_set_size) > 0: - template_id = results.envelope_templates[0].template_id - results_template_name = results.envelope_templates[0].name - else: - - # Template not found -- so create it - # Step 2 create the template - template_req_object = make_template_req() - res = templates_api.create_template(args["account_id"], envelope_template=template_req_object) - # Pick the first template object within the result - results = res.templates[0] - template_id = results.template_id - results_template_name = results.name - created_new_template = True - return { - "template_id": template_id, - "template_name": results_template_name, - "created_new_template": created_new_template} - - -def make_template_req(): - """Creates template req object""" - - # document 1 (pdf) - # - # The template has two recipient roles. - # recipient 1 - signer - # recipient 2 - cc - with open(path.join(demo_docs_path, doc_file), "rb") as file: - content_bytes = file.read() - base64_file_content = base64.b64encode(content_bytes).decode("ascii") - - # Create the document model - document = Document( # create the DocuSign document object - document_base64 = base64_file_content, - name = "Lorem Ipsum", # can be different from actual file name - file_extension = "pdf", # many different document types are accepted - document_id = 1 # a label used to reference the doc - ) - - # Create the signer recipient model - signer = Signer(role_name="signer", recipient_id="1", routing_order="1") - # create a cc recipient to receive a copy of the envelope (transaction) - cc = CarbonCopy(role_name="cc", recipient_id="2", routing_order="2") - # Create fields using absolute positioning - # Create a sign_here tab (field on the document) - sign_here = SignHere(document_id="1", page_number="1", x_position="191", y_position="148") - check1 = Checkbox(document_id="1", page_number="1", x_position="75", y_position="417", - tab_label="ckAuthorization") - check2 = Checkbox(document_id="1", page_number="1", x_position="75", y_position="447", - tab_label="ckAuthentication") - check3 = Checkbox(document_id="1", page_number="1", x_position="75", y_position="478", - tab_label="ckAgreement") - check4 = Checkbox(document_id="1", page_number="1", x_position="75", y_position="508", - tab_label="ckAcknowledgement") - list1 = List(document_id="1", page_number="1", x_position="142", y_position="291", - font="helvetica", font_size="size14", tab_label="list", - required="false", - list_items=[ - ListItem(text="Red" , value="red" ), - ListItem(text="Orange", value="orange"), - ListItem(text="Yellow", value="yellow"), - ListItem(text="Green" , value="green" ), - ListItem(text="Blue" , value="blue" ), - ListItem(text="Indigo", value="indigo"), - ListItem(text="Violet", value="violet") - ] - ) - number1 = Number(document_id="1", page_number="1", x_position="163", y_position="260", - font="helvetica", font_size="size14", tab_label="numbersOnly", - width="84", required="false") - radio_group = RadioGroup(document_id="1", group_name="radio1", - radios=[ - Radio(page_number="1", x_position="142", y_position="384", - value="white", required="false"), - Radio(page_number="1", x_position="74", y_position="384", - value="red", required="false"), - Radio(page_number="1", x_position="220", y_position="384", - value="blue", required="false") - ]) - text = Text(document_id="1", page_number="1", x_position="153", y_position="230", - font="helvetica", font_size="size14", tab_label="text", - height="23", width="84", required="false") - # Add the tabs model to the signer - # The Tabs object wants arrays of the different field/tab types - signer.tabs = Tabs(sign_here_tabs = [sign_here], - checkbox_tabs=[check1, check2, check3, check4], - list_tabs=[list1], number_tabs=[number1], - radio_group_tabs=[radio_group], text_tabs=[text] - ) - - # Top object: - template_request = EnvelopeTemplate( - documents=[document], email_subject="Please sign this document", - recipients=Recipients(signers=[signer], carbon_copies=[cc]), - description="Example template created via the API", - name=template_name, - shared="false", - status="created" - ) - - return template_request -# ***DS.snippet.0.end - - -def get_controller(): - """responds with the form for the example""" - - if views.ds_token_ok(): - return render_template("eg008_create_template.html", - title="Create a template", - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - ) - else: - # Save the current operation so it will be resumed after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - diff --git a/app/eg009_use_template.py b/app/eg009_use_template.py deleted file mode 100644 index 96c776f5..00000000 --- a/app/eg009_use_template.py +++ /dev/null @@ -1,169 +0,0 @@ -"""Example 009: Send envelope using a template""" - -from flask import render_template, url_for, redirect, session, flash, request -from os import path -import json -from app import app, ds_config, views -import base64 -import re -from docusign_esign import * -from docusign_esign.client.api_exception import ApiException - -eg = "eg009" # reference (and url) for this example - - -def controller(): - """Controller router using the HTTP method""" - if request.method == "GET": - return get_controller() - elif request.method == "POST": - return create_controller() - else: - return render_template("404.html"), 404 - - -def create_controller(): - """ - 1. Check the token - 2. Call the worker method - """ - minimum_buffer_min = 3 - token_ok = views.ds_token_ok(minimum_buffer_min) - if token_ok and "template_id" in session: - # 2. Call the worker method - # More data validation would be a good idea here - # Strip anything other than characters listed - pattern = re.compile("([^\w \-\@\.\,])+") - signer_email = pattern.sub("", request.form.get("signer_email")) - signer_name = pattern.sub("", request.form.get("signer_name")) - cc_email = pattern.sub("", request.form.get("cc_email")) - cc_name = pattern.sub("", request.form.get("cc_name")) - template_id = session["template_id"] - envelope_args = { - "signer_email": signer_email, - "signer_name": signer_name, - "cc_email": cc_email, - "cc_name": cc_name, - "template_id": template_id - } - args = { - "account_id": session["ds_account_id"], - "base_path": session["ds_base_path"], - "ds_access_token": session["ds_access_token"], - "envelope_args": envelope_args - } - - try: - results = worker(args) - except ApiException as err: - error_body_json = err and hasattr(err, "body") and err.body - # we can pull the DocuSign error code and message from the response body - error_body = json.loads(error_body_json) - error_code = error_body and "errorCode" in error_body and error_body["errorCode"] - error_message = error_body and "message" in error_body and error_body["message"] - # In production, may want to provide customized error messages and - # remediation advice to the user. - return render_template("error.html", - err=err, - error_code=error_code, - error_message=error_message - ) - if results: - session["envelope_id"] = results["envelope_id"] # Save for use by other examples - # which need an envelopeId - return render_template("example_done.html", - title="Envelope sent", - h1="Envelope sent", - message=f"""The envelope has been created and sent!
- Envelope ID {results["envelope_id"]}.""", - envelope_ok="envelope_id" in results - ) - elif not token_ok: - flash("Sorry, you need to re-authenticate.") - # We could store the parameters of the requested operation - # so it could be restarted automatically. - # But since it should be rare to have a token issue here, - # we'll make the user re-enter the form data after - # authentication. - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - elif not "template_id" in session: - return render_template("eg009_use_template.html", - title="Use a template to send an envelope", - template_ok=False, - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - ) - - -# ***DS.snippet.0.start -def worker(args): - """ - 1. Create the envelope request object - 2. Send the envelope - """ - envelope_args = args["envelope_args"] - # 1. Create the envelope request object - envelope_definition = make_envelope(envelope_args) - - # 2. call Envelopes::create API method - # Exceptions will be caught by the calling function - api_client = ApiClient() - api_client.host = args["base_path"] - api_client.set_default_header("Authorization", "Bearer " + args["ds_access_token"]) - envelope_api = EnvelopesApi(api_client) - results = envelope_api.create_envelope(args["account_id"], envelope_definition=envelope_definition) - envelope_id = results.envelope_id - return {"envelope_id": envelope_id} - - -def make_envelope(args): - """ - Creates envelope - args -- parameters for the envelope: - signer_email, signer_name, signer_client_id - returns an envelope definition - """ - - # create the envelope definition - envelope_definition = EnvelopeDefinition( - status = "sent", # requests that the envelope be created and sent. - template_id = args["template_id"] - ) - # Create template role elements to connect the signer and cc recipients - # to the template - signer = TemplateRole( - email = args["signer_email"], - name = args["signer_name"], - role_name = "signer") - # Create a cc template role. - cc = TemplateRole( - email = args["cc_email"], - name = args["cc_name"], - role_name = "cc") - - # Add the TemplateRole objects to the envelope object - envelope_definition.template_roles = [signer, cc] - return envelope_definition -# ***DS.snippet.0.end - -def get_controller(): - """responds with the form for the example""" - - if views.ds_token_ok(): - return render_template("eg009_use_template.html", - title="Use a template to send an envelope", - template_ok="template_id" in session, - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - signer_name=ds_config.DS_CONFIG["signer_name"], - signer_email=ds_config.DS_CONFIG["signer_email"] - ) - else: - # Save the current operation so it will be resumed after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) diff --git a/app/eg010_send_binary_docs.py b/app/eg010_send_binary_docs.py deleted file mode 100644 index 8251595d..00000000 --- a/app/eg010_send_binary_docs.py +++ /dev/null @@ -1,296 +0,0 @@ -""" Example 010: Send binary docs with multipart mime: Remote signer, cc; the envelope has three documents""" - -from flask import render_template, url_for, redirect, session, flash, request -from os import path -from app import app, ds_config, views -import base64 -import re -import json -import requests - -eg = "eg010" # reference (and url) for this example -demo_docs_path = path.abspath(path.join(path.dirname(path.realpath(__file__)), "static/demo_documents")) - - -def controller(): - """Controller router using the HTTP method""" - if request.method == "GET": - return get_controller() - elif request.method == "POST": - return create_controller() - else: - return render_template("404.html"), 404 - - -def create_controller(): - """ - 1. Check the token - 2. Call the worker method - """ - minimum_buffer_min = 3 - if views.ds_token_ok(minimum_buffer_min): - # 2. Call the worker method - # More data validation would be a good idea here - # Strip anything other than characters listed - pattern = re.compile("([^\w \-\@\.\,])+") - signer_email = pattern.sub("", request.form.get("signer_email")) - signer_name = pattern.sub("", request.form.get("signer_name")) - cc_email = pattern.sub("", request.form.get("cc_email")) - cc_name = pattern.sub("", request.form.get("cc_name")) - envelope_args = { - "signer_email": signer_email, - "signer_name": signer_name, - "cc_email": cc_email, - "cc_name": cc_name, - "status": "sent", - } - args = { - "account_id": session["ds_account_id"], - "base_path": session["ds_base_path"], - "ds_access_token": session["ds_access_token"], - "envelope_args": envelope_args - } - - results = worker(args) - - if results["status_code"] < 299: - # Success! - return render_template("example_done.html", - title="Envelope sent", - h1="Envelope sent", - message=f"""The envelope has been created and sent!
- Envelope ID {results["results"]["envelopeId"]}.""" - ) - else: - # Problem! - error_body = results["results"] - # we can pull the DocuSign error code and message from the response body - error_code = error_body and "errorCode" in error_body and error_body["errorCode"] - error_message = error_body and "message" in error_body and error_body["message"] - # In production, may want to provide customized error messages and - # remediation advice to the user. - return render_template("error.html", - err=None, - error_code=error_code, - error_message=error_message - ) - - else: - flash("Sorry, you need to re-authenticate.") - # We could store the parameters of the requested operation - # so it could be restarted automatically. - # But since it should be rare to have a token issue here, - # we'll make the user re-enter the form data after - # authentication. - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - - -# ***DS.snippet.0.start -def worker(args): - """ - This function does the work of creating the envelope by using - the API directly with multipart mime - @param {object} args An object with the following elements:
- account_id: Current account Id
- base_path: base path for making API call
- access_token: a valid access token
- demo_docs_path: relative path for the demo docs
- envelope_args: envelopeArgs, an object with elements
- signer_email, signer_name, cc_email, cc_name - """ - - # Step 1. Make the envelope JSON request body - envelope_JSON = make_envelope_JSON( args["envelope_args"] ) - - # Step 2. Gather documents and their headers - # Read files 2 and 3 from a local directory - # The reads could raise an exception if the file is not available! - # Note: the fles are not binary encoded! - with open(path.join(demo_docs_path, ds_config.DS_CONFIG["doc_docx"]), "rb") as file: - doc2_docx_bytes = file.read() - with open(path.join(demo_docs_path, ds_config.DS_CONFIG["doc_pdf"]), "rb") as file: - doc3_pdf_bytes = file.read() - - documents = [ - {"mime": "text/html", "filename": envelope_JSON["documents"][0]["name"], - "document_id": envelope_JSON["documents"][0]["documentId"], - "bytes": create_document1(args["envelope_args"]).encode("utf-8")}, - {"mime": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - "filename": envelope_JSON["documents"][1]["name"], - "document_id": envelope_JSON["documents"][1]["documentId"], - "bytes": doc2_docx_bytes}, - {"mime": "application/pdf", "filename": envelope_JSON["documents"][2]["name"], - "document_id": envelope_JSON["documents"][2]["documentId"], - "bytes": doc3_pdf_bytes} - ] - - # Step 3. Create the multipart body - CRLF = b"\r\n" - boundary = b"multipartboundary_multipartboundary" - hyphens = b"--" - - req_body = b"".join([ - hyphens, boundary, - CRLF, b"Content-Type: application/json", - CRLF, b"Content-Disposition: form-data", - CRLF, - CRLF, json.dumps(envelope_JSON, indent=4).encode("utf-8")]) - - # Loop to add the documents. - # See section Multipart Form Requests on page - # https://developers.docusign.com/esign-rest-api/guides/requests-and-responses - for d in documents: - content_disposition = (f"Content-Disposition: file; filename={d['filename']};" + - f"documentid={d['document_id']}").encode("utf-8") - req_body = b"".join([req_body, - CRLF, hyphens, boundary, - CRLF, f"Content-Type: {d['mime']}".encode("utf-8"), - CRLF, content_disposition, - CRLF, - CRLF, d["bytes"]]) - - # Add closing boundary - req_body = b"".join([req_body, CRLF, hyphens, boundary, hyphens, CRLF]) - - # Step 2. call Envelopes::create API method - # Exceptions will be caught by the calling function - results = requests.post(f"{args['base_path']}/v2.1/accounts/{args['account_id']}/envelopes", - headers = { - "Authorization": "bearer " + args["ds_access_token"], - "Accept": "application/json", - "Content-Type": f"multipart/form-data; boundary={boundary.decode('utf-8')}" - }, - data = req_body - ) - return {"status_code": results.status_code, "results": results.json()} - - -def make_envelope_JSON(args): - """ - Create envelope JSON -
Document 1: An HTML document. -
Document 2: A Word .docx document. -
Document 3: A PDF document. -
DocuSign will convert all of the documents to the PDF format. -
The recipients" field tags are placed using anchor strings. - @param {Object} args parameters for the envelope: - signerEmail, signerName, cc_email, cc_name - @returns {Envelope} An envelope definition - """ - - # document 1 (html) has tag **signature_1** - # document 2 (docx) has tag /sn1/ - # document 3 (pdf) has tag /sn1/ - # - # The envelope has two recipients. - # recipient 1 - signer - # recipient 2 - cc - # The envelope will be sent first to the signer. - # After it is signed, a copy is sent to the cc person. - - # create the envelope definition - env_json = {} - env_json["emailSubject"] = "Please sign this document set" - - # add the documents - doc1 = { - "name": "Order acknowledgement", # can be different from actual file name - "fileExtension": "html", # Source data format. Signed docs are always pdf. - "documentId": "1" } # a label used to reference the doc - doc2 = { - "name": "Battle Plan", "fileExtension": "docx", "documentId": "2"} - doc3 = { - "name": "Lorem Ipsum", "fileExtension": "pdf", "documentId": "3"} - # The order in the docs array determines the order in the envelope - env_json["documents"] = [doc1, doc2, doc3] - - # create a signer recipient to sign the document, identified by name and email - signer1 = { - "email": args["signer_email"], "name": args["signer_name"], - "recipientId": "1", "routingOrder": "1"} - # routingOrder (lower means earlier) determines the order of deliveries - # to the recipients. Parallel routing order is supported by using the - # same integer as the order for two or more recipients. - - # create a cc recipient to receive a copy of the documents, identified by name and email - cc1 = { - "email": args["cc_email"], "name": args["cc_name"], - "routingOrder": "2", "recipientId": "2"} - - # Create signHere fields (also known as tabs) on the documents, - # We"re using anchor (autoPlace) positioning - # - # The DocuSign platform searches throughout your envelope"s - # documents for matching anchor strings. So the - # signHere2 tab will be used in both document 2 and 3 since they - # use the same anchor string for their "signer 1" tabs. - sign_here1 = { - "anchorString": "**signature_1**", "anchorYOffset": "10", "anchorUnits": "pixels", - "anchorXOffset": "20"} - sign_here2 = { - "anchorString": "/sn1/", "anchorYOffset": "10", "anchorUnits": "pixels", - "anchorXOffset": "20"} - - # Tabs are set per recipient / signer - signer1_tabs = {"signHereTabs": [sign_here1, sign_here2]} - signer1["tabs"] = signer1_tabs - - # Add the recipients to the envelope object - recipients = {"signers": [signer1], "carbonCopies": [cc1]} - env_json["recipients"] = recipients - - # Request that the envelope be sent by setting |status| to "sent". - # To request that the envelope be created as a draft, set to "created" - env_json["status"] = "sent" - - return env_json - - -def create_document1(args): - """ Creates document 1 -- an html document""" - - return f""" - - - - - - -

World Wide Corp

-

Order Processing Division

-

Ordered by {args['signer_name']}

-

Email: {args['signer_email']}

-

Copy to: {args['cc_name']}, {args['cc_email']}

-

- 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. -

- -

Agreed: **signature_1**/

- - - """ -# ***DS.snippet.0.end - - -def get_controller(): - """responds with the form for the example""" - - if views.ds_token_ok(): - return render_template("eg010_send_binary_docs.html", - title="Send binary documents", - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - signer_name=ds_config.DS_CONFIG["signer_name"], - signer_email=ds_config.DS_CONFIG["signer_email"] - ) - else: - # Save the current operation so it will be resumed after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - diff --git a/app/eg011_embedded_sending.py b/app/eg011_embedded_sending.py deleted file mode 100644 index 759201d5..00000000 --- a/app/eg011_embedded_sending.py +++ /dev/null @@ -1,146 +0,0 @@ -"""011: Embedded sending: Remote signer, cc, envelope has three documents""" - -from flask import render_template, url_for, redirect, session, flash, request -from os import path -import json -import re -from app import app, ds_config, views, eg002_signing_via_email -from docusign_esign import * -from docusign_esign.client.api_exception import ApiException - -eg = "eg011" # reference (and url) for this example - -def controller(): - """Controller router using the HTTP method""" - if request.method == "GET": - return get_controller() - elif request.method == "POST": - return create_controller() - else: - return render_template("404.html"), 404 - - -def create_controller(): - """ - 1. Check the token - 2. Call the worker method - 3. Show results - """ - minimum_buffer_min = 3 - token_ok = views.ds_token_ok(minimum_buffer_min) - if token_ok: - # 2. Call the worker method - # More data validation would be a good idea here - # Strip anything other than characters listed - pattern = re.compile("([^\w \-\@\.\,])+") - signer_email = pattern.sub("", request.form.get("signer_email")) - signer_name = pattern.sub("", request.form.get("signer_name")) - cc_email = pattern.sub("", request.form.get("cc_email")) - cc_name = pattern.sub("", request.form.get("cc_name")) - starting_view = pattern.sub("", request.form.get("starting_view")) - - envelope_args = { - "signer_email": signer_email, - "signer_name": signer_name, - "cc_email": cc_email, - "cc_name": cc_name, - "status": "sent", - } - args = { - "starting_view": starting_view, - "account_id": session["ds_account_id"], - "base_path": session["ds_base_path"], - "ds_access_token": session["ds_access_token"], - "envelope_args": envelope_args, - "ds_return_url": url_for("ds_return", _external=True), - } - - try: - results = worker(args) - except ApiException as err: - error_body_json = err and hasattr(err, "body") and err.body - # we can pull the DocuSign error code and message from the response body - error_body = json.loads(error_body_json) - error_code = error_body and "errorCode" in error_body and error_body["errorCode"] - error_message = error_body and "message" in error_body and error_body["message"] - # In production, may want to provide customized error messages and - # remediation advice to the user. - return render_template("error.html", - err=err, - error_code=error_code, - error_message=error_message - ) - if results: - # Redirect the user to the NDSE view - # Don't use an iFrame! - # State can be stored/recovered using the framework's session or a - # query parameter on the returnUrl (see the makeRecipientViewRequest method) - return redirect(results["redirect_url"]) - - elif not token_ok: - flash("Sorry, you need to re-authenticate.") - # We could store the parameters of the requested operation - # so it could be restarted automatically. - # But since it should be rare to have a token issue here, - # we'll make the user re-enter the form data after - # authentication. - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - elif not "envelope_id" in session: - return render_template("eg011_embedded_sending.html", - title="Embedded Sending", - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - ) - - -# ***DS.snippet.0.start -def worker(args): - """ - This function does the work of creating the envelope in - draft mode and returning a URL for the sender's view - """ - - # Step 1. Create the envelope with "created" (draft) status - args["envelope_args"]["status"] = "created" - # Using worker from example 002 - results = eg002_signing_via_email.worker(args) - envelope_id = results["envelope_id"] - - # Step 2. Create the sender view - view_request = ReturnUrlRequest(return_url=args["ds_return_url"]) - # Exceptions will be caught by the calling function - api_client = ApiClient() - api_client.host = args["base_path"] - api_client.set_default_header("Authorization", "Bearer " + args["ds_access_token"]) - envelope_api = EnvelopesApi(api_client) - results = envelope_api.create_sender_view(args["account_id"], envelope_id, return_url_request=view_request) - - # Switch to Recipient and Documents view if requested by the user - url = results.url - if args["starting_view"] == "recipient": - url = url.replace("send=1", "send=0") - - return {"envelope_id": envelope_id, "redirect_url": url} -# ***DS.snippet.0.end - - -def get_controller(): - """responds with the form for the example""" - - if views.ds_token_ok(): - return render_template("eg011_embedded_sending.html", - title="Embedded Sending", - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - signer_name=ds_config.DS_CONFIG["signer_name"], - signer_email=ds_config.DS_CONFIG["signer_email"] - ) - else: - # Save the current operation so it will be resumed after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) diff --git a/app/eg012_embedded_console.py b/app/eg012_embedded_console.py deleted file mode 100644 index 1165feb1..00000000 --- a/app/eg012_embedded_console.py +++ /dev/null @@ -1,122 +0,0 @@ -"""012: Embedded console""" - -from flask import render_template, url_for, redirect, session, flash, request -from os import path -import json -import re -from app import ds_config, views -from docusign_esign import * -from docusign_esign.client.api_exception import ApiException - -eg = "eg012" # reference (and url) for this example - -def controller(): - """Controller router using the HTTP method""" - if request.method == "GET": - return get_controller() - elif request.method == "POST": - return create_controller() - else: - return render_template("404.html"), 404 - - -def create_controller(): - """ - 1. Check the token - 2. Call the worker method - 3. Show results - """ - minimum_buffer_min = 3 - token_ok = views.ds_token_ok(minimum_buffer_min) - if token_ok: - # 2. Call the worker method - # Strip anything other than characters listed - pattern = re.compile("([^\w \-\@\.\,])+") - starting_view = pattern.sub("", request.form.get("starting_view")) - envelope_id = "envelope_id" in session and session["envelope_id"] - args = { - "envelope_id": envelope_id, - "starting_view": starting_view, - "account_id": session["ds_account_id"], - "base_path": session["ds_base_path"], - "ds_access_token": session["ds_access_token"], - "ds_return_url": url_for("ds_return", _external=True), - } - - try: - results = worker(args) - except ApiException as err: - error_body_json = err and hasattr(err, "body") and err.body - # we can pull the DocuSign error code and message from the response body - error_body = json.loads(error_body_json) - error_code = error_body and "errorCode" in error_body and error_body["errorCode"] - error_message = error_body and "message" in error_body and error_body["message"] - # In production, may want to provide customized error messages and - # remediation advice to the user. - return render_template("error.html", - err=err, - error_code=error_code, - error_message=error_message - ) - if results: - # Redirect the user to the NDSE view - # Don't use an iFrame! - # State can be stored/recovered using the framework's session - return redirect(results["redirect_url"]) - - else: - flash("Sorry, you need to re-authenticate.") - # We could store the parameters of the requested operation - # so it could be restarted automatically. - # But since it should be rare to have a token issue here, - # we'll make the user re-enter the form data after - # authentication. - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - - -# ***DS.snippet.0.start -def worker(args): - """ - This function does the work of returning a URL for the NDSE view - """ - - # Step 1. Create the NDSE view request object - # Set the url where you want the recipient to go once they are done - # with the NDSE. It is usually the case that the - # user will never "finish" with the NDSE. - # Assume that control will not be passed back to your app. - view_request = ConsoleViewRequest(return_url=args["ds_return_url"]) - if args["starting_view"] == "envelope" and args["envelope_id"]: - view_request.envelope_id = args["envelope_id"] - - # Step 2. Get the console view url - # Exceptions will be caught by the calling function - api_client = ApiClient() - api_client.host = args["base_path"] - api_client.set_default_header("Authorization", "Bearer " + args["ds_access_token"]) - envelope_api = EnvelopesApi(api_client) - results = envelope_api.create_console_view(args["account_id"], console_view_request=view_request) - url = results.url - return {"redirect_url": url} -# ***DS.snippet.0.end - - -def get_controller(): - """responds with the form for the example""" - - if views.ds_token_ok(): - envelope_id = "envelope_id" in session and session["envelope_id"] - return render_template("eg012_embedded_console.html", - title="Embedded Console", - envelope_ok=envelope_id, - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - ) - else: - # Save the current operation so it will be resumed after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - diff --git a/app/eg013_add_doc_to_template.py b/app/eg013_add_doc_to_template.py deleted file mode 100644 index b5ef4abe..00000000 --- a/app/eg013_add_doc_to_template.py +++ /dev/null @@ -1,287 +0,0 @@ -"""Example 013: Embedded Signing Ceremony from template with added document""" - -from flask import render_template, url_for, redirect, session, flash, request -from os import path -import json -from app import app, ds_config, views -import base64 -import re -from docusign_esign import * -from docusign_esign.client.api_exception import ApiException - -eg = "eg013" # reference (and url) for this example -signer_client_id = 1000 # The id of the signer within this application. - - -def controller(): - """Controller router using the HTTP method""" - if request.method == "GET": - return get_controller() - elif request.method == "POST": - return create_controller() - else: - return render_template("404.html"), 404 - - -def create_controller(): - """ - 1. Check the token and presence of a saved template_id - 2. Call the worker method - """ - minimum_buffer_min = 3 - token_ok = views.ds_token_ok(minimum_buffer_min) - if token_ok and "template_id" in session: - # 2. Call the worker method - # More data validation would be a good idea here - # Strip anything other than characters listed - pattern = re.compile("([^\w \-\@\.\,])+") - signer_email = pattern.sub("", request.form.get("signer_email")) - signer_name = pattern.sub("", request.form.get("signer_name")) - cc_email = pattern.sub("", request.form.get("cc_email")) - cc_name = pattern.sub("", request.form.get("cc_name")) - item = pattern.sub("", request.form.get("item")) - quantity = pattern.sub("", request.form.get("quantity")) - quantity = int(quantity) - template_id = session["template_id"] - envelope_args = { - "signer_email": signer_email, - "signer_name": signer_name, - "cc_email": cc_email, - "cc_name": cc_name, - "template_id": template_id, - "signer_client_id": signer_client_id, - "item": item, - "quantity": quantity, - "ds_return_url": url_for("ds_return", _external=True) - } - args = { - "account_id": session["ds_account_id"], - "base_path": session["ds_base_path"], - "ds_access_token": session["ds_access_token"], - "envelope_args": envelope_args - } - - try: - results = worker(args) - except ApiException as err: - error_body_json = err and hasattr(err, "body") and err.body - # we can pull the DocuSign error code and message from the - # response body - error_body = json.loads(error_body_json) - error_code = error_body and "errorCode" in error_body and \ - error_body["errorCode"] - error_message = error_body and "message" in error_body and \ - error_body["message"] - # In production, may want to provide customized error messages and - # remediation advice to the user. - return render_template("error.html", - err=err, - error_code=error_code, - error_message=error_message - ) - if results: - # Redirect the user to the Signing Ceremony - # Don't use an iFrame! - # State can be stored/recovered using the framework's session - return redirect(results["redirect_url"]) - - elif not token_ok: - flash("Sorry, you need to re-authenticate.") - # We could store the parameters of the requested operation - # so it could be restarted automatically. - # But since it should be rare to have a token issue here, - # we"ll make the user re-enter the form data after - # authentication. - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - elif not "template_id" in session: - return render_template("eg013_add_doc_to_template.html", - title="Embedded Signing Ceremony from template and extra doc", - template_ok=False, - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + - path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - ) - - -# ***DS.snippet.0.start -def worker(args): - """ - Create the envelope and the embedded Signing Ceremony - 1. Create the envelope request object using composite template to - add the new document - 2. Send the envelope - 3. Make the recipient view request object - 4. Get the recipient view (Signing Ceremony) url - """ - envelope_args = args["envelope_args"] - # 1. Create the envelope request object - envelope_definition = make_envelope(envelope_args) - - # 2. call Envelopes::create API method - # Exceptions will be caught by the calling function - api_client = ApiClient() - api_client.host = args["base_path"] - api_client.set_default_header("Authorization", - "Bearer " + args["ds_access_token"]) - envelope_api = EnvelopesApi(api_client) - results = envelope_api.create_envelope(args["account_id"], - envelope_definition=envelope_definition) - envelope_id = results.envelope_id - - # 3. Create the Recipient View request object - authentication_method = "None" # How is this application authenticating - # the signer? See the "authenticationMethod" definition - # https://goo.gl/qUhGTm - recipient_view_request = RecipientViewRequest( - authentication_method=authentication_method, - client_user_id=envelope_args["signer_client_id"], - recipient_id="1", - return_url=envelope_args["ds_return_url"], - user_name=envelope_args["signer_name"], - email=envelope_args["signer_email"] - ) - # 4. Obtain the recipient_view_url for the signing ceremony - # Exceptions will be caught by the calling function - results = envelope_api.create_recipient_view(args["account_id"], - envelope_id, - recipient_view_request=recipient_view_request) - - return {"envelope_id": envelope_id, "redirect_url": results.url} - - -def make_envelope(args): - """ - Creates envelope - Uses compositing templates to add a new document to the existing template - returns an envelope definition - - The envelope request object uses Composite Template to - include in the envelope: - 1. A template stored on the DocuSign service - 2. An additional document which is a custom HTML source document - """ - - # 1. Create Recipients for server template. Note that Recipients object - # is used, not TemplateRole - # - # Create a signer recipient for the signer role of the server template - signer1 = Signer(email=args["signer_email"], name=args["signer_name"], - role_name="signer", recipient_id="1", - # Adding clientUserId transforms the template recipient - # into an embedded recipient: - client_user_id=args["signer_client_id"] - ) - # Create the cc recipient - cc1 = CarbonCopy(email=args["cc_email"], name=args["cc_name"], - role_name="cc", recipient_id="2" - ) - # Recipients object: - recipients_server_template = Recipients( - carbon_copies=[cc1], signers=[signer1]) - - # 2. create a composite template for the Server template + roles - comp_template1 = CompositeTemplate( - composite_template_id="1", - server_templates=[ - ServerTemplate(sequence="1", template_id=args["template_id"]) - ], - # Add the roles via an inlineTemplate - inline_templates=[ - InlineTemplate(sequence="1", - recipients=recipients_server_template) - ] - ) - - # Next, create the second composite template that will - # include the new document. - # - # 3. Create the signer recipient for the added document - # starting with the tab definition: - sign_here1 = SignHere(anchor_string="**signature_1**", - anchor_y_offset="10", anchor_units="pixels", - anchor_x_offset="20") - signer1_tabs = Tabs(sign_here_tabs=[sign_here1]) - - # 4. Create Signer definition for the added document - signer1AddedDoc = Signer(email=args["signer_email"], - name=args["signer_name"], - role_name="signer", recipient_id="1", - client_user_id=args["signer_client_id"], - tabs=signer1_tabs) - # 5. The Recipients object for the added document. - # Using cc1 definition from above. - recipients_added_doc = Recipients( - carbon_copies=[cc1], signers=[signer1AddedDoc]) - # 6. Create the HTML document that will be added to the envelope - doc1_b64 = base64.b64encode(bytes(create_document1(args), "utf-8"))\ - .decode("ascii") - doc1 = Document(document_base64=doc1_b64, - name="Appendix 1--Sales order", # can be different from - # actual file name - file_extension="html", document_id="1" - ) - # 6. create a composite template for the added document - comp_template2 = CompositeTemplate(composite_template_id="2", - # Add the recipients via an inlineTemplate - inline_templates=[ - InlineTemplate(sequence="2", recipients=recipients_added_doc) - ], - document=doc1 - ) - # 7. create the envelope definition with the composited templates - envelope_definition = EnvelopeDefinition( - status="sent", - composite_templates=[comp_template1, comp_template2] - ) - - return envelope_definition - - -def create_document1(args): - return f""" - - - - - - -

World Wide Corp

-

Order Processing Division

-

Ordered by {args["signer_name"]}

-

Email: {args["signer_email"]}

-

Copy to: {args["cc_name"]}, {args["cc_email"]}

-

Item: {args["item"]}, quantity: {args["quantity"]} at market price.

-

- 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. -

- -

Agreed: **signature_1**/

- - - """ -# ***DS.snippet.0.end - -def get_controller(): - """responds with the form for the example""" - - if views.ds_token_ok(): - return render_template("eg013_add_doc_to_template.html", - title="Embedded Signing Ceremony from template and extra doc", - template_ok="template_id" in session, - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - signer_name=ds_config.DS_CONFIG["signer_name"], - signer_email=ds_config.DS_CONFIG["signer_email"] - ) - else: - # Save the current operation so it will be resumed after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) diff --git a/app/eg014_collect_payment.py b/app/eg014_collect_payment.py deleted file mode 100644 index ab3bfc33..00000000 --- a/app/eg014_collect_payment.py +++ /dev/null @@ -1,338 +0,0 @@ -""" Example 014: Remote signer, cc; envelope has an order form """ - -from flask import render_template, url_for, redirect, session, flash, request -from os import path -from app import app, ds_config, views -import base64 -import re -import json -from docusign_esign import * -from docusign_esign.client.api_exception import ApiException - -eg = "eg014" # reference (and url) for this example -demo_docs_path = path.abspath(path.join(path.dirname(path.realpath(__file__)), "static/demo_documents")) - - -def controller(): - """Controller router using the HTTP method""" - if request.method == "GET": - return get_controller() - elif request.method == "POST": - return create_controller() - else: - return render_template("404.html"), 404 - - -def create_controller(): - """ - 1. Check the token - 2. Call the worker method - """ - minimum_buffer_min = 3 - if views.ds_token_ok(minimum_buffer_min): - # 2. Call the worker method - # More data validation would be a good idea here - # Strip anything other than characters listed - pattern = re.compile("([^\w \-\@\.\,])+") - signer_email = pattern.sub("", request.form.get("signer_email")) - signer_name = pattern.sub("", request.form.get("signer_name")) - cc_email = pattern.sub("", request.form.get("cc_email")) - cc_name = pattern.sub("", request.form.get("cc_name")) - envelope_args = { - "signer_email": signer_email, - "signer_name": signer_name, - "cc_email": cc_email, - "cc_name": cc_name, - "status": "sent", - "gateway_account_id": ds_config.DS_CONFIG["gateway_account_id"], - "gateway_name": ds_config.DS_CONFIG["gateway_name"], - "gateway_display_name": ds_config.DS_CONFIG["gateway_display_name"] - } - args = { - "account_id": session["ds_account_id"], - "base_path": session["ds_base_path"], - "ds_access_token": session["ds_access_token"], - "envelope_args": envelope_args - } - - try: - results = worker(args) - except ApiException as err: - error_body_json = err and hasattr(err, "body") and err.body - # we can pull the DocuSign error code and message from the response body - error_body = json.loads(error_body_json) - error_code = error_body and "errorCode" in error_body and error_body["errorCode"] - error_message = error_body and "message" in error_body and error_body["message"] - # In production, may want to provide customized error messages and - # remediation advice to the user. - return render_template("error.html", - err=err, - error_code=error_code, - error_message=error_message - ) - if results: - return render_template("example_done.html", - title="Envelope sent", - h1="Envelope sent", - message=f"""The envelope has been created and sent!
- Envelope ID {results["envelope_id"]}.""" - ) - - else: - flash("Sorry, you need to re-authenticate.") - # We could store the parameters of the requested operation - # so it could be restarted automatically. - # But since it should be rare to have a token issue here, - # we'll make the user re-enter the form data after - # authentication. - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - -# ***DS.snippet.0.start -def worker(args): - """ - 1. Create the envelope request object - 2. Send the envelope - """ - envelope_args = args["envelope_args"] - # 1. Create the envelope request object - envelope_definition = make_envelope(envelope_args) - - # 2. call Envelopes::create API method - # Exceptions will be caught by the calling function - api_client = ApiClient() - api_client.host = args["base_path"] - api_client.set_default_header("Authorization", "Bearer " + args["ds_access_token"]) - - envelope_api = EnvelopesApi(api_client) - results = envelope_api.create_envelope(args["account_id"], envelope_definition=envelope_definition) - - envelope_id = results.envelope_id - # app.logger.info(f"Envelope was created. EnvelopeId {envelope_id}") - - return {"envelope_id": envelope_id} - - -# ***DS.worker.end ***DS.snippet.1.end - - -# ***DS.snippet.2.start -def make_envelope(args): - """ - This function creates the envelope definition for the - order form. - document 1 (html) has multiple tags: - /l1q/ and /l2q/ -- quantities: drop down - /l1e/ and /l2e/ -- extended: payment lines - /l3t/ -- total -- formula - - The envelope has two recipients. - recipient 1 - signer - recipient 2 - cc - The envelope will be sent first to the signer. - After it is signed, a copy is sent to the cc person. - - ################################################################# - # # - # NOTA BENA: This method programmatically constructs the # - # order form. For many use cases, it would be # - # better to create the order form as a template # - # using the DocuSign web tool as WYSIWYG # - # form designer. # - # # - ################################################################# - - """ - - # Order form constants - l1_name = "Harmonica" - l1_price = 5 - l1_description = f"${l1_price} each" - l2_name = "Xylophone" - l2_price = 150 - l2_description = f"${l2_price} each" - currency_multiplier = 100 - - # read the html file from a local directory - # The read could raise an exception if the file is not available! - doc1_file = "order_form.html" - with open(path.join(demo_docs_path, doc1_file), "r") as file: - doc1_html_v1 = file.read() - - # Substitute values into the HTML - # Substitute for: {signerName}, {signerEmail}, {cc_name}, {cc_email} - doc1_html_v2 = doc1_html_v1.replace("{signer_name}", args["signer_name"]) \ - .replace("{signer_email}", args["signer_email"]) \ - .replace("{cc_name}", args["cc_name"]) \ - .replace("{cc_email}", args["cc_email"]) - - # create the envelope definition - envelope_definition = EnvelopeDefinition( - email_subject="Please complete your order") - # add the document - doc1_b64 = base64.b64encode(bytes(doc1_html_v2, "utf-8")).decode("ascii") - doc1 = Document(document_base64=doc1_b64, - name="Order form", # can be different from actual file name - file_extension="html", # Source data format. - document_id="1" # a label used to reference the doc - ) - envelope_definition.documents = [doc1] - # create a signer recipient to sign the document - signer1 = Signer(email=args["signer_email"], name=args["signer_name"], - recipient_id="1", routing_order="1") - # create a cc recipient to receive a copy of the documents - cc1 = CarbonCopy(email=args["cc_email"], name=args["cc_name"], - recipient_id="2", routing_order="2") - # Create signHere fields (also known as tabs) on the documents, - # We're using anchor (autoPlace) positioning - sign_here1 = SignHere( - anchor_string="/sn1/", - anchor_y_offset="10", anchor_units="pixels", - anchor_x_offset="20") - list_item0 = ListItem(text="none", value="0") - list_item1 = ListItem(text="1", value="1") - list_item2 = ListItem(text="2", value="2") - list_item3 = ListItem(text="3", value="3") - list_item4 = ListItem(text="4", value="4") - list_item5 = ListItem(text="5", value="5") - list_item6 = ListItem(text="6", value="6") - list_item7 = ListItem(text="7", value="7") - list_item8 = ListItem(text="8", value="8") - list_item9 = ListItem(text="9", value="9") - list_item10 = ListItem(text="10", value="10") - - listl1q = List( - font="helvetica", - font_size="size11", - anchor_string="/l1q/", - anchor_y_offset="-10", anchor_units="pixels", - anchor_x_offset="0", - list_items=[list_item0, list_item1, list_item2, - list_item3, list_item4, list_item5, list_item6, - list_item7, list_item8, list_item9, list_item10], - required="true", - tab_label="l1q" - ) - listl2q = List( - font="helvetica", - font_size="size11", - anchor_string="/l2q/", - anchor_y_offset="-10", anchor_units="pixels", - anchor_x_offset="0", - list_items=[list_item0, list_item1, list_item2, - list_item3, list_item4, list_item5, list_item6, - list_item7, list_item8, list_item9, list_item10], - required="true", - tab_label="l2q" - ) - # create two formula tabs for the extended price on the line items - formulal1e = FormulaTab( - font="helvetica", - font_size="size11", - anchor_string="/l1e/", - anchor_y_offset="-8", anchor_units="pixels", - anchor_x_offset="105", - tab_label="l1e", - formula=f"[l1q] * {l1_price}", - round_decimal_places="0", - required="true", - locked="true", - disable_auto_size="false", - ) - formulal2e = FormulaTab( - font="helvetica", - font_size="size11", - anchor_string="/l2e/", - anchor_y_offset="-8", anchor_units="pixels", - anchor_x_offset="105", - tab_label="l2e", - formula=f"[l2q] * {l2_price}", - round_decimal_places="0", - required="true", - locked="true", - disable_auto_size="false", - ) - # Formula for the total - formulal3t = FormulaTab( - font="helvetica", - bold="true", - font_size="size12", - anchor_string="/l3t/", - anchor_y_offset="-8", anchor_units="pixels", - anchor_x_offset="50", - tab_label="l3t", - formula="[l1e] + [l2e]", - round_decimal_places="0", - required="true", - locked="true", - disable_auto_size="false", - ) - # Payment line items - payment_line_iteml1 = PaymentLineItem( - name=l1_name, description=l1_description, amount_reference="l1e" - ) - payment_line_iteml2 = PaymentLineItem( - name=l2_name, description=l2_description, amount_reference="l2e" - ) - payment_details = PaymentDetails( - gateway_account_id=args["gateway_account_id"], - currency_code="USD", - gateway_name=args["gateway_name"], - line_items=[payment_line_iteml1, payment_line_iteml2] - ) - # Hidden formula for the payment itself - formula_payment = FormulaTab( - tab_label="payment", - formula=f"([l1e] + [l2e]) * {currency_multiplier}", - round_decimal_places="0", - payment_details=payment_details, - hidden="true", - required="true", - locked="true", - document_id="1", - page_number="1", - x_position="0", - y_position="0" - ) - - # Tabs are set per recipient / signer - signer1_tabs = Tabs( - sign_here_tabs=[sign_here1], - list_tabs=[listl1q, listl2q], - formula_tabs=[formulal1e, formulal2e, formulal3t, formula_payment] - ) - signer1.tabs = signer1_tabs - - # Add the recipients to the envelope object - recipients = Recipients(signers=[signer1], carbon_copies=[cc1]); - envelope_definition.recipients = recipients - - # Request that the envelope be sent by setting |status| to "sent". - # To request that the envelope be created as a draft, set to "created" - envelope_definition.status = args["status"] - - return envelope_definition -# ***DS.snippet.0.end - - -def get_controller(): - """responds with the form for the example""" - - if views.ds_token_ok(): - gateway = ds_config.DS_CONFIG["gateway_account_id"] - gateway_ok = gateway and len(gateway) > 25 - - return render_template("eg014_collect_payment.html", - title="Order form with payment", - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - signer_name=ds_config.DS_CONFIG["signer_name"], - signer_email=ds_config.DS_CONFIG["signer_email"], - gateway_ok=gateway_ok - ) - else: - # Save the current operation so it will be resumed after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) diff --git a/app/eg015_envelope_tab_data.py b/app/eg015_envelope_tab_data.py deleted file mode 100644 index a32ae4c8..00000000 --- a/app/eg015_envelope_tab_data.py +++ /dev/null @@ -1,107 +0,0 @@ -"""015: Get an envelope's tab information data""" - -from flask import render_template, url_for, redirect, session, flash, request -from os import path -import json -from app import app, ds_config, views -from docusign_esign import * -from docusign_esign.client.api_exception import ApiException - -eg = "eg015" # Reference (and URL) for this example - -def controller(): - """Controller router using the HTTP method""" - if request.method == "GET": - return get_controller() - elif request.method == "POST": - return create_controller() - else: - return render_template("404.html"), 404 - -def create_controller(): - """ - 1. Check the token - 2. Call the worker method - 3. Show results - """ - minimum_buffer_min = 3 - token_ok = views.ds_token_ok(minimum_buffer_min) - if token_ok and "envelope_id" in session: - # 2. Call the worker method - args = { - "account_id": session["ds_account_id"], - "envelope_id": session["envelope_id"], - "base_path": session["ds_base_path"], - "ds_access_token": session["ds_access_token"], - } - - try: - results = worker(args) - except ApiException as err: - error_body_json = err and hasattr(err, "body") and err.body - # We can pull the DocuSign error code and message from the response body - error_body = json.loads(error_body_json) - error_code = error_body and "errorCode" in error_body and error_body["errorCode"] - error_message = error_body and "message" in error_body and error_body["message"] - # In production, you may want to provide customized error messages and - # remediation advice to the user - return render_template("error.html", - err=err, - error_code=error_code, - error_message=error_message - ) - return render_template("example_done.html", - title="Get envelope tab data results", - h1="Get envelope tab data results", - message="Results from the Envelopes::formData GET method:", - json=json.dumps(json.dumps(results.to_dict())) - ) - elif not token_ok: - flash("Sorry, you need to re-authenticate.") - # We could store the parameters of the requested operation so it could be restarted - # automatically. But since it should be rare to have a token issue here, - # we'll make the user re-enter the form data after authentication. - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - elif not "envelope_id" in session: - return render_template("eg015_envelope_tab_data.html", - title="Envelope Tab Data", - envelope_ok=False, - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - ) - -# ***DS.snippet.0.start -def worker(args): - """ - 1. Call the envelope get method - """ - - # Exceptions will be caught by the calling function - api_client = ApiClient() - api_client.host = args["base_path"] - api_client.set_default_header("Authorization", "Bearer " + args["ds_access_token"]) - envelopes_api = EnvelopesApi(api_client) - results = envelopes_api.get_form_data(args["account_id"], args["envelope_id"]) - - return results -# ***DS.snippet.0.end - -def get_controller(): - """responds with the form for the example""" - - if views.ds_token_ok(): - return render_template("eg015_envelope_tab_data.html", - title="Envelope information", - envelope_ok="envelope_id" in session, - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - ) - else: - # Save the current operation so it will be resumed after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) \ No newline at end of file diff --git a/app/eg016_set_tab_values.py b/app/eg016_set_tab_values.py deleted file mode 100644 index e49d30e7..00000000 --- a/app/eg016_set_tab_values.py +++ /dev/null @@ -1,238 +0,0 @@ -"""Example 016: Set Tab Values""" - -from flask import render_template, url_for, redirect, session, flash, request -from os import path -import json -from app import app, ds_config, views -import base64 -import re -from docusign_esign import * -from docusign_esign.client.api_exception import ApiException - -eg = "eg016" # Reference (and URL) for this example -signer_client_id = 1000 # Used to indicate that the signer will use an embedded - # signing ceremony. Represents the signer's user ID within - # your application. -authentication_method = "None" # How is this application authenticating - # the signer? See the "authenticationMethod" definition - # https://developers.docusign.com/esign-rest-api/reference/Envelopes/EnvelopeViews/createRecipient - -demo_docs_path = path.abspath(path.join(path.dirname(path.realpath(__file__)), "static/demo_documents")) - -def controller(): - """Controller router using the HTTP method""" - if request.method == "GET": - return get_controller() - elif request.method == "POST": - return create_controller() - else: - return render_template("404.html"), 404 - -def create_controller(): - """ - 1. Check the token - 2. Call the worker method - 3. Redirect the user to the signing ceremony - """ - minimum_buffer_min = 3 - if views.ds_token_ok(minimum_buffer_min): - # 2. Call the worker method - # More data validation would be a good idea here - # Strip anything other than characters listed - pattern = re.compile("([^\w \-\@\.\,])+") - signer_email = pattern.sub("", request.form.get("signer_email")) - signer_name = pattern.sub("", request.form.get("signer_name")) - envelope_args = { - "signer_email": signer_email, - "signer_name": signer_name, - "signer_client_id": signer_client_id, - "ds_return_url": url_for("ds_return", _external=True), - } - args = { - "account_id": session["ds_account_id"], - "base_path": session["ds_base_path"], - "ds_access_token": session["ds_access_token"], - "envelope_args": envelope_args - } - - try: - results = worker(args) - except ApiException as err: - error_body_json = err and hasattr(err, "body") and err.body - # We can pull the DocuSign error code and message from the response body - error_body = json.loads(error_body_json) - error_code = error_body and "errorCode" in error_body and error_body["errorCode"] - error_message = error_body and "message" in error_body and error_body["message"] - # In production, you may want to provide customized error messages and - # remediation advice to the user - return render_template("error.html", - err=err, - error_code=error_code, - error_message=error_message - ) - if results: - session["envelope_id"] = results["envelope_id"] # Save for use by other examples - # that need an envelope ID - # Redirect the user to the signing ceremony - # Don't use an iframe! - # State can be stored/recovered using the framework's session or a - # query parameter on the return URL (see the makeRecipientViewRequest method) - return redirect(results["redirect_url"]) - - else: - flash("Sorry, you need to re-authenticate.") - # We could store the parameters of the requested operation so it could be restarted - # automatically. But since it should be rare to have a token issue here, - # we'll make the user re-enter the form data after authentication. - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - -# ***DS.snippet.0.start -def worker(args): - """ - 1. Create the envelope request object - 2. Send the envelope - 3. Create the Recipient View request object - 4. Obtain the recipient_view_url for the signing ceremony - """ - envelope_args = args["envelope_args"] - # 1. Create the envelope request object - envelope_definition = make_envelope(envelope_args) - - # 2. call Envelopes::create API method - # Exceptions will be caught by the calling function - api_client = ApiClient() - api_client.host = args["base_path"] - api_client.set_default_header("Authorization", "Bearer " + args["ds_access_token"]) - - envelopes_api = EnvelopesApi(api_client) - results = envelopes_api.create_envelope(args["account_id"], envelope_definition=envelope_definition) - - envelope_id = results.envelope_id - app.logger.info(f"Envelope was created. EnvelopeId {envelope_id}") - - # 3. Create the RecipientViewRequest object - recipient_view_request = RecipientViewRequest( - authentication_method = authentication_method, - client_user_id = envelope_args["signer_client_id"], - recipient_id = "1", - return_url = envelope_args["ds_return_url"], - user_name = envelope_args["signer_name"], email = envelope_args["signer_email"] - ) - # 4. Obtain the recipient view URL for the signing ceremony - # Exceptions will be caught by the calling function - results = envelopes_api.create_recipient_view(args["account_id"], envelope_id, - recipient_view_request = recipient_view_request) - - return {"envelope_id": envelope_id, "redirect_url": results.url} - -def make_envelope(args): - """ - Creates envelope - args -- parameters for the envelope: - signer_email, signer_name, signer_client_id - returns an envelope definition - """ - - # Document 1 (PDF) has tag /sn1/ - # - # The envelope has one recipient: - # recipient 1 - signer - with open(path.join(demo_docs_path, ds_config.DS_CONFIG["doc_salary_docx"]), "rb") as file: - content_bytes = file.read() - base64_file_content = base64.b64encode(content_bytes).decode("ascii") - - # Create the document model - document = Document( # Create the DocuSign document object - document_base64 = base64_file_content, - name = "Lorem Ipsum", # Can be different from the actual filename - file_extension = "docx", # Many different document types are accepted - document_id = 1 # A label used to reference the doc - ) - - # Create the signer recipient model - signer = Signer( # The signer - email = args["signer_email"], name = args["signer_name"], - recipient_id = "1", routing_order = "1", - # Setting the client_user_id marks the signer as embedded - client_user_id = args["signer_client_id"] - ) - - # Create a SignHere tab (field on the document) - sign_here = SignHere( # DocuSign SignHere field/tab - anchor_string = "/sn1/", anchor_units = "pixels", - anchor_y_offset = "10", anchor_x_offset = "20" - ) - - text_legal = Text( - anchor_string = "/legal/", anchor_units = "pixels", - anchor_y_offset = "-9", anchor_x_offset = "5", - font = "helvetica", font_size = "size11", - bold = "true", value = args["signer_name"], - locked = "false", tab_id = "legal_name", - tab_label = "Legal name" ) - - text_familar = Text( - anchor_string = "/familiar/", anchor_units = "pixels", - anchor_y_offset = "-9", anchor_x_offset = "5", - font = "helvetica", font_size = "size11", - bold = "true", value = args["signer_name"], - locked = "false", tab_id = "familar_name", - tab_label = "Familiar name" ) - - salary = 123000 - - text_salary = Text( - anchor_string = "/salary/", - anchor_units = "pixels", - anchor_y_offset = "-9", - anchor_x_offset = "5", - font = "helvetica", - font_size = "size11", - bold = "true", - value = "${:.2f}".format(salary), - locked = "true", - tab_id = "salary", - tab_label = "Salary") - - salary_custom_field = TextCustomField( - name = "salary", - required = "false", - show = "true", # Yes, include in the CoC - value = str(salary) - ) - cf = CustomFields( text_custom_fields= [ salary_custom_field ] ) - # Add the tabs model (including the SignHere tab) to the signer - # The Tabs object wants arrays of the different field/tab types - signer.tabs = Tabs(sign_here_tabs = [sign_here], text_tabs = [text_legal, text_familar, text_salary]) - - # Create the top level envelope definition and populate it - envelope_definition = EnvelopeDefinition( - email_subject = "Please sign this document sent from the Python SDK", - documents = [document], - # The Recipients object wants arrays for each recipient type - recipients = Recipients(signers = [signer]), - custom_fields= cf, - status = "sent" # Requests that the envelope be created and sent - ) - - return envelope_definition -# ***DS.snippet.0.end - -def get_controller(): - """responds with the form for the example""" - - if views.ds_token_ok(): - return render_template("eg016_set_tab_values.html", - title="SetTabValues", - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - signer_name=ds_config.DS_CONFIG["signer_name"], - signer_email=ds_config.DS_CONFIG["signer_email"] - ) - else: - # Save the current operation so it will be resumed after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) \ No newline at end of file diff --git a/app/eg017_set_template_tab_values.py b/app/eg017_set_template_tab_values.py deleted file mode 100644 index a6541cb0..00000000 --- a/app/eg017_set_template_tab_values.py +++ /dev/null @@ -1,241 +0,0 @@ -"""Example 017: Set Template Tab Values""" - -from flask import render_template, url_for, redirect, session, flash, request -from os import path -import json -from app import app, ds_config, views -import base64 -import re -from docusign_esign import * -from docusign_esign.client.api_exception import ApiException - -eg = "eg017" # reference (and url) for this example -signer_client_id = 1000 # Used to indicate that the signer will use an embedded - # Signing Ceremony. Represents the signer's userId within - # your application. -authentication_method = "None" # How is this application authenticating - # the signer? See the "authenticationMethod" definition - # https://developers.docusign.com/esign-rest-api/reference/Envelopes/EnvelopeViews/createRecipient - -demo_docs_path = path.abspath(path.join(path.dirname(path.realpath(__file__)), "static/demo_documents")) - -def controller(): - """Controller router using the HTTP method""" - if request.method == "GET": - return get_controller() - elif request.method == "POST": - return create_controller() - else: - return render_template("404.html"), 404 - -def create_controller(): - """ - 1. Check the token - 2. Call the worker method - 3. Redirect the user to the signing ceremony - """ - minimum_buffer_min = 3 - if views.ds_token_ok(minimum_buffer_min): - # 2. Call the worker method - # More data validation would be a good idea here - # Strip anything other than characters listed - pattern = re.compile("([^\w \-\@\.\,])+") - signer_email = pattern.sub("", request.form.get("signer_email")) - signer_name = pattern.sub("", request.form.get("signer_name")) - cc_email = pattern.sub("", request.form.get("cc_email")) - cc_name = pattern.sub("", request.form.get("cc_name")) - envelope_args = { - "signer_email": signer_email, - "signer_name": signer_name, - "signer_client_id": signer_client_id, - "ds_return_url": url_for("ds_return", _external=True), - "cc_email" : cc_email, - "cc_name" : cc_name - } - args = { - "account_id": session["ds_account_id"], - "base_path": session["ds_base_path"], - "ds_access_token": session["ds_access_token"], - "envelope_args": envelope_args - } - - try: - results = worker(args) - except ApiException as err: - error_body_json = err and hasattr(err, "body") and err.body - # we can pull the DocuSign error code and message from the response body - error_body = json.loads(error_body_json) - error_code = error_body and "errorCode" in error_body and error_body["errorCode"] - error_message = error_body and "message" in error_body and error_body["message"] - # In production, may want to provide customized error messages and - # remediation advice to the user. - return render_template("error.html", - err=err, - error_code=error_code, - error_message=error_message - ) - if results: - session["envelope_id"] = results["envelope_id"] # Save for use by other examples - # which need an envelopeId - # Redirect the user to the Signing Ceremony - # Don't use an iFrame! - # State can be stored/recovered using the framework's session or a - # query parameter on the returnUrl (see the makeRecipientViewRequest method) - return redirect(results["redirect_url"]) - - else: - flash("Sorry, you need to re-authenticate.") - # We could store the parameters of the requested operation - # so it could be restarted automatically. - # But since it should be rare to have a token issue here, - # we'll make the user re-enter the form data after - # authentication. - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - -# ***DS.snippet.0.start -def worker(args): - """ - 1. Create the envelope request object - 2. Send the envelope - 3. Create the Recipient View request object - 4. Obtain the recipient_view_url for the signing ceremony - """ - envelope_args = args["envelope_args"] - # 1. Create the envelope request object - envelope_definition = make_envelope(envelope_args) - - # 2. call Envelopes::create API method - # Exceptions will be caught by the calling function - api_client = ApiClient() - api_client.host = args["base_path"] - api_client.set_default_header("Authorization", "Bearer " + args["ds_access_token"]) - - envelopes_api = EnvelopesApi(api_client) - results = envelopes_api.create_envelope(args["account_id"], envelope_definition=envelope_definition) - - envelope_id = results.envelope_id - app.logger.info(f"Envelope was created. EnvelopeId {envelope_id}") - - # 3. Create the Recipient View request object - recipient_view_request = RecipientViewRequest( - authentication_method = authentication_method, - client_user_id = envelope_args["signer_client_id"], - recipient_id = "1", - return_url = envelope_args["ds_return_url"], - user_name = envelope_args["signer_name"], email = envelope_args["signer_email"] - ) - # 4. Obtain the recipient_view_url for the signing ceremony - # Exceptions will be caught by the calling function - results = envelopes_api.create_recipient_view(args["account_id"], envelope_id, - recipient_view_request = recipient_view_request) - return {"envelope_id": envelope_id, "redirect_url": results.url} - -def make_envelope(args): - """ - Creates envelope - args -- parameters for the envelope: - signer_email, signer_name, signer_client_id - returns an envelope definition - """ - - # Set the values for the fields in the template - # List item - list1 = List( - value = "green", document_id= "1", - page_number= "1", tab_label= "list" ) - - # Checkboxes - check1 = Checkbox( - tab_label= "ckAuthorization", selected = "true" ) - - check3 = Checkbox( - tab_label= "ckAgreement", selected = "true" ) - - radio_group = RadioGroup( - group_name = "radio1", - radios = [ Radio( value = "white", selected = "true") ] - ) - - text = Text( - tab_label = "text", value = "Jabberywocky!" - ) - - # We can also add a new tab (field) to the ones already in the template: - text_extra = Text( - document_id = "1", page_number = "1", - x_position = "280", y_position = "172", - font = "helvetica", font_size = "size14", - tab_label = "added text field", height = "23", - width = "84", required = "false", - bold = "true", value = args["signer_name"], - locked = "false", tab_id = "name" - ) - - # Add the tabs model (including the SignHere tab) to the signer. - # The Tabs object wants arrays of the different field/tab types - # Tabs are set per recipient / signer - tabs = Tabs( - checkbox_tabs = [check1, check3], radio_group_tabs = [radio_group], - text_tabs = [text, text_extra], list_tabs = [list1] - ) - - # create a signer recipient to sign the document, identified by name and email - # We"re setting the parameters via the object creation - signer = TemplateRole( # The signer - email = args["signer_email"], name = args["signer_name"], - # Setting the client_user_id marks the signer as embedded - client_user_id = args["signer_client_id"], - role_name = "signer", - tabs = tabs - ) - - cc = TemplateRole( - email = args["cc_email"], - name = args["cc_name"], - role_name="cc" - ) - - # create an envelope custom field to save our application's - # data about the envelope - - custom_field = TextCustomField( - name="app metadata item", - required="false", - show="true", # Yes, include in the CoC - value="1234567" - ) - - cf = CustomFields(text_custom_fields=[custom_field]) - - # Next, create the top level envelope definition and populate it. - envelope_definition=EnvelopeDefinition( - email_subject="Please sign this document sent from the Python SDK", - # The Recipients object wants arrays for each recipient type - template_id=session["template_id"], - template_roles=[signer, cc], - custom_fields=cf, - status = "sent" # requests that the envelope be created and sent. - ) - - return envelope_definition -# ***DS.snippet.0.end - -def get_controller(): - """responds with the form for the example""" - - if views.ds_token_ok(): - return render_template("eg017_set_template_tab_values.html", - title="SetTemplateTabValues", - template_ok="template_id" in session, - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - signer_name=ds_config.DS_CONFIG["signer_name"], - signer_email=ds_config.DS_CONFIG["signer_email"] - ) - else: - # Save the current operation so it will be resumed after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) \ No newline at end of file diff --git a/app/eg018_envelope_custom_field_data.py b/app/eg018_envelope_custom_field_data.py deleted file mode 100644 index 9e7e8b52..00000000 --- a/app/eg018_envelope_custom_field_data.py +++ /dev/null @@ -1,107 +0,0 @@ -"""018: Get an envelope's custom field data""" - -from flask import render_template, url_for, redirect, session, flash, request -from os import path -import json -from app import app, ds_config, views -from docusign_esign import * -from docusign_esign.client.api_exception import ApiException - -eg = "eg018" # reference (and URL) for this example - -def controller(): - """Controller router using the HTTP method""" - if request.method == "GET": - return get_controller() - elif request.method == "POST": - return create_controller() - else: - return render_template("404.html"), 404 - -def create_controller(): - """ - 1. Check the token - 2. Call the worker method - 3. Show results - """ - minimum_buffer_min = 3 - token_ok = views.ds_token_ok(minimum_buffer_min) - if token_ok and "envelope_id" in session: - # 2. Call the worker method - args = { - "account_id": session["ds_account_id"], - "envelope_id": session["envelope_id"], - "base_path": session["ds_base_path"], - "ds_access_token": session["ds_access_token"], - } - - try: - results = worker(args) - except ApiException as err: - error_body_json = err and hasattr(err, "body") and err.body - # We can pull the DocuSign error code and message from the response body - error_body = json.loads(error_body_json) - error_code = error_body and "errorCode" in error_body and error_body["errorCode"] - error_message = error_body and "message" in error_body and error_body["message"] - # In production, you may want to provide customized error messages and - # remediation advice to the user. - return render_template("error.html", - err=err, - error_code=error_code, - error_message=error_message - ) - return render_template("example_done.html", - title="Get custom field data", - h1="Envelope custom field data", - message="Results from the EnvelopeCustomFields::list method:", - json=json.dumps(json.dumps(results.to_dict())) - ) - elif not token_ok: - flash("Sorry, you need to re-authenticate.") - # We could store the parameters of the requested operation so it could be restarted - # automatically. But since it should be rare to have a token issue here, - # we'll make the user re-enter the form data after authentication. - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - elif not "envelope_id" in session: - return render_template("eg018_envelope_custom_field_data.html", - title="Envelope Custom Field Data", - envelope_ok=False, - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - ) - -# ***DS.snippet.0.start -def worker(args): - """ - 1. Call the envelope get method - """ - - # Exceptions will be caught by the calling function - api_client = ApiClient() - api_client.host = args["base_path"] - api_client.set_default_header("Authorization", "Bearer " + args["ds_access_token"]) - envelopes_api = EnvelopesApi(api_client) - results = envelopes_api.list_custom_fields(args["account_id"], args["envelope_id"]) - - return results -# ***DS.snippet.0.end - -def get_controller(): - """responds with the form for the example""" - - if views.ds_token_ok(): - return render_template("eg015_envelope_tab_data.html", - title="Envelope information", - envelope_ok="envelope_id" in session, - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - ) - else: - # Save the current operation so it will be resumed after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) diff --git a/app/eg019_access_code_authentication.py b/app/eg019_access_code_authentication.py deleted file mode 100644 index 5323542b..00000000 --- a/app/eg019_access_code_authentication.py +++ /dev/null @@ -1,160 +0,0 @@ -""" Example 019: Access Code Recipient Authentication""" - -from flask import render_template, url_for, redirect, session, flash, request -from os import path -from app import app, ds_config, views -import base64 -import re -import json -from docusign_esign import * -from docusign_esign.client.api_exception import ApiException - -eg = "eg019" # reference (and url) for this example -# access_code = "NJ9@D1" -demo_docs_path = path.abspath(path.join(path.dirname(path.realpath(__file__)), "static/demo_documents")) - - -def controller(): - """Controller router using the HTTP method""" - if request.method == "GET": - return get_controller() - elif request.method == "POST": - return create_controller() - else: - return render_template("404.html"), 404 - - -def create_controller(): - """ - 1. Check the token - 2. Call the worker method - """ - minimum_buffer_min = 3 - if views.ds_token_ok(minimum_buffer_min): - - # More data validation would be a good idea here - # Strip anything other than characters listed - pattern = re.compile("([^\w \-\@\.\,])+") - signer_email = pattern.sub("", request.form.get("signer_email")) - signer_name = pattern.sub("", request.form.get("signer_name")) - recip_access_code = request.form.get("accessCode") - envelope_args = { - "signer_email": signer_email, - "signer_name": signer_name, - "status": "sent", - } - args = { - - # Step 1: Obtain your OAuth token - "account_id": session["ds_account_id"], # represents your {ACCOUNT_ID} - "base_path": session["ds_base_path"], - "ds_access_token": session["ds_access_token"], # represnts your {ACCESS_TOKEN} - "envelope_args": envelope_args - } - try: - - # Step 2: Construct your API headers - api_client = ApiClient() - api_client.host = args["base_path"] - api_client.set_default_header("Authorization", "Bearer " + args["ds_access_token"]) - - # Step 3: Construct your envelope JSON body - envelope_definition = EnvelopeDefinition( - email_subject="Please sign this document set" - ) - # Add a Document - document1 = Document( # create the DocuSign document object - document_base64="JVBERi0xLjMNJeLjz9MNCjUgMCBvYmoNPDwvTGluZWFyaXplZCAxL0wgNDI3MTAvTyA3L0UgMzg3NDMvTiAxL1QgNDI0OTEvSCBbIDg5NiAxODVdPj4NZW5kb2JqDSAgICAgICAgICAgICAgICAgICAgDQp4cmVmDQo1IDMwDQowMDAwMDAwMDE2IDAwMDAwIG4NCjAwMDAwMDEwODEgMDAwMDAgbg0KMDAwMDAwMTE0MSAwMDAwMCBuDQowMDAwMDAxMzE4IDAwMDAwIG4NCjAwMDAwMDE0NzkgMDAwMDAgbg0KMDAwMDAwMTg0OCAwMDAwMCBuDQowMDAwMDAxOTk2IDAwMDAwIG4NCjAwMDAwMDIxOTcgMDAwMDAgbg0KMDAwMDAwMjYyMSAwMDAwMCBuDQowMDAwMDAyNjU2IDAwMDAwIG4NCjAwMDAwMDMzOTYgMDAwMDAgbg0KMDAwMDAwMzkwMSAwMDAwMCBuDQowMDAwMDA0NDExIDAwMDAwIG4NCjAwMDAwMDUwMTEgMDAwMDAgbg0KMDAwMDAwNTUzMCAwMDAwMCBuDQowMDAwMDA2MDQ5IDAwMDAwIG4NCjAwMDAwMDY1ODcgMDAwMDAgbg0KMDAwMDAwNjk4MyAwMDAwMCBuDQowMDAwMDA5NjkwIDAwMDAwIG4NCjAwMDAwMTYzMjUgMDAwMDAgbg0KMDAwMDAxNjU0NyAwMDAwMCBuDQowMDAwMDE3MDg3IDAwMDAwIG4NCjAwMDAwMTczMDYgMDAwMDAgbg0KMDAwMDAxNzYwMCAwMDAwMCBuDQowMDAwMDE5NTcxIDAwMDAwIG4NCjAwMDAwMTk3OTUgMDAwMDAgbg0KMDAwMDAyMDE3MiAwMDAwMCBuDQowMDAwMDMwNTAxIDAwMDAwIG4NCjAwMDAwMzA3MzMgMDAwMDAgbg0KMDAwMDAwMDg5NiAwMDAwMCBuDQp0cmFpbGVyDQo8PC9TaXplIDM1L1Jvb3QgNiAwIFIvSW5mbyA0IDAgUi9JRFs8OTNEREQ1RjRBQjk1NTU2NTVFMUFFQkU3Mjc4OTFGNzQ+PDUyRUNGNjUzRTlDQTM4NDNBMEI2MTY0ODI1RkZENjJDPl0vUHJldiA0MjQ4MT4+DQpzdGFydHhyZWYNCjANCiUlRU9GDQogICAgICAgICAgICAgICAgDQozNCAwIG9iag08PC9GaWx0ZXIvRmxhdGVEZWNvZGUvSSAxMTYvTGVuZ3RoIDEwNC9TIDQwPj5zdHJlYW0NCmjeYmBgkGZgYN7DAASTHjGgAmYgZmHgWIAqKg3FDAzKDHxMFuwPghsKmWZIBDAwHWSPkN3Q6/iEfYJ8QZRXQboC94Y6hx0sPJUM+o5hC27whJ88ADWDhYFhSRiQZgTiRwABBgBLlxXzDQplbmRzdHJlYW0NZW5kb2JqDTYgMCBvYmoNPDwvTWV0YWRhdGEgMiAwIFIvUGFnZXMgMSAwIFIvVHlwZS9DYXRhbG9nPj4NZW5kb2JqDTcgMCBvYmoNPDwvQ29udGVudHNbMTQgMCBSIDE1IDAgUiAxNiAwIFIgMTcgMCBSIDE4IDAgUiAxOSAwIFIgMjAgMCBSIDIxIDAgUl0vQ3JvcEJveFswIDAgNjEyIDc5Ml0vTWVkaWFCb3hbMCAwIDYxMiA3OTJdL1BhcmVudCAxIDAgUi9SZXNvdXJjZXMgOCAwIFIvUm90YXRlIDAvVHlwZS9QYWdlPj4NZW5kb2JqDTggMCBvYmoNPDwvQ29sb3JTcGFjZTw8L0NzMSAxMyAwIFI+Pi9Gb250PDwvVFQxIDkgMCBSL1RUMyAxMCAwIFIvVFQ1IDExIDAgUi9UVDYgMTIgMCBSPj4vUHJvY1NldFsvUERGL1RleHQvSW1hZ2VCL0ltYWdlQy9JbWFnZUldL1hPYmplY3Q8PC9JbTEgMzMgMCBSPj4+Pg1lbmRvYmoNOSAwIG9iag08PC9CYXNlRm9udC9aUFFQU0ErVHJlYnVjaGV0TVMvRW5jb2RpbmcvTWFjUm9tYW5FbmNvZGluZy9GaXJzdENoYXIgMzIvRm9udERlc2NyaXB0b3IgMjQgMCBSL0xhc3RDaGFyIDExOC9TdWJ0eXBlL1RydWVUeXBlL1R5cGUvRm9udC9XaWR0aHNbMzAxIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgNTk4IDYxMyAwIDAgMCAwIDI3OCAwIDAgNTA2IDAgMCAwIDAgMCAwIDAgMCAwIDAgODUyIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDU1NyA1NDUgMzcwIDAgMCAyODUgMCAwIDI5NSA4MzAgNTQ2IDUzNyA1NTcgMCAzODkgNDA1IDAgNTQ2IDQ5MF0+Pg1lbmRvYmoNMTAgMCBvYmoNPDwvQmFzZUZvbnQvTVVLRlJOK0NhbGlicmkvRmlyc3RDaGFyIDMzL0ZvbnREZXNjcmlwdG9yIDI2IDAgUi9MYXN0Q2hhciAzMy9TdWJ0eXBlL1RydWVUeXBlL1RvVW5pY29kZSAyNyAwIFIvVHlwZS9Gb250L1dpZHRoc1syMjZdPj4NZW5kb2JqDTExIDAgb2JqDTw8L0Jhc2VGb250L0hGQU1aRitDYWxpYnJpLUJvbGQvRmlyc3RDaGFyIDMzL0ZvbnREZXNjcmlwdG9yIDI5IDAgUi9MYXN0Q2hhciA0NS9TdWJ0eXBlL1RydWVUeXBlL1RvVW5pY29kZSAzMCAwIFIvVHlwZS9Gb250L1dpZHRoc1syMjYgNjA2IDQ3NCAzNTUgNTAzIDUzNyA0OTQgNTM3IDM5OSAyNDYgMjc2IDQzMCA1MDddPj4NZW5kb2JqDTEyIDAgb2JqDTw8L0Jhc2VGb250L1VHSkVDSCtIZWx2ZXRpY2EvRW5jb2RpbmcvTWFjUm9tYW5FbmNvZGluZy9GaXJzdENoYXIgMzIvRm9udERlc2NyaXB0b3IgMzIgMCBSL0xhc3RDaGFyIDEyMi9TdWJ0eXBlL1RydWVUeXBlL1R5cGUvRm9udC9XaWR0aHNbMjc4IDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAyNzggMzMzIDI3OCAwIDAgMCA1NTYgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDY2NyA2NjcgNzIyIDcyMiAwIDAgMCAwIDI3OCAwIDY2NyA1NTYgMCA3MjIgNzc4IDY2NyAwIDcyMiAwIDYxMSA3MjIgMCAwIDY2NyAwIDAgMCAwIDAgMCAwIDAgNTU2IDU1NiA1MDAgNTU2IDU1NiAyNzggNTU2IDU1NiAyMjIgMCA1MDAgMjIyIDgzMyA1NTYgNTU2IDU1NiAwIDMzMyA1MDAgMjc4IDU1NiA1MDAgNzIyIDUwMCA1MDAgNTAwXT4+DWVuZG9iag0xMyAwIG9iag1bL0lDQ0Jhc2VkIDIyIDAgUl0NZW5kb2JqDTE0IDAgb2JqDTw8L0ZpbHRlci9GbGF0ZURlY29kZS9MZW5ndGggNjcwPj5zdHJlYW0NCkiJjFVNc9MwEL3rV2wpBRsaVd+SrxQOZbgw45kcCIeSpDSQr8Y0DP8eWbLl2LKdJhlrZa3e2327Up7gKzwBZeVPGgNaGTgsYQpbuLktKMwLIEAwZVJkhFmLayU0UVDM7T6CmSjXvUEVx1oSCirTWAg038CHHChxDmHMN3CT5xQo5A+QQAr5L/iUuzA6cJrgzBDZhiOYEEuRz8eBv0Ey3aXWTg7rBUxXiyXc2vl3yD9bNhSzcZZhySWr2MCzTRwdH6FDPo/DfjgRi4oZt5gtaCGdRzV4NF6hXYyoEkTWChtoRGFlkNI4Tz+gjtSjWjNMjVQRKjdnYK3QH1cpMEiOTm5vF+7pbV+GbRC/h54xggXn2tNXlW6RvrxtGBNYSSpaWE4gHaUS9czuwQULo8EKiqVUOiLg/DzBl6op3XO5SWHCHJ2d3aW2+sm+cJPnzVgInFGsiWk3QauhurVv9PLd23d8s6zqLEntgYeeKlSoEs62qbYHyRZC98JYVdCLYWRmMGenLSmGWlIGmV9dOhVfu+fV1Rs3Xrh2fOvsWeKG04VZaicoeecml6de1f73YyWhhmOpMt7EiwZ6uCdt6r59NTEK6/Lq8Kin3Ryfd+iqcD1LywwmNvaJH65Pr8DBTmDU4ExmbemHqdC5CmqKs/MwpSIodCmm3H16rZ6YSx6hcKZqHp+dH6r+UM0xTMur3dZ5WRvHYB3ua+tn5AUpEhwLSGKfVW2se3aB32VvlxYFSjbB6W9t7EIgv4uw80+1cxdh7muoQ1g7hlgWw7HcR29qAJT8qNeea6OoAwjexb9tFPE+uK9qqCJKvskvLD1GIgZZHyPpY32b2q2jpHax08HqgzoQQf0CdxO9q17UCkSNgsLSKiZzfyXwX4ABALkO5agNCmVuZHN0cmVhbQ1lbmRvYmoNMTUgMCBvYmoNPDwvRmlsdGVyL0ZsYXRlRGVjb2RlL0xlbmd0aCA0MzU+PnN0cmVhbQ0KSImUVT1zgkAQ7e9XbAlFLgiI2mYmTZpMZq7LpCD4RQyagOL47wPCLeTeQcxYuLO3t/fe27eqnuhR0Qt9kyf9kLz6Uwczn6aBL6cRJRk9KJpeT9ovoTK6VyqiCak1vZITu3RXXSPnC4L8oCMuSrY6WrmiCQqqUjO5IOfo0jXD1/gk4VTO3btUrFvpBitoUOijnKFA8aYJhHOpr83JWZt4CriTWyB+doEw6L/bwBt4Up0oTRqCa1hFLt7rYIepDchxdAUq3WN8YnjwIlC3oEL9D0iqY46CtXBKLDnZ3ngjNeLlSYXAFxYvk+lli3PRbkVq2i2FGpZetiUVyKhaKXKetQ/ZbTFc4n6JTaI2uHBL7tShPvfEaYpGZ9SUbDsawniMERXAOeH7Z5Ah54Eu4bUyBSSImjs1xhTdyRKh5ScY2uBO9TrhvuyBSDnmsTBYyDAMJv9x2vDW9zwkBiWLQSmL+DuWAX+IktvXxwvk7La/ApxJb9uL1KRcO7hFJQzh+3b57ZEMF5Kp82zzbAWz1K/KMb6+70svCsOW9d+jrAGqj0HxwtlcevNKPGFrQz8CDAB5QXlvDQplbmRzdHJlYW0NZW5kb2JqDTE2IDAgb2JqDTw8L0ZpbHRlci9GbGF0ZURlY29kZS9MZW5ndGggNDQwPj5zdHJlYW0NCkiJjFTJboMwEL37K+YIUuIQICzHRuqlt0o+VIp6oCVJqULSOpvy92WbMcqYFHHAmuX5zfOzZ0pFMAe1gRU4S124MPWkD84eF1tcQLUIIhmDc3JFEznYUtBEvjC1Hq7J2pRwPrBm96DriqE+xXdQL/Cs4BV+a8wQvPqrF/NkIZMgiiGME+kl4rOEpYJFk8efKmFG4ztTF9T3EFhagfnJaLDVAN/7oY5nV7QxfaG6S8b06HWgepqqMLJGrC3bmvQ88mOwnCIhEeWKaLfSlrP5oSSBEbtPdIuZz4KQY4gASq5bhrSQesG6qFZ2JWaLp9NdSIxy65pB572izoCCeyb2IYwCGSUwxi00wKZTqyaQpv1h7STbmpzJfps0baJXtGVq/XPb27YN+QStemW7aSIw6Ync9mccco9QNMrVVnRPV5e2myGGdGIXxGDzmiMmhSF1ObDtjHfzYaXwFgintKjPDUURfqcLROION9BvZlmwefjlEGPNNfy61uYOU+mFo55CfWbkuYVu9UnOvUCmNjYZk/2hdwmJPXKPvNto1jVmFjDz9nJe5c5GbFhBP6rkS6OejPAnwADWhXX1DQplbmRzdHJlYW0NZW5kb2JqDTE3IDAgb2JqDTw8L0ZpbHRlci9GbGF0ZURlY29kZS9MZW5ndGggNTMwPj5zdHJlYW0NCkiJjFVNU4MwEL3nV+yRHhqhobVc/bh4cZzBk+OhUmqroDXYOvrrBWSXbTYwDgcyydu3Xy+bixTmITRf90tLOEvTBUSQbuABgnwC01DPIFjjAupFFBqdQJAVf5sqeMfTwxi+Ilgh8J8TaJl2uEHYN1zoFnLCeI9nFQVq6VgFz7iZC7IevzoNwZ+zopxlXMJOnLCIdzXVKYgdWqJYsUo9QnoD1yncwUdDF7eNahdzs9BhaBKI40SHscpKuBjtaDCdQPriJzufQWxivVxCVqpxmoc+NepXmbOE2v8XqmMn2l642K1I/Cj4ygPWrnALTB72woOgQaVJzWZbQUOg3WBzq1od7WLTbZCNBXcHIfivto4jFVjpiWSsx8RgTKijmfl3E5uCjGkhTGqXHkUpVwqXond7UTUs1srpDxPHD+/UNDLnOvJNAXZYiOJ9eVBSWrKwbs3Z6GBM2DV7GMyPoYXO2BnZ0YgZHh6E3XQ6+0YinhMjf/INEWexx6tEGPvKKI7S9CAiJbzqWdbDia09oZJdJub/UdT2WxQXvMV9wh7mLoOolm96V7jTvzdq+NbVN8UksZ4vfFdFTM3broX9cLN2RAttmFHcPA2f7qDMsYN/oDmzJ0ap7+F7OEJz/F+Ict6LoMnJiGCYtTrdkMNAypQ8ZIVDqILsVQRk5StARSNQxZLcdDrtDUtyedWMZ/gVYAC6zcvPDQplbmRzdHJlYW0NZW5kb2JqDTE4IDAgb2JqDTw8L0ZpbHRlci9GbGF0ZURlY29kZS9MZW5ndGggNDQ5Pj5zdHJlYW0NCkiJjFRNU4MwEL3nV+yRHozQBGyvznjx4MdMbo4HbKFFSa2lyvjvpZQsIUuww4Gd3c3Le/sRdQ93Cp7hC0I+lxCevpMho5DHYiFALCWPE1hpuFUQt/Hux5SGa6USiEDl8AJBNoOr5jAEP48zaI298VS880DjiaIYggcTSs8GC3bG82mMY3cIgVdbY9Hksvews7Exnt/m1ldQHqk3cxCLkEeXiSwMqNZI61AVhA4SbBWHSy6YkTrg6qZvyLm+Dqg+MxJNkrSIIVTeHcNLDjp1S1sYpAni1sUFoYmh2iCldtkNQk0QSmJYYlYl4VVll1WGglEsNPYjmJOVyGdsiLD6RoK0A6SZDZvKmljmkC7HSufnI+0CuFj/znwyOvCgNBsM/Jt3LY/ufGnoPKgDx2tHGsSn+EVC8lAIL8vhWp60qne/UhnyBuoSsU+oZY0Nxnmu+qXxNJq1rZE3y7ER65+LkS2k74G7TYM1aa/oX8PURwhvtZhpmp36jjXZmZPNkDUi0nr0u7AmiFaJ2PChwkhN6nH4yN3ZwlhtiFUIQEYTI0gDu4nicdJxMVFFtSVAqeWZXrR5zJMFGx8/gD8BBgANznUrDQplbmRzdHJlYW0NZW5kb2JqDTE5IDAgb2JqDTw8L0ZpbHRlci9GbGF0ZURlY29kZS9MZW5ndGggNDQ5Pj5zdHJlYW0NCkiJjFW5csIwEO31FVvaBQLLB9BmJk1SZUYdk8IxZ/BBbELC38eAd6V4bcNQeEfSHu/t22WsdQQe6DUswFm7MJEKnDJ2YXS1shVaP2gUrrgZ5V4278EF5ckZOLo5IP/9Dq0cjQ0ajZdwKkpi8qZo0V1VUZo9HZ7p7HA7EsahRDgFnWQxKydZteoBZ4eh6NESDYKTUKQjy2IKLTASloLf1Ar4DvoFnjW8wdflMoDJ5Xcx/LmSYTiPwFehjGaQZOJJQ3i9x4/OYGz3MGfEUTWtSoW5Mbz8EsKMrKo/1JaxTk8IYnFXAiZejOKi5BTGEls/Y1MFvudJ9RhXrytG1oF1OkeIdtWjaSS9Wn6E7Zu6b26NSldtV9HBinFssfu/l82bDzMzosVUyl/T9G4ZvvakCcvtwAroGqreGbdDxbyoJYvO22AI4zN46qDlPq1ikNbq2F5h5XFIcEFYu/nKb2QnkgyGZeeMXNCfPcGiifTULHg42MISHqPM3t45ioTBSxmt+OTMBWlvjsEhVPNpjQTuAxAdKii7O3zbu7zBJxJuuWGxuvd8z16iJCX9yVXVEFJV7+XAi4bhiq7mw58AAwAldHafDQplbmRzdHJlYW0NZW5kb2JqDTIwIDAgb2JqDTw8L0ZpbHRlci9GbGF0ZURlY29kZS9MZW5ndGggNDY4Pj5zdHJlYW0NCkiJlFRNc4IwEL3nV+wRDqYQwodXbS+9OZNbxwNF7FgLWqg69tc3KAkhiwwdZ0zIbt6+3X2bFXyDRxkHr/k1G8bmlPvMBzaPqc8gK2AhILzZ24WIAp6EiMAHsYU3cI4uzORlcL7UJlWbHxdu67ZdD8pQFXA7Is5OHZX2pVwdfOhbCFjfPrjEgqEurEG8wouAFcozCELKoyiZnKcj+YrPYbCYAYt9yvkQDIiC9Mq17CjbdcMFfLecyVgNzjkCukras4RJcgboRmEVuPj6pFbwOKAOo8FJd1YcUX4XtdFOVW5x6WEVOmK1z+3gtZHReYcKZliPShK6GidDUY+1wZnUBkviXlOJ3VRTG7PH2uDNQDGedGBEKsSjnucxEBnSigmblpsRYC6BeRS0wGRYelNZhoHEaOZ/Itj42F+NZpao+aqTFySUal/T1ghjHWoGLoxoMonns2ZVIqLdAGQpUktviJiRcDXGzQ89GkVB9A+GOv+sm7UaTXepCI7Oo/xfMPW97Bu0/OtcYVX13aQx1UuNsKu7I34xJFPS54UrODju5vOWYmqWR44C5zbnnV0YqSZiYGkldGUuHr+mmZ0PQYro3p/6hDLRztrp12gUWQP8CTAADON86g0KZW5kc3RyZWFtDWVuZG9iag0yMSAwIG9iag08PC9GaWx0ZXIvRmxhdGVEZWNvZGUvTGVuZ3RoIDMyNj4+c3RyZWFtDQpIiYySP0/DMBDFd3+Kx9YMuLFrO54RDGXhjywxIIbKBNoqaSFNi/j2xEnOgbZIUSLl7t3553ex3S1uHB7wiZRLhTQ8IcgkpJLcKPgSVw66rdDHlZg6ZyDg3vCMid8n7LJZh8muTtAG2wSdUuYUVehrr6SsYik2+TphbRCV2E3LfWRvKDgMm7xT1Ets6PL5MerrxAn5X5IQN4szUgtBBvOH2FwMq47AK7J1OsUiDvFN7EeS7nph3n95ghe4cHrs9PRmNuVWSD36DMMsbs3aq9CVd/7snRCaa0k82/HsL57ueRcJc+t/r5awlls7ChNtncMYydVoTPerAiibcWMbQIoSQiuuTZsUlBiluVVN3jXGdIknbCBkeHXjPzOWVXkrTu/zyucf9X5RoFo1e4QT6KxkigvzF9U4ns5LgestgqMfAQYAXae4kA0KZW5kc3RyZWFtDWVuZG9iag0yMiAwIG9iag08PC9BbHRlcm5hdGUvRGV2aWNlUkdCL0ZpbHRlci9GbGF0ZURlY29kZS9MZW5ndGggMjYxMi9OIDM+PnN0cmVhbQ0KeAGdlndUU9kWh8+9N73QEiIgJfQaegkg0jtIFQRRiUmAUAKGhCZ2RAVGFBEpVmRUwAFHhyJjRRQLg4Ji1wnyEFDGwVFEReXdjGsJ7601896a/cdZ39nnt9fZZ+9917oAUPyCBMJ0WAGANKFYFO7rwVwSE8vE9wIYEAEOWAHA4WZmBEf4RALU/L09mZmoSMaz9u4ugGS72yy/UCZz1v9/kSI3QyQGAApF1TY8fiYX5QKUU7PFGTL/BMr0lSkyhjEyFqEJoqwi48SvbPan5iu7yZiXJuShGlnOGbw0noy7UN6aJeGjjAShXJgl4GejfAdlvVRJmgDl9yjT0/icTAAwFJlfzOcmoWyJMkUUGe6J8gIACJTEObxyDov5OWieAHimZ+SKBIlJYqYR15hp5ejIZvrxs1P5YjErlMNN4Yh4TM/0tAyOMBeAr2+WRQElWW2ZaJHtrRzt7VnW5mj5v9nfHn5T/T3IevtV8Sbsz55BjJ5Z32zsrC+9FgD2JFqbHbO+lVUAtG0GQOXhrE/vIADyBQC03pzzHoZsXpLE4gwnC4vs7GxzAZ9rLivoN/ufgm/Kv4Y595nL7vtWO6YXP4EjSRUzZUXlpqemS0TMzAwOl89k/fcQ/+PAOWnNycMsnJ/AF/GF6FVR6JQJhIlou4U8gViQLmQKhH/V4X8YNicHGX6daxRodV8AfYU5ULhJB8hvPQBDIwMkbj96An3rWxAxCsi+vGitka9zjzJ6/uf6Hwtcim7hTEEiU+b2DI9kciWiLBmj34RswQISkAd0oAo0gS4wAixgDRyAM3AD3iAAhIBIEAOWAy5IAmlABLJBPtgACkEx2AF2g2pwANSBetAEToI2cAZcBFfADXALDIBHQAqGwUswAd6BaQiC8BAVokGqkBakD5lC1hAbWgh5Q0FQOBQDxUOJkBCSQPnQJqgYKoOqoUNQPfQjdBq6CF2D+qAH0CA0Bv0BfYQRmALTYQ3YALaA2bA7HAhHwsvgRHgVnAcXwNvhSrgWPg63whfhG/AALIVfwpMIQMgIA9FGWAgb8URCkFgkAREha5EipAKpRZqQDqQbuY1IkXHkAwaHoWGYGBbGGeOHWYzhYlZh1mJKMNWYY5hWTBfmNmYQM4H5gqVi1bGmWCesP3YJNhGbjS3EVmCPYFuwl7ED2GHsOxwOx8AZ4hxwfrgYXDJuNa4Etw/XjLuA68MN4SbxeLwq3hTvgg/Bc/BifCG+Cn8cfx7fjx/GvyeQCVoEa4IPIZYgJGwkVBAaCOcI/YQRwjRRgahPdCKGEHnEXGIpsY7YQbxJHCZOkxRJhiQXUiQpmbSBVElqIl0mPSa9IZPJOmRHchhZQF5PriSfIF8lD5I/UJQoJhRPShxFQtlOOUq5QHlAeUOlUg2obtRYqpi6nVpPvUR9Sn0vR5Mzl/OX48mtk6uRa5Xrl3slT5TXl3eXXy6fJ18hf0r+pvy4AlHBQMFTgaOwVqFG4bTCPYVJRZqilWKIYppiiWKD4jXFUSW8koGStxJPqUDpsNIlpSEaQtOledK4tE20Otpl2jAdRzek+9OT6cX0H+i99AllJWVb5SjlHOUa5bPKUgbCMGD4M1IZpYyTjLuMj/M05rnP48/bNq9pXv+8KZX5Km4qfJUilWaVAZWPqkxVb9UU1Z2qbapP1DBqJmphatlq+9Uuq43Pp893ns+dXzT/5PyH6rC6iXq4+mr1w+o96pMamhq+GhkaVRqXNMY1GZpumsma5ZrnNMe0aFoLtQRa5VrntV4wlZnuzFRmJbOLOaGtru2nLdE+pN2rPa1jqLNYZ6NOs84TXZIuWzdBt1y3U3dCT0svWC9fr1HvoT5Rn62fpL9Hv1t/ysDQINpgi0GbwaihiqG/YZ5ho+FjI6qRq9Eqo1qjO8Y4Y7ZxivE+41smsImdSZJJjclNU9jU3lRgus+0zwxr5mgmNKs1u8eisNxZWaxG1qA5wzzIfKN5m/krCz2LWIudFt0WXyztLFMt6ywfWSlZBVhttOqw+sPaxJprXWN9x4Zq42Ozzqbd5rWtqS3fdr/tfTuaXbDdFrtOu8/2DvYi+yb7MQc9h3iHvQ732HR2KLuEfdUR6+jhuM7xjOMHJ3snsdNJp9+dWc4pzg3OowsMF/AX1C0YctFx4bgccpEuZC6MX3hwodRV25XjWuv6zE3Xjed2xG3E3dg92f24+ysPSw+RR4vHlKeT5xrPC16Il69XkVevt5L3Yu9q76c+Oj6JPo0+E752vqt9L/hh/QL9dvrd89fw5/rX+08EOASsCegKpARGBFYHPgsyCRIFdQTDwQHBu4IfL9JfJFzUFgJC/EN2hTwJNQxdFfpzGC4sNKwm7Hm4VXh+eHcELWJFREPEu0iPyNLIR4uNFksWd0bJR8VF1UdNRXtFl0VLl1gsWbPkRoxajCCmPRYfGxV7JHZyqffS3UuH4+ziCuPuLjNclrPs2nK15anLz66QX8FZcSoeGx8d3xD/iRPCqeVMrvRfuXflBNeTu4f7kufGK+eN8V34ZfyRBJeEsoTRRJfEXYljSa5JFUnjAk9BteB1sl/ygeSplJCUoykzqdGpzWmEtPi000IlYYqwK10zPSe9L8M0ozBDuspp1e5VE6JA0ZFMKHNZZruYjv5M9UiMJJslg1kLs2qy3mdHZZ/KUcwR5vTkmuRuyx3J88n7fjVmNXd1Z752/ob8wTXuaw6thdauXNu5Tnddwbrh9b7rj20gbUjZ8MtGy41lG99uit7UUaBRsL5gaLPv5sZCuUJR4b0tzlsObMVsFWzt3WazrWrblyJe0fViy+KK4k8l3JLr31l9V/ndzPaE7b2l9qX7d+B2CHfc3em681iZYlle2dCu4F2t5czyovK3u1fsvlZhW3FgD2mPZI+0MqiyvUqvakfVp+qk6oEaj5rmvep7t+2d2sfb17/fbX/TAY0DxQc+HhQcvH/I91BrrUFtxWHc4azDz+ui6rq/Z39ff0TtSPGRz0eFR6XHwo911TvU1zeoN5Q2wo2SxrHjccdv/eD1Q3sTq+lQM6O5+AQ4ITnx4sf4H++eDDzZeYp9qukn/Z/2ttBailqh1tzWibakNml7THvf6YDTnR3OHS0/m/989Iz2mZqzymdLz5HOFZybOZ93fvJCxoXxi4kXhzpXdD66tOTSna6wrt7LgZevXvG5cqnbvfv8VZerZ645XTt9nX297Yb9jdYeu56WX+x+aem172296XCz/ZbjrY6+BX3n+l37L972un3ljv+dGwOLBvruLr57/17cPel93v3RB6kPXj/Mejj9aP1j7OOiJwpPKp6qP6391fjXZqm99Oyg12DPs4hnj4a4Qy//lfmvT8MFz6nPK0a0RupHrUfPjPmM3Xqx9MXwy4yX0+OFvyn+tveV0auffnf7vWdiycTwa9HrmT9K3qi+OfrW9m3nZOjk03dp76anit6rvj/2gf2h+2P0x5Hp7E/4T5WfjT93fAn88ngmbWbm3/eE8/sNCmVuZHN0cmVhbQ1lbmRvYmoNMjMgMCBvYmoNPDwvRmlsdGVyL0ZsYXRlRGVjb2RlL0xlbmd0aCA2NTUwL0xlbmd0aDEgMTAzMTY+PnN0cmVhbQ0KeAG1Wgt4VNdxPufc5z60une12l0QQrushWSvsF4IAcHmgrSSYC2QBFgrjCwJEBKOsYUNQWnshtjOB5Ed5Kb5IImdOs3LbvKlXm2oEW6CnZchjnHsxHFcN23dlhrqIJf4I6mLpVX/OSthyNfH16S50tyZ87xzZubMzDnSnrv29jMv288U5mzb1TfE5OPbCPTctg/tieTKusmYGNoxNLArVzaHGdNGBm7/8I5cOb+FsfCnBvv7tufKbBJ4ySAqcmW+GPiawV17MI4en4pX2+13bptpz5+H8nW7+oZnvs9+gXLkjr5d/cB4ysvxumbozrv3yCIro/abhu7qn+nPU4yp38i1zb59jHHQAdbKDJZkGhPMYpVsE1ZygU9jvVy2a5g49HpRT/6KX7MiLBPPN/qOzSH83e99/UfTbVNnzSeMB1F0y/7UgHmN+7PLGDP/dbpteth84nILtdITcNwbn/kKd75iFzZ9eZx7MzUlfz7OC51MScmdJ6ujdwB2AW4HfBBwG2AnYBAwANgB6AdsB2wDbAX0AXoBPYBbAd2ALYBbAJsBXYAUoBNwM2ATYCNgA6AD0A5oA6wHrAO0Am4CJAFrAWsALYBmQBMgAWgEjPP6zJ0m0JLMHYTqMrsILc7cTqg280FCNZnbCFVndhKqygwSqswMELo+s4PQokw/oYrMdkLxzDZC12W2Ero200eoPNNLqCzTQ2hh5lZCpZluQtdkthCKZW4htCCzmVA000UokkkRKsl0EpqfuZlQcWYToXmZjYSKMhsIzc10EJqTaScUzrQRCmXWEwpm1hEqzLQSCmRuIlSQSRLyZ9YSsjNrCFmZFkL5mWZCvkwTobxMgpDXmW40ozs7q0tSgJsB7R3VJU2N1SUJwPp11SWtgMiRqiPOkbYjatVBnv8QH33gsQeefOCZB378gDY6+Njgk4NK786hnWL0Fj66mQ918tG2x9qebHum7cdt2mj7Y+1PtiujHY91PNmhrLxn/T2i7SO9Hxn6iDK0jg+N8qrR3tGhUYUd4vh1Dg0dEuxQ1SHnUNuhXhR0a8gZEr17eO/dfKiR5zaW3zad+vyVLwZ5/hdLvijCUHoA4APkAbwAD8ANcAFMgAHQARpABSgAAeAA5x6G9xthM/qLgBl91WdGf5ZnRl/xmtGfeszoT9xm9GWXGX3JNKM/Nszoi7oZPa2Z0RdUM/ojxYw+L8zoD7kZPcXMaMyXWOBNRN2JiJko0RPz1USxSMxjiblm2AyaAdNvWqbP9Jpu0zR1UzWFyczkuDHdkUybbbekxjg/1JX2J1ly4+rjjPPpj38y/js+d6/mxcl00YZU+nBxVzJdA4IVjwXZ6q5kBKVY+nD75lS6qrgrzhM7N6zmybbUmInWhi05HLSGbhyrr0/sjKTZxlTa6e1qHKtiQ9+sYVVszlB46G757NmTw1e8f0d+/y/D9sThsLQ3AcfZHEBArYTXZNNnAecJslvQ9j3GsogKylLQP4UP3QB8Cj7x//s5xU7j5zA7ip/cc4Kdws9D7AvsEdTTk6th7Gv4oWc7u5fdhx6H0WeWfoT95DKNelHFl/Aw/zJ/lTWJMK/g34Ubf5X9kv2Sv8w/yjfyAp7gg7yCfVLU8S5lpaaBPsruwKhb+Qv8BfU1dgdKr2LWHn4RbcPiJf6w8lG2X+xHC/H6lewXWQ07Dj5+78f8X/Ux+wnSBz2kjz/I8wfSh7NkYzK6dk1Lc1OisWH1KmfljTes+MDyZUvrl9RVXr+oonxh6TWxBSXhgG3l53ncLtPQNVURnFUkYk29kfTC3rS6MNbSsojKsT5U9F1R0ZuOoKrp6j7pCI3rQ9NVPR303PFbPZ1cT+dyT25FVrAViyoiiVgkfboxFhnnm9tToD/ZGOuKpCck3SppdaEs5KEQjWJEJBEebIykeW8kkW760OBIordxUQUf87gbYg397kUVbMztAekBlS6PDY3x8hu5JER5YvmYYGYefTatlCb6tqfb2lOJxqJotEvWsQY5V1pvSBtyrsjONHhmD0bGKp4deWjcYlt7497tse19W1JppQ+DRpTEyMiBtB1PXxtrTF/7R2fCEGB/uiLWmEjHY2As2XH5AzytlVqxyMivGZiPTZwH11fU9M3U6KXWrxk10hIviynN+2ZpBt7AIdYXjRIvD447bCsK6f3tqVw5wrYWZZhTGe9Ki15qeXa2pXATteyfbbk8vDcGySZiid6Z3w8NhtP7t0YWVUCz8rc0rZaiPZJWFvZu3TZIuK9/JNaIFUKW0gM3gnD6ZoSZGKuqRP++XixiJ4mhPZWujA2lA7HVOWmjApOUwren5JBcbSIdaEiz3m0zo9KVCYyFiSRGSDHEIM0Va08dZ7XTb4wtjhR9s5YtZl3ERzrYAKUsTIyktu9Il/QWbYd97oikiqJppwvi64ql+rtISzErfe0b+BweKFCOwtp+q/dsZyw7bZSakZQoUrpIW6iINOEVW70CDVZazxVJo6tXRFK8iM12w1dmehB11TwoKKUNLRgMjKENLUVRGLd8/geWinILABtp8zJPKpjQ3ucp953/lrVcb2Lo2kiiv/EKBq+aFAXJ4Mxs/zWfgmQxIwywYJI6W2gNiyoE6AiazbTAOmUVaTGMYN0WScX6Y10x2JDTliLlkKylfpMbYklEfantmT05YsaSG0aoNrY0V8UiI2vSDObkYCst9S/O1TbBiY2MNMUiTSO9I33j0/u3xiJWbGQsmRwZSsD5sLYUbGB8+ukHi9JND3Wlrd5Bvhy2OxJbs30ktiG1AgqAZbVd3qxp0bAxNcOQ/LK0GPSBB1k9FuMH28ccfnDD5tRxi7HIwY2pjOCioXd1V9cinJwYothO4B+yNvUcOyyWs+NqHjusv8zy1L0sqXazVeLXgBbWJrag/svsgNoDSLJVaD+gzGOHlRtA97B7lV/iNMlwHqKzFwOts3HgCOucqZHVv8dL4OQ2+9ApEmnIbPEqrF9VurJgMJO5ZEUue/GAyzzmY/mos5iNt58VzAwoZ+Wc8++ICnG/skm1tBbtW/oa/VnjBXOXq87tdt/jiXqe8a5Bb8H281uV/eqt4M9gC52g9qjyefVRg4VYPbFsPMpRZKxyamqCV/Z0A1VXFdhRuzRqR/crbGq/YFmGKdgUtEESbJs+q35Y+wkr5D1O5mwhZ4bJlU6Diz+2+B95eKHOlYPsM+wJCPgUe42dw2cNb8hb5q33qswb8Xr1pDekJ2yvZYmkrefn4x3xePC2vF68/Xl5eHt9Pj1ph6k31eAdrGdnmRCH8LXhfL4vwEW/i7uVzcptinJd/vJ8oXi8R/jj/Bg/yX/Oz3Kd8bNezk0WZgeZQiwc8GLB49MvOfm2rSdZwOsQ9vqU8elzR+l7IN45almSOHc0P18SF1AjqOmcU0rMKmsCrjC10VskXWt8roBFg+gtkgGFrYzHV07E4xYAj3Wafnu6u3u6a3q67VpQIPHb082tV2cz4gmQPd3PUQuguko2GwtjC4Qd8NfW1If0aITZFovWqDc09L74nX/9t2+/eOcdf5l9O/uP2acQ4kL/pt3/9abs0eylS9kffuqz3+B/xjfwFp4hC0B2qn4GmbLJ/LzOqQg4JOdwHm4nhtFq5h/MF/mJQWPYEKTGAwYvN7gRKCgQSWN8+i25fBAXHA9pyXDTaFkOkaYML+nIECS/GXp8+qKTTzoz3I0GD/EyrnASMQkJxPmjNDWIi3JqEJccD4mSq/QBlLNHaWYQb0u1gJikHhiNL8g53nNq6AO8IGwNWsOW0pm3L0+wTd5+716vstnmSpnGRaGXu70tXLjsFpWbqmArV1oTNbUAKX2S9RUaiMe7p16ZVUdc6mhCqqKbx6NR+5pakj+3g7U1ftuKLuDfzv4Nn7ue1/Idk881bPvJheyiedpxd/bB7POT5zTtveNu/gEextGPI/dm/GM4wSjsxHGmTT/rVErZarxc40pjmJWzpayFpdgg0wehEto9qoHtg+1WTjKDzb4iZQbijJQZiLdyMmNSZihfPEp6AfG24yHpsQhpRJZtEh3jJDo2j8SG2jeOyj0wPv2i43G5UNWsMpOTiLj1m8tSIKKne/ddE90wSR6vtWvt46e045easSpYlXIRq9LYnzhBUw2r4pT6mnpOfVdVmdLI1GYsQVqP/F5O6ajJKR3EjNIvLyCndNljhvOLOaUzRXJOVpUnuW/WlZYZZie49eb73FpvWhNgt7qKGI0WHj4lDoPZ946C27zp8/r3wa2X948JikrOPMMtFE3lLtXlEabQhNfj1nTDbQjhy7P9yypPn7b+jn4hEzy2P7SsuqpoTP89BjtLmnFWaFF4M0Rd7mvxDfsO+k75NLrDw5L4sMr3Md6CRjdqDENR9SbN0+Qdn37X2ep260lTww6o9zZ793nVpfD6w5riHWbasOod5nuZsldtVjnmYfe51PtMIVx7MZdhlpn1Zqf5uKkxc1AMC8HXgAv3ISZGTeOQyiAArxRAbqHWBP1I/4N40D2xbE5lmGq6u+lNjVPwUXEQENKcSpIUOS25pchvdbOe7q4uLcZ5DJtD/qqTK7Nfyj69MlvzEl/IVzfxG3n8pYDyzqRPOz3JlelJRZmCtSenJ5TzOPH72Bw+3wkWweHAjlvz5nhbm/N4XoR2Td6sRwLxm9wWyDOpH5XlFgBxJrcF8rxk+Cj/Exok8Y6zgywoLyCdVmDdAZ0v1Vt0wXSTHJA+l2bS59JO0mUc0mUc0hGZ9KTupbH63NJgXTARVK6zl9trbUVJKYOKCIY9GBO0aJZgh2JTtPBRld3hanFx4XMjgFx4ipoVjwub9BjtUZeRxyD0OH56IF0ZMa72ShDnVPwKrzS1wkJ85vHSOh2hoW4xq60JwdYVBAq90IrWLKlXrt/27R9e4pGTX735xImWez/7bd67CJF5/TYeufArvmkd/9WlImXJ7WfS2XuXRSg6rJo+r85TG9gctoBPOweukVJPhbgybvAR43PG14ynjeeN1w1ddGp80Bw2D5qfMZ8wtXJzqdlipsz3q8bNU+Zrppd1hvfB8S2gaGjKGGkijoOuoDWbUpamRbI0S9Y1z+ciFK4PN4cHwgfCR8KPh4+FT4ZdYfJQ5AZA/L10eSB+gZCMwJXTpyRekYECNWekholwPkgzh/n8dXzdAYsvtVqsFGKEalk0HUI0uLKknqx5pCArSFyBKWjYkhq2YgeMI8bjhsLKtHqtWevUVGF0XGcuN9eaitJpDpj7TMUwQ7jXG59+9ihNAoJYwYpQ49TJdXrntzXzTj4gUxKNcYeY53Bh9JbBjGupuXxuR0ByE6AISY1EONJ5B6T9BgaQ6qwNbA4ITSZIWofXEzBgNhNx2IpF3m+i215WKV31hPUz4O7dPd20g3e/H96wkSesv6POeDBu4vuXyd1kcpRz8DiPBkIIcMg2DNhX2cI6q34JLCxYGOALDN2ILi5bqOZNTg7cMvqlXW0Vt9z10POf/PyfP/yDf7nvj7PXfPTmDo9ob1kvtG/1p3o+URG57hNHprnrC6Mfu+f0Sr6zY92eu1s3wjetgtFNY5cXsi8fZyEIMB8aCZHcFpEo9/q5UrgUmyblUlwen9fwaK1Gq6e1xcuZV5D2vH6SJJzi20dpu4I459hkYN4Iyc8bJEWj9lcONjKoACnG6+owfR35oqPA687XkUfDpZM/W4GsNycJinQTENIK6znrue7nqqvgx3g8zuXWCoRqC2M2fupqF5NExPPLazp3a6+/fuLRR5//6oYebUXggf6i4i9M7lZGv3D6rfnYV22I+t/Rvs88yN3/5qkCqf4CLPMpYtL1EfiDi7NJ57vSsFFzSXowEP/hyERZsX3e1n02Z7ZKzs+W67d9tH64mNz6QZyXw0C8mXN81AMZM4UND4lgJoMen37VkbvANin42wV57Rbi7bOOFCi7R5c8wtm5LR3yQUZKLul9GyI54YTwnCUdkvRDcV4D44CtLKxbvESBeKI2T1YnEtVVicb6P+Xt2vcTVVSsbry0ApJ572U6QRyePi8MWIDJTh5nblgArc0NRuRGJ8K5jvRpuEPuMne9u9nd6R5w73Pjti2kl+mKLlpzaeXPucqQHF5OKN+eTSjfcZDzYJ/lEkqVJkO/F5wQmQaX2TsvIMlw7H68MYdT5PGAch3TTmqCu825Ju14Ve8wPDgzYrdhi1wlCpJELi2KxwvggwPB2sLI4RPb2rKjvFI99t7Om7fhGpizA4zpxfCvJXzcWRp1yNqjMgM35uF0Eh2MirJofbQzeiR6Mqqx1voSXmKRQEpMMvaSebSQEmIwRNZeIl1piUpGVCLVjLaJnJpL4Nx06pt1immhJWZrPTJ5OZshTceYS7MheZ+ZzZCzGXI2AyEQKTtmk74URNYJk3QMnaY1rFbDDtll9llbZWRakLW0MeRx9DHbcrmkVSJrk7YqJ0XHXMJHRE4ptpvWYZukFFm7QRqrDAt2JDSvbF79PIWHpaMMO/SRMAKHnmwOdyJG7AurLOzQB/GGfMLB/EBQ6SjydgRL3KbbdhcZQcNP5ksP7XBK4k6T4UrrvUqFZM3vB9d498RVBVJuzgdE7cWIrwb2/2LyBkTUhsgNLMErWGhrPX+bGA6e2Ld12ceKTtyz8o6vvLkl8pdbvvqU+OrUpiWT58R/rL8lVTf5llp5z8OjN3T8IDO1OGcXymuwCxxWnHCBjLpGfihf8H0FnLXaUpeQzyvSSYB4V26TnBzJMmy3dAPvy9Emm7DnSeledgBSl3I0yVCOtsjQbbPVcIVcZS6FIR2Z0ZlL6gzl38iNlGugr7ikzlDOaT3XQEpwyc+jfImUi7Jf7fDZbheSVR/FKFLBlSK/WuAUuWZkDBFfKdqIbStzBz686tDCE/etuPVnvFfc+fVPrFs2eUatHPmz7MYpXAvlMhc3JOjBXcQKpzQEY0EkwV4QyWMa50tD3F4X0uv1Zn1AV3VkAMjqKJ2gFYGYlOEDBCUPsiaXTqDmLSl0EG9I+VEf50MkQN277gA7wh5nx9hJXEfoswc3XPnIzIJJ38wipAQGF463zC+YzCOZNHImswwWvE3huMFQBnx8sICLhG+Tr9+nrC3YXHBbgeKTOYGvw4WIccEJEscFHcrMjUPu9sGDMEGXFLm7ib8i6SsQOe4e5NUD0kl5hkVsv0IBlE9eNvLu3PkW7ovySJxqWWwBK6Assg4HXH0V97z+ZjY7+ca/TLNTvPjjR7Jn7ntEzH2XV2f/NjuZncr+jF/PWXbXa3/ND71C+jiQ3aJWQB/5rIi3OsFiadNni3lwXbP7392i2X3ELeDdz0imQbwl5Q4iJ3cQuYgI4k0pd+rjzCe5u22bq628NSS4IUKiTBwQZ8W/C50JGkT+BgTOA6RjIa0YZWmTKEvjFdJSZXcyXBBZp5aEJgQFAiHoM2JeKm8wD9cgA4YQiQBfO4cHZMScydE68uZAH9I4iHCQoInknA6D+xQd54xcCxFOgFryPIYcbUhtGrPaoU2Rux6SGrpCPfK0LXOQGSV176YdEi8lj1MIZ+O3A1DSQsUOBmsjuS3DEydO3P7ZM9lp9k7jI0H/nnq+9Ynj5Xs+kI1qP03dnj2Tffti9kdVSsXUw0XV/OEffWspRWCc3Snn97EnnfluJA2Ul4etcuuUpZ70/dwnfLqrVW8dRPRArJDyBZELryBmwqshw6shw6tsJnGCmJTKo34yRlCNU0pCh3+7IqDrTHNLT+4WHV4TJ2+v9BjWxOk4LsqulArlrjLWspl0bFYcIXH6xpX9R06cGDxd26sMx5++f+pzauXXn/FjjTjXiEmsMcrOOvGYtEWzJFwiDF/IJ8pjLbGDMYUt9/K6Yq6csrmtrRso3lcsimmdxCyIN5zN5FOLQyy0DvnnEe/j3mPes3SNqVIP74Jzc3hZEaf7srJ8Xh7BleTcvXOFYsyZqwSUFn/KP+gf9p/ya/78ep47DOyT95MGzvs0fcAuwK7tiATCRAQ68iOefBJC7iz4A6T3P+imiwfk8vKELaUCckLmYEjiZ1N3hl0er68P1tbULS67XkEyhuhE14d6YSAUCuZytFUNj/Wlv3b/LQ3RZx/Zm1my+67G7r0H773r+W/+hXN08NO33bTixvVdtQ98em16s1M9UHfjsk/d8fCXKIO5d/q88s/467ufveeUBaQkcf3qRlqOq9+Q2Yzz0OOmesA8Zp41FRP3uvIcdElKEQeinP0QkdufZpC8I8pvy/MOiBdmu16QloOai04h7UeTtZ7zv+sXhj/kL/MrzD8bBkFckjEKxG+cGG02v5em9eMgh7dJevPDMnW8aSYa4NikNX+B0uFzuX0eU5/JE347RF3hInGUumuCtiDORnagFudrZHqxOl2njNfmH/j5rY/OOXEi8p0t6b9WK6c2vXt7s7jw3sufXrnz+afFMyQ7L2T3D2oN/t4wMXPv5FMF92i60PGnY6GoWOsxYlxVTYPuUuKX75z8V9w32VcPmul7dVdnsN7kJv2W42BO53Q6kBsKTmTry2QY7NT36Vq5WCpaRAoXQZp+LxP3ctzX7V3Kx7ko4/VccLWqMKQn1YgHshtXeUgtU+vVAXWfqjF1N/fo4JwCO1204XRUCQ+17AZ5QzQFu8Q10W5IS97/0OWPcNVk617iRTx6MqA2TfnF2/JvEnjh/zbeYx+VxG+/AqhQ2EJWxirwn3HLWTNuR9ewm9h6nKnaWQfbgP+Wuxn3pV1yIIdd5v6qo4Nim9va2zasim+8q3/r3m2D/XtaN/wndtLHZQ0KZW5kc3RyZWFtDWVuZG9iag0yNCAwIG9iag08PC9Bc2NlbnQgOTM5L0F2Z1dpZHRoIDQ1NC9DYXBIZWlnaHQgNzI4L0Rlc2NlbnQgLTIyMi9GbGFncyAzMi9Gb250QkJveFstODYgLTI2MiAxMDgyIDk0M10vRm9udEZpbGUyIDIzIDAgUi9Gb250TmFtZS9aUFFQU0ErVHJlYnVjaGV0TVMvSXRhbGljQW5nbGUgMC9NYXhXaWR0aCAxMTE0L1N0ZW1WIDAvVHlwZS9Gb250RGVzY3JpcHRvci9YSGVpZ2h0IDUzMz4+DWVuZG9iag0yNSAwIG9iag08PC9GaWx0ZXIvRmxhdGVEZWNvZGUvTGVuZ3RoIDQ1OC9MZW5ndGgxIDY1Mj4+c3RyZWFtDQp4ASspKk1l4GBoYGBmYEjOTSxgAAPGBCAllZ5TmQbltwDpFxmpiSkQPsMfIG2WARSAypsAaZWM3JIKKD8CSHPk5CfD5GuAfLbcxAqo+Qx3gHyFvMTcVIh6phwQH8KmFsnHwMAINIuJUYFBgOEwAzsDE5DWZ2iDms8ClAXJszH1i2ieSInnt/nKIMkBltz9uuYMiHGx95T7719/uznfcJgBuZxAEyAAqI993t9bDAxcC37/+rWA8w3YJKgkmGJiAVl/HsyG2MPAwMPABsQMDIpQm0GSJUDIwMDKwPCvmPkSKx8wFtgZLBl8GfyAugUVBcFYhI+JnV2ETVlJj8lUXc3M2NjIjsnURE1ZiY8JLGZiZm7HbGwkx8QMVAkRsWMC8RmZL/2JYvb/y8ZUp2wfZswqJ8UvwsvGyiQjIaRroyoQHK1qoyfLzszOxszKwa5h7qTkneOqdItdUFZUTFaIg0NIVkxUVpD9721Wvl+fWPl+O7Pk/J7CzGYdY6/CPIOLg4mFjW2HnISklrWiZxi/sAALt7CAoBgHu5Agj4ZLzN82URmQGTKiohCz/vqC/MvIIAQNKzYGYAj5hnq7BflpOyfmZCYVZQIA3NJaww0KZW5kc3RyZWFtDWVuZG9iag0yNiAwIG9iag08PC9Bc2NlbnQgOTUyL0F2Z1dpZHRoIDUyMS9DYXBIZWlnaHQgNjQ0L0Rlc2NlbnQgLTI2OS9GbGFncyA0L0ZvbnRCQm94Wy01MDMgLTMwNyAxMjQwIDEwMjZdL0ZvbnRGaWxlMiAyNSAwIFIvRm9udE5hbWUvTVVLRlJOK0NhbGlicmkvSXRhbGljQW5nbGUgMC9NYXhXaWR0aCAxMzI4L1N0ZW1WIDAvVHlwZS9Gb250RGVzY3JpcHRvci9YSGVpZ2h0IDQ3Nj4+DWVuZG9iag0yNyAwIG9iag08PC9GaWx0ZXIvRmxhdGVEZWNvZGUvTGVuZ3RoIDIyND4+c3RyZWFtDQp4AV2QwW7DIBBE73zFHpNDBM4tEkKqUkXyoU1Upx+AYW0hxQvC+OC/LxA3lXrYAzPzYFh+bt9bcgn4LXrTYYLBkY04+yUahB5HR6w5gnUmbaeqmUkHxjPcrXPCqaXBg5QMgH9lZE5xhd2b9T3ui3aNFqOjEXbf564q3RLCAyekBIIpBRaHfN2HDp96QuAVPbQ2+y6th0z9Je5rQMiNMtE8KxlvcQ7aYNQ0IpNCKHm5KIZk/1kb0A9b8tgoWUYIcar5X6eg5YuvSmaJMbepe6hFSwFH+FpV8KE8WOcHcEZwGQ0KZW5kc3RyZWFtDWVuZG9iag0yOCAwIG9iag08PC9GaWx0ZXIvRmxhdGVEZWNvZGUvTGVuZ3RoIDE4ODcvTGVuZ3RoMSAyNTM2Pj5zdHJlYW0NCngBrVZbbBtZGT4zZ26eGY89sWfGcXz3eJyME8fxZWzHiXNpNzfn1qZJt/cmaXpNSNWmTVO6FQ9LuUihiItW+wCUFTzsUxd1tUgtC2hBPEDFslsk2CwvCB5Y7fJAF7QiqRPOeJwsIB45kuc//7n93/n/7z+/V65cWwAM+ByAAMwvzV4GtYZ9FYn0ucW1s3X9zwDQG+cXZs9YOniGpHEeDdTns0iq55dWbtT155EsLC7P787/HOmBpdkb9fPBH5Ae+szs0oK13vZTJJut/v/rKwCAobMELASc4E1AAxzJdnC6fj6BZs15Cr/70qPHZ045uv4BGpna5KMPbz02O++s/7KytVFdt33ETKK1NnSC1dA++tvVDQDYe1sbm3dsH9VOqk/WhECY5t8GgHgPKPBjMEhUQIWgwQj8FWjCPgGXkH4b/z24DTvAMJ4D9+BlgKM930C7LVwA8IACKtLDwI7GcKTZAAkIFCkGsOg+HFphrxtdASvgAcZix7Dv4zp+Av8e/iEcgS/CV+Ff0QoSgO2r8H1SQHtpUARjYByZEcNi7ecWcJqmqGgkiefimpHJpMt4LqtFIwJeG8sa+TLMpAM4RCutkTJu6hh8/9kEfK6q4mvh0lQHiSViStDFMDAYsMcyIUdlLGo0e0mCoSDJ0HGjPzq9OhL5NeuJ+/xxD4uk34dk9S1S2HxKCluHif1bP8L/Uny+rFJrdg4nbcy3mgOS2uHrrtgddlJoUrw+mhEFVh+arb7sjSksq8S8vph5VqxaQj5SdjaJn5FuEAEaADFZtq4Vh2FagNGIphl5zLqLQkdhmPgBT8mFjkwxwBOHt70HCbs/l0hm3RSP3aWc0XKmNBAXqbewH2LLc6oukdDmtGNEVXBxBKXoUeKWKHEQcrLrFyYZcDC48wG8Dn8HMqAXWXebxjUtl6v7Nou8lsllk8iHe34kTD9KtDkiueVM2sjD6+6E3tYi5tdnBlcPp7rXXl89LMb7Uj3zoxknJ3IU6xs4uVy68M3TrZ+c7p4xGgd7ckeSQcFJ005hsNQfG14cGr9aUQ29R3f7Ij7BqylB1R8NuFqm7xzfaFAz4UKvkUVoKwjtY3IJeapooq1j+ne0aVkRTdQmRqjVGCG5A0gv43n4mPW0BELNjdxzLx0/u36kOTP3tVOVm12cPxWLpXz8pjFvdAwmpIaW/VlvR8YIRTgHSxCsg5sfOThx58H86o/vDHWXsD+xTo6iOCdbze4f6ji4kCtcnEo7IvlmhHAEIXwD+TMB0PuCualouG69hsgtWJzdRRg3aghp+EaLWv1jU+lEX/+Z4ZTDxjMQJxh759GV/tUHN0rl669evPyds6m/w2OnUoPtjTi2mWwtnuiLuBQX3RBulIOyQ/AoYtfNhy+s/uTzA/3X7p0MXVxTu6faAcqfpu2vw1fgE1BGOXTKQhWVZanmPi1OUSiMihKA1gDKKIQuj0iAksj8ms6UlXBaxqwEqwW9jLuyWjwuoE2IAmUcviI7L8iu7OyXDiXGJd6VSb43unog0bly/9qV755rF8OpYKLdSET1/NwXD+pjYaxJlLbfnByOFWINk4NaIeYqDfU88AZd1MLx4njKDU+nkp7u8PjaVEIS7Krsj+EMjO072dV/bSat9h7JhbvyaUWZaC/NxqNzw+OfnW5jba3b/xyabEwUg/snPHq+OtOWwklXNBRwprOKhjyBg0s7m9g6OQ4k9EJ9yp89wrhQvCLmxdGNsZt8jRZ+fle6yoemS93Th7oirIMlSfSBNxE3EBMcLJYa7SwMj5YQLTFwe2cTvos4kP4PG2YSUbS4S9o9o5KBzJlsfZf3daixDh/vUotaai63a5f1tgRDusKOvDx17IWxyJ51rNo3kvMP7Kve38Nza7d3bnKy69yXZ+to8Ee1DEd8tDLGzN3/haT2zlD4I0Q9lnE3BhokvQ3hqfthF0e0XCj47IGQhyMJHFbUpJelGVpUu1qrv921D/eQLKf7NAekbSwv6cg3wzsf4E8RmmGThzU0hEUxK0fRW0PVdUTLT9GiHKbwp8XzX5lKHxtKyTzB8DYu0TttRHJxd6x77MBYdyx98guH9IneVhdDQEjzjE0rVlKRdMiplScOTJQ1LDC6Mh53KB6prdUflejGgFfwNnsDiZAv0tp7tKf30qjON0gOhxRUmiJuWvJIgjfqDuohX7i194jJn3s7H2MP4X3gAvE9/LV8MSO4F9p6dmMPhbDR0mKEed6Swn/rUNYLqsOhFvREp+p0qp3VIb1oDhR1vWTKkllFt9+BM+TbiLPAZVaEJL5bEOhnpNOfCLdkmwgKnyGcvraQnvES5HbV7mRJxtkoUnftotUzmYAqNXyNVECyzsuwWT5NsBkRFVMr1/Mxq9hIonkl+BrFCbaqwQgcRaLe336j+EUKZwQek0mHJx7U2j3ME5uDI8/44mZpq5VJDo5c5UhR1zxBWWBeJ0iIoXjYtp5wHuQ2s2GgAf3MRiFfgqGBvrGjA4l9s4sX5q5caOtfXjT/tP0Lyd6s8g0KZW5kc3RyZWFtDWVuZG9iag0yOSAwIG9iag08PC9Bc2NlbnQgOTUyL0F2Z1dpZHRoIDUzNi9DYXBIZWlnaHQgNjQ2L0Rlc2NlbnQgLTI2OS9GbGFncyA0L0ZvbnRCQm94Wy01MTkgLTMwNiAxMjQwIDEwMzldL0ZvbnRGaWxlMiAyOCAwIFIvRm9udE5hbWUvSEZBTVpGK0NhbGlicmktQm9sZC9JdGFsaWNBbmdsZSAwL01heFdpZHRoIDEzMjgvU3RlbVYgMC9UeXBlL0ZvbnREZXNjcmlwdG9yL1hIZWlnaHQgNDgzPj4NZW5kb2JqDTMwIDAgb2JqDTw8L0ZpbHRlci9GbGF0ZURlY29kZS9MZW5ndGggMzA3Pj5zdHJlYW0NCngBXZHLasMwEEX3/got00Xw2M6jAWMoKQEv+qBuP0CWxsFQy0J2Fv773lHSFLo4i6OrGUaj9Fg/166fVfoeRtPwrLre2cDTeAmGVcvn3iVZrmxv5pvFMzNon6QobpZp5qF23ajKMlEq/UDJNIdFrZ7s2PKDnL0Fy6F3Z7X6OjbxpLl4/80Du1lRUlXKcod2L9q/6oFVGkvXtUXez8saVX83PhfPChOhIruOZEbLk9eGg3ZnTkqiqjydqoSd/RdlxbWi7W5X86wqBSI6VEmZ51BAtMlECygg2u1FN1BAtM9Ft1CAdCu6gwLoRnQPBdDY6hEKoCzpAQrQqhDVUIA0jtFCAVGhJTVQQJR3ohYKkKIzHvn7Gnmv/Mt9j+YSAlYYPy9uV7bWO77/rx+9NIj8ALTZlwMNCmVuZHN0cmVhbQ1lbmRvYmoNMzEgMCBvYmoNPDwvRmlsdGVyL0ZsYXRlRGVjb2RlL0xlbmd0aCAxMDI0My9MZW5ndGgxIDE1MjM2Pj5zdHJlYW0NCngBvXt7fFTF9fjM3Ofe3ewr+35vNrubzZMk5EUCWUNePBKRZ4IGwyMQEBQwBKHiNyqIRIoK8lCoFR88xSwhygLFL6UgYq2CpajUWv2K1j7ys+0PbRV293vmbkghn3778Y9+urtnZs7M3Htnzpxz5pwzdzuWLmtDKagLMWjC9JmL5yL5U6pFiOmYvWjm4iSeOgfyd2Z3dniSOJcB7QvnLp63KImLTyEkOectXDFwveEJhNI+aW+bSa+jn2sAxe1QIWMID4c8vX1Rx31JXN8HeWThPbMH2g20ffqimfcNPB99BLjn7pmL2pL9S/4Eefrie+7tGMBpv/sXL20b6I+bYHzvIgy12WgjUqC7kIAI0sK3BSHhS8mJWGil7fAZM+oj152aiq+RTpTxOxsel/Pzk3/xxN/brgWVT4rfQoXien+a86F4CCEVhvZ+5ZODLfJ1kGRH0aSsKBoDUAlQBJCVdYsFdeFd6AmA5wAYNB8/hlYArAN4GoAdLO0F7Ah+rJcVw0fxCmTDY8NK1j3ZYHVbJKX7vSjm+551f2j57Bi2wup9iq29KUhxi4Sfwz9Gc5Abv4T8eCWqRxn4mUOhhe5WaNqLFgN0ATByivHeXleB+3WcjfwshmsCyMXi19y/y89xf54fJbjXfTIYZSH7qQuwsMZ9wvms+7+d89yvA+xPNu0LQY/X3HudC92bXFH8TK97ozOK4Zonk9kyJ1z6mntRaIt7Tr7cPn5LlOzvdZdB+9Sw0l1c6nUXOS+784JREQOe4xzvzsz/hTsdLoRuHripP6xzO5yb3COgyeWsCY4AOIb34e0oE2/v9Y91H4UiTPfQmFDplij+waH6jHx/FK8MF9dnbAnVB/2h8W5/qDYYhPLUN4XVwu3CLUKBkCVkCAHBK9gFg6gXtaJaVImSKIpCFL/cW+nmj+H9qBLIsv+QyItcFL8ClewxfECuPHBYZEUiItEQTXwCzIuRIYr392lpCQqv8XKJj+IDh5JVB8JulpZYuUFLaBkSSBHBIkFjUQT/MMqjNabOSkulfpSurLb6/0pa5Zbradb//bFgZ2TLuElNkX3O5kgBLSSczde7W64X/s+8Yxk0tVVlZY2buOJQ5+IFc2vafDWtvpo2gNbIY53tlkjXLI/n4ILFtMETYQKts2a303xmW2Sxr606ssBX7TnYKV83pHkube70VR9Ec2smNx2cG26r7u0Md9b4ZlY3H5pVtbTlpmetG3zW0qp/8qwqerOl9Fmz5OuGPKuFNs+iz2qhz2qhz5oVniU/i06+Zv6kqns7gDs9NfPHeSIZkyJjbpveFPHMbK6O4l1QWb0McSeQljuOMrguZGPzkBuhxIcAl2gen5L4gjuDtPFFib8w5bCoRyiQeGUFOoF+iLajHsSjPVDOQDPQNnQWLwDZvgP1oYvYhXJB97Ioisajt3EicR7NRS9C/w50Em1GB5EKrlmEjNC6AfsTKwEPQ3kWWp14HqWjUvQIOo7K4K4bUH9ib+IQtE5EU9A+tB+u/zn2kYNsauKVxGUkotvgnquh5XxifKIH6UEvVqEJULsavY79zKVEO7KgchjdDvRjtBP9FP0JP4T7Eu2JzsS5xKfAqhbkQJPguwr34U+ZHvaRxI7EHxJxoEQGyoSntqJN6AW4fw98T4BqrcF34Q68CW8mYfIQ6WPXcOZ4DOgQQnXwrUf3oEeBAkfQKfRX9C3+ilgYLdPBnE4UJf4/UqJxMEs6kzbUCd+18N0AczqGeTwMj8YT8Cr8FN6Mf0kyyRTSRJaT+8gXTCNzB7OC+SV7L9vLree28cr414ljiTOJXyEzcqLb0VL0AMzuJDqHrqDvMAP3cmA/LsdVeAZ8u/B2cgTvxEfIBHwCnyP78G/xZ/grfJVwREWMJIt0kE1kPzlJ3mHmM5uZp5nfMl+zozjC7eQ+5/3Cr+Oz4uvi7yTKE58m/g4qVkReWJkq1IjuRDNhtovRcPRfMIsD8O2BVTuFTqOz8vcz7ED96O9ABYT12IYLcAN8G/GteC6ej5/FR+H7ujyWbwgsBFEQHTETB5lEZpFFpIv8inQxdiaTGctMZ3rg+yZzkbnKXGU5NpU1snXsGLSeXcQ+A99d7B62l32XK+NGcY3cVK6LW8etZ2Zz57mL/AP8Br6X/4r/M6jF8cI9wnpYnbPAsz8FXv7Hh8XpMPoCdDeajavxLLQFVmMnnom6gbvm4EeBXotRRqKFeYCpI8OAG15HPwBufQatQuuYO9DOxAfMPvQ+cMpCuGUX2s1WISe3FVbnITQMuGjgGw5lhjKCAX+6L83rAZXvsNusFrPJaEjV67QpKqWkEAWeYxmCUXaNr7bVEwm0RtiAr74+h+K+mVAx84aKVhBlT6T25j4RD71uJjTd1DMMPecO6RlO9gwP9sRaTwWqyMn21Pg8kV9U+zxRPP22Jij/sNrX7In0y+UGufyEXE6BstcLF3hqLO3Vnghu9dREajvbu2taq3Oy8ZEwkEPKyaaKI4yU9MYRNHrmKlCwaDTtUROx+aprIlYflKGN8dfMnBOZcFtTTbXd622GOqia2ATPyMmeH4FxosdUc3xzHouG0axWWpp5R1OEmdkcIa30XrqsiNlXHTGv/NzyD/R6qWb9DY0R4q+d2dZdGwm3PgbEpWgrxWauB2zcJA/clqxpborgNQODoGNcACOlw03uCf7WBZ6Iwlfla+9e0ArERRObem1hm6x8I2hCU681bJWRnOwjlgfKvTD7Izm35NxC83Kv5YFk/ruHk/XvnaC55YFTn0A+buIgATClgG8MjDPimS0/xAeDLaVJWynqnl0KdIJPM4ZpzofxjI4Q4BnGH+H8Y2ZGuiZdH0Z7dXJwrQuqexVWm7wJVTVD/9Zu7QhYKeiv9Xm6v4bdutXX/6eba2YO1PB+7deINtKFHuSVCJ55vdxJN0s/zLrd4mun69spryngPkvNDRWAU9LQMUcMsIFPaPJGPM1QAdZk9rgoUkxoOojxhuYoTqyJomrnEbBRmTtnQHM2ZbX51fB8QHLAGs3O9EIpN9tTC0+upbzi6fZ0j5nT7an1tAMzsX45h4a27uY8oOCkJqATmgxPDDfbB4ttzc0j4D559D5wCXTvboY7LBi4A+RyVV4MOg3Lhs2UCUxouq0p0lVtj4Srm2EVgH1PTGiKnADObW6GXvmDI4URr5pvGRhzAYw5PxPaC5N3AdulC27R3N1N7zmpyeeNnOjutndTeUviUYyGVoQHKqKIdqEkj+KuCXAtZD6vXV4Dr88Lw2qmNB0OLH2do8Bm/9cULh4cN1xZAqMtlilc+m+icNn3ofCI70Xh8sGR3kThChhzOaXwyP8chUfdROHKf03h8OC4YZC3wGjDMoWr/k0UHv19KFz9vShcMzjSmyhcC2OuoRSu+89RuP4mCo/51xQeOzhuGOQ4GO1YmcLj/00Ubvg+FG78XhS+dXCkN1F4Aoz5Vkrh2/5zFJ54E4Un/WsKTx4cNwxyCox2skzhqf8mCk/7PhRu+l4Ubh4c6U0Ung5jbqYUvn2QwmF7BN2oh7uGqF30b1fMd9xAcrCUOD2qImXgOJehfWQfmgJ5D3svCnNTkQtgK/hi0wFegvqzgO+Ath2A7+DL0ATAe6Dcx36GvJDvAzwT2icCdIKDXg55KUA9XOuAfCTAanwGrYa2LsjX8fugDHUAtG8nPH8dtNHxmAHvgrIS7qunOYARgMayrseaVOABnQDcA/4IuP7/9EPAe4DL5A/3T3tcr+QhqiXCnn/9I0FBCT4igpiQGmkgh2gD0oG3Rz+pcnpjYgDP0gTekQVZkQ3ZwcNDYIu7wKfzgPeSBpgPvEw/CqAgeHkhsM+zBi4vRsWoHbzPXeCtvE6qyHvMcGY88wbz/9hq9iRn4Z7nzvEW/ilhhvCmOFZ8TdGleFvqkE5Jf1XOUq5WIdUdqgsptSnr1QvVP9GYNC9rp2u7dR7dYrg7AZ8JsefA12ZgdqOTcTQxL4pYAFEbRegcAMWhzHwEZcgFyBnIFR+ho3AVQlOzjsKdOMiH5RfqvLogQBW7IXrtf7jj342Osg1XIS4DK7Avfg53oUtArZywCfnU0hxR0prNNmG4NAeJVs3sNktWo/ZKQ0Wsv7GmrfoLVNnQf6E/f5i5uKS4aHgg6CsqNBp4YV+NQ4PJooutnedVU3IyBaVw6a3lfUZKLIym4I/JOLIV5uMJSyiPwTYOWVmIKFUd8h6thzBM42XtFyivAW6b6jV6p+Bv4hLZSmMIGHwtJI+PQYFwKs5kJA4Gh+fQ6+d46eCyGq80xAaHlj+spNDo6zl//hIEJuj1YaCli/sRrOWucGMxW8tO4+5y3u1a6VqN1xIxU5xuvct6v/V+x6tWDqVhDetQW72CwwrxQM6t0aSlSkWpnMe9zJum8v6XUGq6J00d1DzoLk1Lr/NpO09ZLvRf6dd+3X8ZVVbEKir7dfqyPL25DEOuLyvTQYJa8oeNXhF2sFaVXxdQ6tUZSGEQMrCVTdFKGVg0QgKRUa0W00DMg6ilWF+Jk7T1pQm84IOyt0BvNAi8BvNQAQQau+anJx4cPnHLqiN1AfYwU7UMZ3zz2YraV9fNKp1jY9TXQkewfvE944om3bVq0/pxa451not/88LLK+vaxhfnT1uwD+jCAI8jbiTwGEESSMyl8IR63ITbMfMos5XdJu2VooqoxGdIGAk8j4moUEAiIYHD6zHDegyS5NdDnYHj/HrooFRyjEJieQ4rCWYQcQliFDeHFeCi8gqJ4QDbE9anpMDacc/iZyWrKmWnd/0MWD1r4xVLQyxmlVewttqCKs0VlRUNMSCnrqySEjJJyby1uVmrtOPAkmZP2CPsqea1uZaBCgYqmFPNWQN912orKgSA/GG4pQW1YCVOLcQ+xsv4MLPht/1rPiXGS5tjx378NnmCTCfrYsuZ2d+NxtF4vSxxW4EuLJQk0AwZ6KFw6fSU6boFZEHKAt1KstwrjEmp1xGn6Naw7lSgYVB0mYnSFRTZfPt8Tb7Plqkw+jNM1lBmFN95yNs5V2ZQOp9G7TcNwCyoMlbZD1wSk5lE5g29xcaJVj8fECxsFuZsYhZwBGWHBx+EGeACKmjBgM+ru6HIeD00HgD8YErmIUxOPVB797Kqh+I/wgcON+Y/Pn5VfNnPyHKQyvCtoYYlpbOb18Q/jm1iJvhKHn+iwBEvi01fMPrO50a4Y1e51GduX/5Yc14wq7h174Z7XwaumJ64xC3hPkdUGx4Ml9u5rXgLx7ixm30Ir+XWpXKTROYRp05n5Ec4GdUIo8JFXC4rk0/Ktfk6m0eRb7W6PTu9C5IEaOgfmD7MHFVW9ssk0IK8g2iMQA6zPzWg9tsDSpOiAKUYtAVYr9NoBQdgHGIKMCYsI1lUBUijh0S08QWYxZBQkcHaCm0FCJCc0ooHW3CLiM2+XOxLQzqtvhAIWFIIouP1BAM6LYiTj3Xh4bqT3tO9H8a//stXH9070nXStrEn/n4CvfL5y0dxXQb3efzSsQ274u/GT8fj8f/e2/zklz86vv0X+GVcc+5/QH4Iegn4ZDbwSQrsHfPC7rW6LXpSICpdGoJcZlHMT7XZUvxqq9V20du57rqWoixAGSAmTzyATTq/McALnMAKjEAEjpe0IszWBIlCryzAggEiTTDFrKxMOi8/nQnVDVoC3CCzgM4gEFj6c223dIwtt2k+/Ev8x2+SSThv9+am7fFHYj37jMF7mh+bVId1OPfqNi71/ZPx8384Hu+VdeNZUJAbZa4309OZo6AuEcrNglgs3V5I3rD81EKd7+zZs3TLgMYdMOdG6K9ER8Mrec7PBcV6oUlYzj3KbGOiEHb7naDcxexiCcdliCHFHsW3hIPJiZyCuUAwx/FwtqAgJINh/HqWVfBUeUAVx0I4i0azBF4hcoSVWAYTSeDFu/gf8F/yDG9LwZJfiUBpADGp0mi8QiXK2qj9ogUURgUojApZ65rLxLUNuVncKu1pqh5Y7biJ9608rRUrRNAGaOmSFrykBaaEvQrsxYLOt+MkeRunxn5EOuKxWPyPJ7njseHk7Vjk2iby6adxmUYwZ3YczJlD+eFURBjiYjmRsQmY+GET4+GsZBKI+T8GBWOCEcHoKmEzg43X6N1xhnx57TYg4V97gG8oDTPhfgrQMJ1hQwku5YmAzTiI63ATkAsTEsXbw2bQqsARIlACDl8kRpIwL8LToe1VjrWpqH7dHpYUyKpUPeftXDxIFJAz+nSqC2VRA9pAkQXtuXbVaSACCAeoQx2sK4bfjj+SL47/NqZ5nYzgjl+dzu76bjT70tXbYXx0j5iQ+BX3JegAjWwZdYez14Jhdwb/jLwpnpX40aJxhIaxjxAUDuJwKPX5jM1lyVdana4Phoj9oNDLbF+AbCkB7Ff4uYBJbSlABqQvwDYRSloeSmaVsQCnEkiskr0A6VhIZDmnCf3AHonMJp1WIAMCrfcifZEWUWk36L0Mu/3Yxt2n4pvjB04eeOp1CMHb/xj/yx8vxz/5Gzaquc+/+1n8XPzwpQT65AM8FmdewNrvnscrvoZweEX8TPzdK/GD3AxYJ7A72L8DHSQY38xw0XzVfP0K1Uo9W29oMrQbVhpYQXTptFoJqzUuOMiSRMLrVazCYMhnbSaNwo+sRlMUKw95N1+XfnkHiOmAUUEDwvamBbJAhsFAaEn1FoA250GqfUhW9d6C4qIesvnUny9+HC84w3TdV3VvvAOvf2Q3d/w3b76ciG1ij4xwx5mlT1BdBMdv3H0yTwXRU2G9kDIG13PNuImbz80x3MeJpmNwaGBFduwIV/m8nkCrfol+mYHRu9wGh5HxukwGNqBP97uQQmEXXEoScNhFj9/o9puYfM18uy0kBvxByZoRuujdfPOGdgVswQug0kAEY8nplOmS5g/dtVuAC7PoNoxhNsktjPEW0P2KF1zYjWHrMhtBT+fhgDxpH1O3/oWlI+fGbWfInj2L3l00a+o0TmCU+twrkopVCXPKVsbLzzCOxRt/VOYCE3Fn/ozY6j2FvqVdpyeHag3e1IqpXz+Rb491g67ywvqB3gJbeHjYhnkXEggrKsD+QFcJ4+fYq7xVpAYItW2vwDJcGTAhZdmFMRthKXTeIvZsXPdWXMcd7/nur5wamILSe1/iQy4P7k19h4qwz8wFuVItIyHCjdAqTIzJZFD4VTYL9husZstz3s1J6RzYA68LQwWsO9YZzCa6PxWBQMoMzQSsoJo6Kpp/Gbs9/60xj8TXx9evGUNGc8evdTy34LkDM37MrL92Jv6XjfFvsLQRa5gymGsmrP9wGI8S/SS8OAMXkzoyjZnGzmPmsZ3kPvFR/AirDCpLSAlXKrZzHKgRLGtgThREhQAGHGhmBRT9ekkpEUwY7AeHSUk4UQmqWODpQQNYcUiUeBYmKSpFBRYUthQGg0qOYtUh74YBU67BckrbaP0GsiSfU1uuApQR8ARVzKL2BAeaSM60N2RJveT1KXCh/PNhbPszUcdTv8XLcUd/PJVwf4t3kL+Abn6HFMSGxzTkDtBPExMfyac7Gji3q0C/CZdmDsOSVmlXOYKF9dr5igVaoUzUqxSMvUBIVzi1Kmd5FskNlR8uJ+UFmX69VuBERzDN7IjiblhGp1sIOnOVxFmkrBAqKhwGIZS5J902yh5yjNUES60jR/0EbwXGOoK3oOSODvYctWsux05dX1WwbMC6oxOmzJ/bn9tPTViQCVn1ZRSXGNMQtvpxscaLLC67F5k8Bi/2pqES4kU2p9kLjAcJ1Xhg2Mjq7kFQeLglXeaTkViNZTfAeJOPMAoXUrHSGaATPEINlk8wEKRZoGh4cUkqVi9tvLN5i7e9YNGs/Em4b5RR9fDKH5Z7pT3c31443rnM7Fe5dJnZgZZMk6Lknfs3Hz+6tfvd6dljdj1pdPDqFEfePLxQzLbk3DFpfOakN7bX12+LbXWkMcwaFV/lC9cvePXRzS+m4stUNjoTH7N+7iT43C60OJy7S9jteN/BpIkaFwFX3uzkBJ3kciqVhqBo89hytbk4hHRgKq71Hm+5voldvixbiwjsRPjpwI+SqWfRm3jJxBsCWC9BYhTMAZyqcAWSFhIlE2y4lBR6nYHIFDD60qmT6kvjjVTQOnvKX2x989tvLq2cXFC2i8x98skf/uBIoO4kdzL2x4bb4v3xK/F4pNzXsG7Vl6/v/fi181tnHJTlHU40mXNsoxwh2B3O223F2yx7xH0WZqyo225gGAPvtAkpToPSLtjtZm1Qj5kg0dmcUtBsdcBrHsIh79JVAxwj78/9ZWXUBxhqCQ9HVtGvMkoBpE7VwiypDWwFDGxgr2wDK00pAbCBIVFY+AC1gb3/xAaW+QWZkhawAFOXuaKQsgOBfbJQIBc/M/dolz7w8thhj25c/LC1x/XnY+99h/UXHGxj5P3ZD+9Z9NzOj9Yt/9VpXPgFHMeO4GBdSxOXmH5YVyVyouXhghJ1nXqaeje71875RQPROLVIdDqFVIk4zUouNzVXG9LpbW5l0GZ1udd6l1bdOP3YZfCXb15bm8WhkBDGFiXMzQEJspIAkuxiACYIvwfp8urpRAbWE0wAM7Viiui0UNFwfeE3G3eu2rlr5aN7cfekYSMPPF/58j2H4t999TG+88v3z/78Z+feIiXDXeOI87tRm2c34Zzv/oCngQ6pT1xibXBC7KBxHqwKr9gqPm3b7WY4NdFwBqNarzEawqqwQQzZ8Djla8wZ/AZzxv6B+KHiovsD35fmL33KM7ozenKHyHnTNc+YnOllvCCYvE6HIDlNSr+w1bHbcRhkgPWbNH4HZ5VUgg5iCM4gZwum5wpBqzUQvODdlWT+hliS9S/EZK9Xdn7zWgb5hFoNNMYgi0Mt8rEcA8fvmGN5N3g2em2q1qBleZU/zZ4egAiWM4BdToVZCCClUR3AKWqfzQtVHCSiBfgKIhBAaKpkZF0j65vMrMwHwUpGS8B3pvuzyeh1gUhRF0oNW4HAy04VKpS37DQe7NW+i6XFeu21r7gntv5w8jDDQeHW/Ikrbpn4ZvwP2PI/2K3MGHvg/j0c9rF1d025beHY51843VJcV/5k7gSHFvvgHQSCq+KBZbUPHerGH9H9FdMoHDFz70FcriGcJTh5yclgjaHMlMLrJSts4eoUXcisF/QatVtN1NcMVov1mnfeA0kWi7WUnaJ2lfbGDb1SjlnpS4oLC8DkyAWW4Y0QJ4ItHuJXRa/6Kvt06WaHVTnR09vXu3kzVzX8DkJeJHjKKxuuzWF2bNgD42LQyHg58yXwihvlwFsrh8MNxYYx4hhFk9iseFS1177HuTe4K+uIXRkWGVNaSH1KSoMtheVDTqukd0qaXCE3l3MwuabcnBBnG6ZSB1NGBYIOa96wGwTkSn8ZVX6xy1/DOg9oCNCC8rIn1z3bl2FzKXXpfm3A5woEUIYNEp1S7UUatSrF70wL4KA9BHpCBYbxwEaS3EqSUkQlp6gQHEfemxYIFsIS0+WVd4t0HagHBPpyQGuAYYLJ/TMKi3ZVLI6fPfAn9eGU4MiH3w0HmOJtq16JX8XCUVz94n+9XuvfdP/JW7Pj59mqUb7Ra68VvN15aftL9cGKjVN/M3HC37ATp+Dc+M4TvXc+8+rxntmrSY68zquBqFSnmNCkcDZIjWgWzGKQDaYuE5aJYmoKSYUAos7JC0aVlBKSwKIyhpAJbCp4+++Qd1ZSpwzGVsAKlXeLMkwFRN4MwFdObow+HTU/YdF1vtV94cJpD/1+Us4RV/7axa/1gfL/6DZv2QvNz8ZuIy90ljQ9czH2JuVDAm8GIVwOdhWNwxaHHcLnLDAnz0jUjAS+DQkMKGzFvn+M5FSs4tQg20GgVHYBfTrgtNWH4cNmXr3IHX9bnnsXzJ36F0q0OzynmeARIrYSEDAzP42bx63g7xPWckeYs8wliHgmHWeGrCZPAVMypAxCbywHL4Dwi/RANdl55pK+M1ihiGV48J4lcJsVRAohJRhpvd5ZR7ApabVQgsm+84DrXCmbZ2CxyBbaKu1PwVm0ZLWAE31iwHPGVB0spY4zmGc+6jh3HcDvfBGfiw9+Ee/degCM0/34TPye2Czi6I7fLc9vHdCOxhcZFArDKg7EbUkIMRC5vYFkEBhLhpWT/rJvXV+fHG2gegDoz/vZOoi+rwmXC6Kg5jVm0aw2a4JiEFRovXWqcp5S5fNLNqfPKhHW7Pc6zc4UXkC83eFnUqUMWChdCF5MxL22EH0fMwx7TK4fhMMazIjilBuZ6LL2Sv+V64FksFvBEegHXXs9mJvkKOMAR5mvW1zAWAN8dQOH9YaHNy/pasxOr3i+7YPGzGN3NSx4+rAttHju7j42b9ut6SMr02unTtoxeUOshHx514QNu2JPkmOLCsY9+y7lPJnvmH7QM/RMYkY4/zB/hicsb+CDhk6+Q+AMKmKwaMGSQrxFKdkEmw2pQgqbA+daQlZktYM5e5N4JLeUpDaBefXryq6LCI0FGG+YCpUR0PFqDHKCV+8fv6/98oTsw85hD4RDY0tz7H14N4x/xsQfT3ueysqsijkppqqiJfNj78JgYaXLEx+yXrCTVPJ5yhPhwm3iFu3TppfYPeIu7V5TVHxTfJ/9XP17g2qEyDstgsqpV1oFq9VIghqbXRE0Wm32KFaAtTSwGyajhoN6UN72suGYKaBMVcDOpSMBLJihxKVASTKoAghrIRFNYBwxakjkvY0mNGqQrpedUGoMmgr1EBokXrAcZIPokzXDxh99acuWF+CFxGvxv/0mfg3rf8d3YM2uLTOeuta7/zJzKf4nMA9j8Vdw1jUwwsPUJuqMT2H9MHU1nC50hLP3irvNJEP0OHRq3mkUNLza6VCmqUnQYkuXwNL1htI0Vl/6P7V0ZXOInhfIc3SY7IizBdgAssPEOBMk2KoOIMYsz0meFjWIqHWbXDPZvsWFSf6El8ToPg0ugM5H3tjtrz16rMYPaTy3pzh8+w9eix/ueGbFxGHlfSt++V7XHQePzXnm/mm7mIMbxmRUxH8Pc3x+y51FrjGx31AZBDkmG0EGdejWcCDIBFJKmDqWVYtaolboFKqgSNlQJ4m2VExtPmTVp0ZxDQhWcjsGZQPsByGxyobKUzHwCoH3ZAOG6meZ9Qb3Y51v3X7ji3dxFqfWrn10I4jKkeLthHmdIT1LY9uoXFQl3mdeY8fB3puHc8OPlyq2cVv0Txu2Gbdl8hnp/mCxt9Zbl14XnJo+LTg3fV5ghWpFygp1p68jvcPfEdjl2pOdyoApxOWwuanIZrSbHRZjjiE3Q6OcDxGOYj/xp6VIbFaq5Q2HM1VgnbnPZCnzBIVaSwSU582zuS0mS9A8KiMgBDNs+Wp3UDsKBXOtw/J7B+03UCHJ/btMCyU63bI8SEHkqBFHvUOqUpbIqzwe55CA0W8LeNVuL1LAq9iYyQb/ksuEklMPdXaDxYs9mjQv8qapU8Sg5MUBv0LCOawX3r+HxKVzeLHVBIlsxslBcDmRWeQ649OYn7zNy+wiR1vALaQ7o+CjkZekeyTHY2gQCqwC/JXor94zZ9vI4L2Pr7ul49dH/nrXaLKPC4x6eu78mozG5Ser5n/48VdnBHwYT5g+bNq022vSwfJNyxzz4LafbJjePrKgrjFcm2lNdeZl1zz1+LkPnyPfAi+ZE18RBTcdtMPEV1NypRNqHMWVYT9rKjMzvFrS2UBdw1upIWRUGzWMmyHMNRNE0MG2G/Cehth2eVRJxyr6tbHL8k5LLTo5oDLgAweKqHm357X9+wPG/BSXwT06+MD0J5/kpsd/tSlWU5qqxGSDQnxwHjm9Sd7vuxKfMR+DPNPz4BnhEVHDmwaiSBUN1lSrIYNfzrwPmy3i1BLiUyQOdJdFsFjAJcuVQiqlzYZDdLDvXbcG5HAPZX9Y/qQdV1lBGSIZ67gp8uMrke3qIIzXj0ttwx7+SbW/bx/xDZ+36fNJObiHhdOiicNb90z/EVFfPf/syMzJT09cRz6wUflUguL9A5uHwB4J51bh05igeaidtDPz+LXso9xutIeI8LYxqWHHco+w67gz7JucOCbj3gwaQQZVK5vN8Hp7NLG4DxwJDxvFDx9mmEV6iAPBCe3DYRcPVgY8iYMwEMYQPOIZBKaHJNLF6iFHMbWSVh/CPbw1eZb3yScDp3nUvoDTPP3AcagAASBt4+UGIZlljbttRdhPQnqGYVEIwt3gx9x0czjz6eHQP+5bVhYrK0ueEw7emRO0WfCDSBq4LC1LUiF6BAbKR9iFs07HF56IL2Pzrm1j2q+eBwph+hYAtxNKKuwJP1DH7lPA8uNaYYxyLdMtrpHeIqeYN4Sz4hvSWaVyrrBAbJPmKzuFFWKntEK5RuhWSrQvqWOWo/s4ZlqGKQM8U7Ycl7OP48dZXsFiRkkYjldxCEL2SkaQ1EAjONXZLjLsKYkoTikR3q6yplCaw+EFPfiUJ5VMB6cGxgdQDSJIlEIqDmgjwFvjepVKya3VZsEPlqtPAe8AS1H8WDhVD6EBiNFxtCMvKESFBCv7WFgNRyyMUgXTli+V429rtatOWSACZ8kS4YhELqxdpT01WENjtUuWLAFrz04K7ZSWSiDn+++cf+u9X/fFzx679Mtj8Z8DSfuY8deOMHVXzzMjr/0MCDrAh59CUQlvTByUxMoIX3gQ8ZURphBHVHmRlIvwLr+k0x8kYlkZPVeyYzPYktScdP3+m29/Hd+KV3wR/yYev4xXsHnxtXgFF7sa+zXeGL+b+KnuN8bHyL4XfVPjrfDd3cZHLbstDLWXS/X1+ib9PGE5s1xYb9gGb79sM241bTXvQXtM2no0zlhnPmtkq7k3OLKW2wUvbOzm9pi59AzOYjSbwJ43qpQap6imhojJDgtGec5stPSoHjeBPXIhKSHA2g2XLTctVFKsYQkLrHkWGuakexssTVhvhMCwaZHebLZwGFPhsUDQk5KeZiLkQOX8YUvArG7BhTycexFZ6RZRR7u4ZBQuAcozjPdM4OFZVTu6dgRCrrxMbUGelhuljne8DYFzNm9e/Mn4n16Jz+3jxRdTeK9FfCqdbQRWf4jSCt63YfpAj9EzpoXhqhK+Hk1DTXgaD5oBz+OXcwqQZj5EpZqeK0EQAZMy8BrgrL8M2EcSuFGCTcWMpYdLvYPGmGxGQuCGnl2UyYl8Ol9GT+blsyXcUoK9RV4jhkMvPJz8INbHjIqtI93XuvC7Gxi0c1MMpG8MjE/+JNpQW7I0JM0GnIEzJy3YHMbBt2+y6a6P8uE8tBAVoRL450c5qkY1qFb+L8UY+PsQ/cdEI7pV/k/HRPifxhQ0lU4bNaPp8K7RHfR/b/ChWgDLJZ6+GzSlbmzN6Pqs+raFnW0d82fPlHvIzZDAOSj8SwGhCwCXAa7A5SyAASAdAOiMqwEmA8wB6ABYDfAUwIsAfQCnAC4AXAa4AovDAhgA0gGGA1QDTAaYA9ABsBrgKYAXAfoATgFcALgMcAUIwwIYANIBhgNUA0wGmJMY+MA40WAZI88QnFL3xnbqid+I5w7BC4bgtwzBq4bgo4fgML6b7g+Owk34uCH4+CF44xD81iH4hCH4xCE40Oam500ZgjcNwSkH3EiPWUPw2UPwOUNwmadvoP/cIe3zhuDtQ/D5Q/C7huALh+Dy/09veB71vm8c/z1DcPqG2Y3tS4fg9w7BO4bgy4bgnUPw5UPw+4bgK4bgKyn+v1djb5wNCmVuZHN0cmVhbQ1lbmRvYmoNMzIgMCBvYmoNPDwvQXNjZW50IDc3MC9BdmdXaWR0aCA0NDEvQ2FwSGVpZ2h0IDcxNy9EZXNjZW50IC0yMzAvRmxhZ3MgMzIvRm9udEJCb3hbLTk1MSAtNDgxIDE0NDUgMTEyMl0vRm9udEZpbGUyIDMxIDAgUi9Gb250TmFtZS9VR0pFQ0grSGVsdmV0aWNhL0l0YWxpY0FuZ2xlIDAvTWF4V2lkdGggMTUwMC9TdGVtSCA4NS9TdGVtViA5OC9UeXBlL0ZvbnREZXNjcmlwdG9yL1hIZWlnaHQgNTIzPj4NZW5kb2JqDTMzIDAgb2JqDTw8L0JpdHNQZXJDb21wb25lbnQgOC9Db2xvclNwYWNlIDEzIDAgUi9GaWx0ZXIvRENURGVjb2RlL0hlaWdodCAyNzYvSW50ZW50L1BlcmNlcHR1YWwvSW50ZXJwb2xhdGUgdHJ1ZS9MZW5ndGggNzgyMS9TdWJ0eXBlL0ltYWdlL1R5cGUvWE9iamVjdC9XaWR0aCAzMDA+PnN0cmVhbQ0K/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAoHBwkHBgoJCAkLCwoMDxkQDw4ODx4WFxIZJCAmJSMgIyIoLTkwKCo2KyIjMkQyNjs9QEBAJjBGS0U+Sjk/QD3/2wBDAQsLCw8NDx0QEB09KSMpPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT3/wAARCAEUASwDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD2aiiigAooooAKKKKACiiigAooooAKKKKACiiigAopM1HLcRQqWlkRFAySzACgCWkrCvPGmh2ZKtfxyN/diy5/Ssqb4maYn+qt7qU/7oX+daxo1JbRM3Vgt2dlRXBP8UY/4NMlP+9IBUY+KBzzpf8A5G/+tVfVavYn6xT7noVJXCp8T4P+WmmTD/dcGrdv8SNJlKiWO6hz13R5A/EUPD1V9karU31OvpaybLxRo9+wW31CAt/dZtp/I1qK6uMqQR6g5rFxa3RomnsOopM0ZpDFopKKAFooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoopKAFopM1navrtlosHmXsoUn7ka8u/wBBTSbdkJtJXZoE4+lYereMNL0lmjeXz5x/yyh+Y/iegri9b8Zahqm5I2NlangIrfOw9z/hXOB0X7ig9+fWuynhOszmniOkTpNT+IGq3eVtEW0ibgbRuf8AM1zdzLPfPvu5pZm9XYmlhPnXCK5OCKszwBGGw54yR6Cu6lThFe6jknOUt2UlhUdBgU/YKnijV9wBY4HXb1oMJx8oYn6VrdGZB5Yo2rUrQtng5I4xSNZSInQlj19qHILEZCDqaTYpGQeKa1uyfeBFCq6Z21HOVyg0AParNlqOo6aQ1jeTQgc7Q2V/I8UxDvHOBS4xVtKS11JV0zq9M+I11EQmp2ySrnmSL5W/LpXZ6Xr+n6woNncKzd424YfhXj5TjimLuhkV1LI68h1OCPxriq4SL1Wh1QxMlo9T3TPpS96820Tx/c2hWHVFNxD/AM9VHzr9fWvQLHULbUrZZ7OZJom7qensfQ1wVKUqe52QqRnsWqKSgVmWLRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFITSE1xnirxY0TSafpcgMw4lmH/LP2Hv/ACq4U3N2RM5qCuy34j8YR6WxtLEJPfdCM/LF9ff2rg5Wmublrm7d57h/vOx/Qegp0NvtGTkk9STkmrKJ6V6tKjGmtNzz6lRzeplXFtJGhkcg5647VVALdK2r5QLcoer9Kpw2uO1VJGdyosBbHBq/ZK0MvXKkYOeanSADtUojApIGxGsf34ZZULH+7xxUv2YCdBnCt8uAelNwAKsw2ksgDGNlGMgnrTvYW5CbeQOQEU4PSpYdshKOuJAeferttD+6ZxEwduoY9aq3iSLcKXQqD93b1P40uYVhs1iko5HI7is+WzEblT1rft4yYgSxZT93IwQKq3lm4/eRgsO4xyKLgkYgtNzgDHPHSpZLPHZsfSr9mFecocE4OBVxoQRVRlYUjmhHknCkY9ajkjyMV0ElqDnAFUJrTGeK0TvuIyDERnIyKtaZqd5o12LmykKHo6nlXHoR/WkmRo3z/D3FQvtZSBw3oamUE1YuMmnc9X8OeJ7TX4T5f7q5QfvIWPI9x6itsV4Xb3MtpOk9vI0U0Zyrr1Br1Hwt4qj1yHybjbFeoMsoPEg9V/wry61Dk96Ox6FKtzaPc6Simj9KdXMbhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABSE4orK8Ra0mh6Y05AaVvliT+83+eacU5OyE2krsyPGHiM2EZ0+xb/TJR8zA/wCqX1+p7VxUFuFHTnvmhBJPM89wS80jFnY9yauIuTXr0qSpxsebUqOo7iLHkdKsQW+SKlhhzV1IQsLljtABJPpVuRFjBvF3XRXAwmBnHWmqgFSyRCOVl378Y5z1ptTcQAUtJS0CLukrE9/GJsY5Iz0zXT+WCMkZ964rvWlYa1cWmyLCyRlgAH7fjWc4t6o0hJLRmnrMSx6dI0cZDZ6pxt9zWPYXu2XyriTMLckuM4Na/iK+a3j+zRpkTpkuT29MVznf1pU9Y6hPSWhuwXcM87QIR8o+U/3vXFWTHgetcyshjkV0IDKcj2rb07WI55lguhtdzhSOhP8ASnJNahFpk8enxSyhxGBt5yOKlls05KnFWcrGp296rSyk1Ck2aciMyf8AdkhuMVQnlHNatwomXa2KyLi2eMkbcr61vBmMoWKEyrJlSMqagjs2lZQOSv8AEfSrnlkEHHcVcWBYyxXPzHJFa6E6nOyoySFWXHNOt5pLa4jmgcpJE25WHY1t3UUUkRE5CgDhvSsm0kVHdCoZSeSR1qLJ6FX6nqfhjxDHr2neYQqXUXyzRjsfUexrbrxrS9Qn0jVEvLUjKnDR9A691r1zT7+HUrKG6tm3RSruU/0rzcRQ9m7rZnfQre0VnuizmlpB1pa5jcKKKKACiiigAooooAKKKKACiikBoARmwCTwAM5ry/X9TOua00ikm3hJjiX+bfjXXeNNVNjpH2eFiJ7o+WuOoX+I1xFtFtUYFd+Dp/bZx4mp9glSOrUMOSKbFGSa0raHpxXZJ2OW1x0EPA4qjqt1uY20f3VP7wjufStabdBaSyKOVUkVzGSSWYkk8kmso6hLQSijvRVkhRRRQAUdQRnrRRSAlnup7op9okL+Wu1c9hUOSzBVGWY4AHemvIAOtVjMzSBYslycAL1/ChaD1bNqDSorjKC8zcYyYo1yF9i3SnX0UGg2olgDXN0zrtZhyo7gY6fWqNpDqWkyJKIz50wKJBtz+LHoK6K0jnRWlu3Xe5+4o4T6HvWUm77m0UuxDp+qDVIWcQvAynBV+tTlc1KFVju2jd645pStK5disYs1BdRboGAq+VqvOrZUKOCeaaYmjFWHdIox3q00foKs/ZvLmZhjHYUMlaqRk4mXd2q3MTRv+B9D61zuHhmIkGGBwRXWyIapXFlDO4d0y4GM1ZKMSc7JlXGCOa67wPqwtbw2Mjf6PcnMWf4ZO4/H+dcddTNNcM7AA/dwPaprKRgw2NtdSGU+hHSoqr2kXFlQfJJSR7ZS1naHqa6tpUF0v3mGHHow4NaGa8hpp2Z6ad1dC0UUUhhRRRQAUUUUAFFFFABSUtUdYvRp+k3VyescZI+vb9aaV3YTdlc4DxHetqniOdgcw2/7mPHTj7x/Oool6VUskbbliSxOTn1PWtOFMkV7MYqEVFHlSfM22TwR57VpwR4HSq9vF0rRjXispyKiipqsbtpc3lqSdo6ema5c/wAq7pV+Ujtg5ripreWFY5JFKxzZaM+ozSpS6BUjbUiopSKStTMKKKQnFAAeKjklApssuKoTTkkgUDsSSzk8KGJ9AM10WjaDPaTxXk04WXbgx7c4B9/WsvwpGLjUpC8bMqAMGHRWB4rsz65rKcuhtCPUik4Yk9e3pSqC5y3SjaXcE9KlAAGBWZoJSYp2KMUgGkU0rUmKMU7gVmSomSrbLUTLxVpktFGRKrsuG5q/ImarSR1rGRjJWOPvIjFdSIeu7NMhbZMpqxqrZ1GX/ZIWqgPOaofQ77wLf+VfT2TH5JV82Me46/piu5615JpV61neWd2DxE43e46H9DXrSsGAI6GvOxUbTv3OzDSvG3YdRRRXMdAUUUUAFFFFABRRRQAVy/ju4KaRDbg8zzAH6DmunriPHUofU7KDJ+RGc/icVvh43qIyru0GYlutaECciqsC8CtK2XpXpSZ5xcgTgVcRahiXGKtAHPBx+Fcs2axQ9dqrlvu45rldUJl022dVISCR4sHqOeM11Egd2CA+5BqjPp7XMd/a9DIRIrY4HH+IpQai7suabVkckTSUuDyCMEHBHpQckYUfMeB9a6jlsIAWOFBJ9AM1FIX5CKzH0AOa762sbbT4kEMSq+0bmI5PFRvFF5/nbF80LtDAc4rFVr9Df2Om55rcu6E71ZM9NwxmptH0l9YncF/LhjwZGAyeewrvpoIrjAmiSRQeAwziuf8AD6pY+IdRsQpRW+aNfYf/AFjVc91oHJZm1Z2UNjCIbSFYogc47sfUmptrZ56U/HNLisbmlhoUdqOlOoxQMbRTsUYp3AbSU/FGKQEZFMYcVKRTWHFUmJlZ1qvItXGFQyCtIszkjiNW41Of/eH8qqdBWr4gtPJvRMCSJevsRWTWqING2/eWhUdxj6V6r4euvtmh2cxOWMQB+o4/pXlOn8qRXongeXfojx/88pmH58/1rmxavBM2wztJo6WikFLXnncFFFFABRRRQAUUUUAJXAeLm3+JSOyQqP5mvQK898Uc+KJv+uafyrqwn8Q58T8BBAp4rTtl6VQgHStO2HSu2ZxIvRLxVlBjtUEQqyorlkbwQqKFJJ5Y9TT1P73HtmjHFRK/7wGs9zXY4zWdi6zdCMYUPz9e9R6UQ+s2qN035GfUV0Gu6Qt6z3FsAs4HzL2f/A1xck8trdKRuSWJgdrDGMV2QkpQscso2lc9BlueD3NMDeYMr261hr4l02VFZ5zE7nlGUkqf8KxLjUrvVda8mwuJo4XYKoQ44HVqyjTNXM7V3SJd8jBV9ScVUjFjLqa3KSRG8VDHjd8xU+1W0ZCirknbgZbnPvUV1p1reqftEKyEdHA+ZfoetIonXI4brS1nW1vqtnKITLFdWgxteVtsoH4daW813T9Puvs95OY3wDkocEHuDRbsK5oUU2KWOVA8UiSIejK3B/GnmkMSiloxQAlLRRQA0imkVIRTSKYiBhULirDConHFWiZI57xKg/s9XPVZR+tcyeldvf2q3ltJAx2h/wCLGce9cZdW0lpcSQyrgqePcdjWyMSzp/3WrvfATf6Pfp6SqfzH/wBauE05d2R6mu88DDaNQ/30/kayxK/ds0oP94kdYKWkpa8w9EKKKKACiiigAooooASuA8Upt8Tuf70SH+dd+elcR4zj2a1bSf8APSHH5H/69dOFf7w58Sv3ZTh4ArStjnFZkB4FaVr0Fd0ziiacNWVFVoelWVrkkdEBWbYv1qDPrUk54A/GoGbFSi2NkfHFZWo2FpqIX7TEGZejA4YfjV2V+tQda1joQznNX8NIIRNpqkMg+aIHO4eo96ytJvo9LvzNcQO7qpVVB27c9TXc9Kr3mnW2oRsk0S5ZcBwPmU+ua0U9LMjl6op6f4htr+5WARyRO2du7kN7VsoSDwa43QrU2/iQwTkF4Q2D6n2rsVPzDFKaS2HF3JY4Y4i3lrt3nc2O5pjWNq0qytbwtIgwrFckVI8kcMZeaREQdWZsCs5fEVhJJMqTIY4Vy0pOBnsoHVqz1L0NBoY3UAxoFB3YAwM1JjNV7S/tb62E1vOjJ0yTgg+nNTGRBII96eYRkJuGT+FAC4ooUhlDKcg9DS0AJilxRRQAhppp9NNAEbCoXAxUzVDJVoiRTuDtjcjsDWNc6cmp2MLFtsyKQr+vsa1r87LaQ+1ULFT5Uh7ZwK6I7GDKljafZ7c78eZnkius8EDC6gfV0H6GucVdkGMY7muq8ExbdMuJOu+c4P0AFZYl2pMvDa1UdIKWkpa8s9MKKKKAEooopiCiiigArlvG0Gba1uAPuSbCfYj/AOtXUVl+IrRr3QrmNRlwu9fqOa0pS5ZpkVVzQaONg5APpWjbt0FZVpIHUEHOea0YGwRXpyPNRsW5+XFXENULduBV1K45nRAjuBiQH2qrK1XZ1Lr8oyRWZK+Se1ESmMY5NIKbnmpY4i65Xse9abEiBGP3RS7GXqKuDGAB0FGPyqbjsY9/p0d4EYMYZo23RzIPmU/1FJb22qSyTLcXSxsi/uGiUYc/7QP8q1zGh/hpgjjjO9mChRkk9AKfMFjhdd1u61FEs7iFITCf3qAdXHGfpWOe2O1XdZuI7rWbuaFg8bSHaw6EetU66YqyMW7sQHB7j8aeJH8wSGR/MHR9x3D8aZS0xHUaH4qdfLttQK+UBgTk8/8AAvWuna9tQVH2qHLcj5hXmFIAPSs5U02WqjR6sPm6YPoRRXH+ENVaK6OnzOzRyDMQ67T3/Cuw/iNYyjZ2NE7q4vammnY4pppARNUL1O1QSHiriRIzdUbFvj1OKbZxERRLjrzU11FHOAHBODkDNKo2RtJ6DA+tb30sYlC6IXf6DNdp4XtzbeH7VWyGZTIc+5zXDzRvcyJAnLzOEH4mvSoYhDCkajAQBR9BXNi5Wiom+EV25ElFJS1wncFFFGaBiUUUUCuFFFFAXCg8jBxjvRQaAPOLi1Onatc2pGFR9ye6nkVbhbOK0/GFiV8nUEX7n7uUj+6eh/P+dY9uwIFepTnzwTPNqR5JtGtbvWjG2RWLHLs5NaMMuRWc4jgy2zYFZ95Fk7159cVZL5puazWhre5nqrH+E1egQrCA3rmkkk8vb8ucnGfSpRyKpsAx6EfjRRiipGIa5Xxhqs0TJp8LlVkTdNj+IHoK6zNcN4wluH1QQzKqwoAYiFxuyOcnvVwV2TN6HPjsOw4FLRnFFdJiFFFFABRSUUAWrO/nsC5tZPKaQbWkVQWx6A9q0U8VanHMGWZTEMDy3Xdx9euaxaM0nFMd2jq38cNuKxWIx2LSf0q7YeKrK8VVnPkXBbbsPIJ9jXD1e0ixk1DU4IoxlUYPIcfdUGocIpDUmegvx1qvIeKnlYEk9jVWRs1MQkyEjJovCIbRYectzU8Ee85PSqGqT7p3I6JxWi1djOWiLHhm0+1a6JWX5LZd3/AjwP0zXcVj+GNPNhpSmQDzpz5j/j0H5VsV5+InzzdjuoQ5IJMKKKKxN7hS0lAoAKKQ9TSUCHUU2igB1J3pKKAGXNul1byQyDKSKVIrz9oJNPvZLWb70ZwD/eHY16J2rB8T6U13bC7gX9/AOQP4l7j8K6MPU5JWezMK9PmV1ujD3HyzjrjipbS8BjHmfI6naQfWqdtMHQc1ZKJKpDqCD1rvcTiTNASe9SIc1lIlxBKnluZI+6v1A+taSOCKxlGxpGRY4I55FOqJWp4NZmqY6ijNFIYVS1fTE1fT5LdgPMAzE391u1XqKAseTOjI7RyDa6EqwPYikrrvF+iSySjULSHduGJwg546NiuRGCK6oyUkYSVmLRRkYoqhBRRRQAUUmaCcUAOVWdlROXdgqj3Neg6Rpcej2Xkod0p+aWQjlj/gK5Dw7Ym+1mLJ/dwYlc/ToPxNd1I2Sazm7uxS0VyORqgwWbAHWnscmp4Ygo3H71LZEbseqCJB7VnaPp51LVcyA+TGd7e/PAq3dTsBsQbnY7UUdz6VvaVp40+zCHBlY7pGHdqyqVOSL7s1p0+eXki8owOcfhS03vRXCdw6im0UAOoptLQAHqaTNB6migQZozRRQAZozRRQAZooooA47X9J/s24N3bgi2kPzKP+WZ/wNVoZAy9a7eSNZY2jkUMjDBU9CK4vUtMk0a4LLue0c/K39z2P+Nd9CrzLllucdalyvmjsSqcVMjVUilBFWAfStmjBMsq1TK1VFapFes3E0UrFoGlqJXzTwazsaKQ+lpB0pe9IdxASO9ZGt+H7LUbWabyhFcIpYSxjBOBnn1rWZgvJIA96qaxcfZ9FvZVZQwibbn1PFCunoDtbU8xXBAI6GlpAMAD0FLXaYBSUUDjmkAvvVuy0i91H5rWBig4LtwoP1NanhbSYb6SW5u03xRYCIR8rN/XFdcXAG0ABR0A6ColJrRAUdI01NJtPLDBpX+aRwOp9PoKsMxNPJz0qSKHHzN1qA3Ehh5DN+VFxMsUZJOAKdLKI1JJAA9ak03TWvHW5uVKxA5jQ/wAXufaockleRUYuTsifRdPbcLy6H7wj92h/hHr9TW1mkxS1xSk5O7O2EVFWQUZooqSgzRmiigAzS5pKKAEPU0maU9TSUwDNGaKKADNGaKKADNGaKKAFzTJIkmjaOVA6MMMpHBFOooA5HVNDl0zdNaBpLbOSo5ZP8RVaC4DAe9dsRmsTUvDizu01iVilPJQ/db/A12UsR0n95y1aHWBnKc09TVAtPaS+VdRmOQdj3+h71YScEda6LdTmuWw+KVrhIhmRsCoBID3qtfQzThDGqsq9Rnmp5R3HS30kjHa21e2OtM+1SYxvOKpMkkX+sRl+tIJKrlHzF4zuwwzkj3rJ8SXDDTUTdxJIAR7CrHm4rC1q6a4uzFwI4jgD1PrQo2Yc1zNHc0tJnFWILG4uFDxx/If4icVoBXJA61raRpaXDebfb0hB+VFGGf8AwFWbCyW2jPmbHcnOducewq6kbyEAZwT1pNCubsSpFAiQoEjA+VR2FOWMyH0qWGALEm7IwBjPWpGkVB1rBy7Dt3EWJUHODUU1wIh654AHOaE+0XpMdmm493P3V/GtWw0iO0IkkJmnPV2HA+g7VnKajvuaRg57bFWx0gystxejpykJ6D3NbQ46UUVyyk5O7OuMFBWQZozRRUlBmjNFFABmjNFFABmlzSUtADWcbj9aTeKRh8x+tJiqshXHbxRvFNxRiiwh28UbxTcUYosA7eKN4puKMUWAdvFG8U3FGKLAO3ijeKbijFFgGXMMN3HsniWRfQ9qw7nw+6ZazkDD/nnJ/Q10GKTbVwnKGzInBS3ORdWtztuI3hI7uOPzqUAkZVgfpXUtGrrtZQw9CM1Qn0SzlcusZif1jYit1iE/iRg6DWzOX1GYlBFGxJz8wHSs8bh1BFdLceFiCWt74IT2kAxWbNpWoQMQHtZlH918H8q6I1YPZmEqc10MwsyDdg4Fc/MJJ5ywjwD6d665ba+B5tc/RxUJ0ecvuW0dD14YY/nVOS7iSl2MjS9Kct5z5U44UitdbTB+Y8D0qylnfNjdGF92YVah0SeUbmngT8aTnFdR8sn0KIjiQep96kRt2ApCjuT2rYh8LQHma5aT2XitO30mygIMcKkju3NYyxEVtqaRoTe5kRNcXOFt4mfAxvfgVeg0UffvXMh/uLwv/wBetUKB04oxXPKq3todEaMVvqNjCRIERQqjoAKfvpuKMVkajt4o3im4oxRZBcdvFG8U3FGKLAO3ijeKbijFFgHbxRvFNxRiiwDt4o3im4pQKLAPYfMfrSYpxHJoxU3KsNxRinYoxRcLDcUYp2KMUXCw3FGKdijFFwsNxRinYoxRcLDcUYp2KZJMkfU89hTAXFMaZEHJGfQVXkkeU46D2poj9qpR7kN9h7XJP3Fx9aYWZurn6Cn7KRvkxxmq0ROpWliGd3X61HgDGAKssS3Hb0pvl57VXMTykOSKrF3DfNmr/l1BcQsHyehouPlEimDDBwDT9qN2qDy6erMgx15p3DlJRGByuQfrUiyyr1c4piHfyTin4x70mw5Wiyk7AfMufxqVZVcY6H0NQocjpTigeoaRSuTgUYqAF4/9pfSpklV/Y+lS9C0xcUYp2KMUrjG4oxTsUYpXCw3FGKdijFFwsNxRinYoxRcLDcUuKXFGKLhYfijFOpKkobijFOopgNxRinUUANxRinUUANxQeBk06kIzQBC8jHhRUXk556n3qzspdlUnYlq5WEXtTvLqfZRto5g5SDy6DCCMVPtpdtHMPlKpg54FHkn0q1to20cwuUq+T7UyaDcnA5q7tHpSFQR0o5g5TL8j2o8j2rR8oUnlCjmDlKHke1PRCOGGRVzyqPL9qOYOUiRAw4p4TFSKmKdto5g5SLbTfLGc1Pto20rhykakjg9KkHNG2lAxSGhMUYp1FAxuKMU6igBuKMU6igBuKXFLRQAtFFFIYlFFFABRRRQAUUUUAGKWiigBKKKKACiiigApaKKACiiigAooooAKSiigAooooAWiiigBKKKKACiiigAooooAKKKKACiiigApaKKAP//ZDQplbmRzdHJlYW0NZW5kb2JqDTEgMCBvYmoNPDwvQ291bnQgMS9LaWRzWzcgMCBSXS9UeXBlL1BhZ2VzPj4NZW5kb2JqDTIgMCBvYmoNPDwvTGVuZ3RoIDMzNjYvU3VidHlwZS9YTUwvVHlwZS9NZXRhZGF0YT4+c3RyZWFtDQo8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzAxNiA5MS4xNjM2MTYsIDIwMTgvMTAvMjktMTY6NTg6NDkgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6cGRmPSJodHRwOi8vbnMuYWRvYmUuY29tL3BkZi8xLjMvIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICAgICAgICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIj4KICAgICAgICAgPHhtcDpDcmVhdGVEYXRlPjIwMTgtMDMtMjFUMTM6NTg6MDdaPC94bXA6Q3JlYXRlRGF0ZT4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5Xb3JkPC94bXA6Q3JlYXRvclRvb2w+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDE5LTA1LTI2VDExOjE3OjE3LTA3OjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcDpNZXRhZGF0YURhdGU+MjAxOS0wNS0yNlQxMToxNzoxNy0wNzowMDwveG1wOk1ldGFkYXRhRGF0ZT4KICAgICAgICAgPHBkZjpLZXl3b3Jkcy8+CiAgICAgICAgIDxwZGY6UHJvZHVjZXI+TWFjIE9TIFggMTAuMTEuNiBRdWFydHogUERGQ29udGV4dDwvcGRmOlByb2R1Y2VyPgogICAgICAgICA8ZGM6Zm9ybWF0PmFwcGxpY2F0aW9uL3BkZjwvZGM6Zm9ybWF0PgogICAgICAgICA8ZGM6dGl0bGU+CiAgICAgICAgICAgIDxyZGY6QWx0PgogICAgICAgICAgICAgICA8cmRmOmxpIHhtbDpsYW5nPSJ4LWRlZmF1bHQiPk1pY3Jvc29mdCBXb3JkIC0gV29ybGRfV2lkZV9Db3JwX2xvcmVtLmRvY3g8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6QWx0PgogICAgICAgICA8L2RjOnRpdGxlPgogICAgICAgICA8eG1wTU06RG9jdW1lbnRJRD51dWlkOjJlNjBiMTYyLTY4MmQtNGZjOS1hYjFjLTcwMTQ0OTllMGQ0OTwveG1wTU06RG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+dXVpZDplYmRjZjYzOS02NWNjLTQ0YTgtODEyMi02ZDA2YWFjNzI3MDI8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz4NCmVuZHN0cmVhbQ1lbmRvYmoNMyAwIG9iag1bXQ1lbmRvYmoNNCAwIG9iag08PC9BQVBMOktleXdvcmRzIDMgMCBSL0NyZWF0aW9uRGF0ZShEOjIwMTgwMzIxMTM1ODA3WikvQ3JlYXRvcihXb3JkKS9LZXl3b3JkcygpL01vZERhdGUoRDoyMDE5MDUyNjExMTcxNy0wNycwMCcpL1Byb2R1Y2VyKE1hYyBPUyBYIDEwLjExLjYgUXVhcnR6IFBERkNvbnRleHQpL1RpdGxlKE1pY3Jvc29mdCBXb3JkIC0gV29ybGRfV2lkZV9Db3JwX2xvcmVtLmRvY3gpPj4NZW5kb2JqDXhyZWYNCjAgNQ0KMDAwMDAwMDAwMCA2NTUzNSBmDQowMDAwMDM4NzQzIDAwMDAwIG4NCjAwMDAwMzg3OTQgMDAwMDAgbg0KMDAwMDA0MjIzNyAwMDAwMCBuDQowMDAwMDQyMjU1IDAwMDAwIG4NCnRyYWlsZXINCjw8L1NpemUgNS9JRFs8OTNEREQ1RjRBQjk1NTU2NTVFMUFFQkU3Mjc4OTFGNzQ+PDUyRUNGNjUzRTlDQTM4NDNBMEI2MTY0ODI1RkZENjJDPl0+Pg0Kc3RhcnR4cmVmDQoxMTYNCiUlRU9GDQo=", - document_id="1", # a label used to reference the doc - file_extension="pdf", # many different document types are accepted - name="Lorem" # can be different from actual file name - ) - envelope_definition.documents = [document1] - envelope_definition.status = args["envelope_args"]["status"] - signer1 = Signer( - email=args["envelope_args"]["signer_email"], # represents your {signer_name} - name=signer_name, # represents your {signer_email} - access_code=recip_access_code, # represents your {ACCESS_CODE} for your recipient to access the envelope - recipient_id="1", - routing_order="1" - ) - # Create your signature tab - sign_here1 = SignHere( - name="SignHereTab", - x_position="75", - y_position="572", - tab_label="SignHereTab", - page_number="1", - document_id="1", - # A 1- to 8-digit integer or 32-character GUID to match recipient IDs on your own systems. - # This value is referenced in the Tabs element below to assign tabs on a per-recipient basis. - recipient_id="1" # represents your {RECIPIENT_ID} - ) - - # Add the tabs model (including the sign_here tabs) to the signer - # The Tabs object wants arrays of the different field/tab types - signer1.tabs = Tabs(sign_here_tabs=[sign_here1]) - - # Tabs are set per recipient - envelope_definition.recipients = Recipients(signers=[signer1]) - # Step 4: Call the eSignature REST API - envelope_api = EnvelopesApi(api_client) - results = envelope_api.create_envelope(args["account_id"], envelope_definition=envelope_definition) - envelope_id = results.envelope_id - - app.logger.info(f"Envelope was created. EnvelopeId {envelope_id} ") - - return render_template("example_done.html", - title="Envelope sent", - h1="Envelope sent", - message=f"""The envelope has been created and sent!
- Envelope ID {envelope_id}.""" - ) - - except ApiException as err: - error_body_json = err and hasattr(err, "body") and err.body - # we can pull the DocuSign error code and message from the response body - error_body = json.loads(error_body_json) - error_code = error_body and "errorCode" in error_body and error_body["errorCode"] - error_message = error_body and "message" in error_body and error_body["message"] - # In production, may want to provide customized error messages and - # remediation advice to the user. - return render_template("error.html", - err=err, - error_code=error_code, - error_message=error_message - ) - - - - else: - flash("Sorry, you need to re-authenticate.") - # We could store the parameters of the requested operation - # so it could be restarted automatically. - # But since it should be rare to have a token issue here, - # we'll make the user re-enter the form data after - # authentication. - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - - - -def get_controller(): - """responds with the form for the example""" - - if views.ds_token_ok(): - return render_template("eg019_access_code_authentication.html", - title="Access-code recipient authentication", - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - signer_name=ds_config.DS_CONFIG["signer_name"], - signer_email=ds_config.DS_CONFIG["signer_email"] - ) - else: - # Save the current operation so it will be resumed after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - - diff --git a/app/eg020_sms_authentication.py b/app/eg020_sms_authentication.py deleted file mode 100644 index 3e3f79de..00000000 --- a/app/eg020_sms_authentication.py +++ /dev/null @@ -1,160 +0,0 @@ -""" Example 020: Sms Recipient Authentication""" - -from flask import render_template, url_for, redirect, session, flash, request -from os import path -from app import app, ds_config, views -import base64 -import re -import json -from docusign_esign import * -from docusign_esign.client.api_exception import ApiException - -eg = "eg020" # reference (and url) for this example -demo_docs_path = path.abspath(path.join(path.dirname(path.realpath(__file__)), "static/demo_documents")) - - -def controller(): - """Controller router using the HTTP method""" - if request.method == "GET": - return get_controller() - elif request.method == "POST": - return create_controller() - else: - return render_template("404.html"), 404 - - -def create_controller(): - """ - 1. Check the token - 2. Call the worker method - """ - minimum_buffer_min = 3 - if views.ds_token_ok(minimum_buffer_min): - - # More data validation would be a good idea here - # Strip anything other than characters listed - pattern = re.compile("([^\w \-\@\.\,])+") - phone_number = request.form.get("phone_number") - signer_email = pattern.sub("", request.form.get("signer_email")) - signer_name = pattern.sub("", request.form.get("signer_name")) - envelope_args = { - "signer_email": signer_email, - "signer_name": signer_name, - "status": "sent", - } - args = { - - # Step 1: Obtain your OAuth token - "account_id": session["ds_account_id"], # represents your {ACCOUNT_ID} - "base_path": session["ds_base_path"], - "ds_access_token": session["ds_access_token"], # represnts your {ACCESS_TOKEN} - "envelope_args": envelope_args - } - try: - - # Step 2: Construct your API headers - api_client = ApiClient() - api_client.host = args["base_path"] - api_client.set_default_header("Authorization", "Bearer " + args["ds_access_token"]) - - # Step 3: Construct your envelope JSON body - envelope_definition = EnvelopeDefinition( - email_subject="Please sign this document set" - ) - # Add a Document - document1 = Document( # create the DocuSign document object - document_base64="JVBERi0xLjMNJeLjz9MNCjUgMCBvYmoNPDwvTGluZWFyaXplZCAxL0wgNDI3MTAvTyA3L0UgMzg3NDMvTiAxL1QgNDI0OTEvSCBbIDg5NiAxODVdPj4NZW5kb2JqDSAgICAgICAgICAgICAgICAgICAgDQp4cmVmDQo1IDMwDQowMDAwMDAwMDE2IDAwMDAwIG4NCjAwMDAwMDEwODEgMDAwMDAgbg0KMDAwMDAwMTE0MSAwMDAwMCBuDQowMDAwMDAxMzE4IDAwMDAwIG4NCjAwMDAwMDE0NzkgMDAwMDAgbg0KMDAwMDAwMTg0OCAwMDAwMCBuDQowMDAwMDAxOTk2IDAwMDAwIG4NCjAwMDAwMDIxOTcgMDAwMDAgbg0KMDAwMDAwMjYyMSAwMDAwMCBuDQowMDAwMDAyNjU2IDAwMDAwIG4NCjAwMDAwMDMzOTYgMDAwMDAgbg0KMDAwMDAwMzkwMSAwMDAwMCBuDQowMDAwMDA0NDExIDAwMDAwIG4NCjAwMDAwMDUwMTEgMDAwMDAgbg0KMDAwMDAwNTUzMCAwMDAwMCBuDQowMDAwMDA2MDQ5IDAwMDAwIG4NCjAwMDAwMDY1ODcgMDAwMDAgbg0KMDAwMDAwNjk4MyAwMDAwMCBuDQowMDAwMDA5NjkwIDAwMDAwIG4NCjAwMDAwMTYzMjUgMDAwMDAgbg0KMDAwMDAxNjU0NyAwMDAwMCBuDQowMDAwMDE3MDg3IDAwMDAwIG4NCjAwMDAwMTczMDYgMDAwMDAgbg0KMDAwMDAxNzYwMCAwMDAwMCBuDQowMDAwMDE5NTcxIDAwMDAwIG4NCjAwMDAwMTk3OTUgMDAwMDAgbg0KMDAwMDAyMDE3MiAwMDAwMCBuDQowMDAwMDMwNTAxIDAwMDAwIG4NCjAwMDAwMzA3MzMgMDAwMDAgbg0KMDAwMDAwMDg5NiAwMDAwMCBuDQp0cmFpbGVyDQo8PC9TaXplIDM1L1Jvb3QgNiAwIFIvSW5mbyA0IDAgUi9JRFs8OTNEREQ1RjRBQjk1NTU2NTVFMUFFQkU3Mjc4OTFGNzQ+PDUyRUNGNjUzRTlDQTM4NDNBMEI2MTY0ODI1RkZENjJDPl0vUHJldiA0MjQ4MT4+DQpzdGFydHhyZWYNCjANCiUlRU9GDQogICAgICAgICAgICAgICAgDQozNCAwIG9iag08PC9GaWx0ZXIvRmxhdGVEZWNvZGUvSSAxMTYvTGVuZ3RoIDEwNC9TIDQwPj5zdHJlYW0NCmjeYmBgkGZgYN7DAASTHjGgAmYgZmHgWIAqKg3FDAzKDHxMFuwPghsKmWZIBDAwHWSPkN3Q6/iEfYJ8QZRXQboC94Y6hx0sPJUM+o5hC27whJ88ADWDhYFhSRiQZgTiRwABBgBLlxXzDQplbmRzdHJlYW0NZW5kb2JqDTYgMCBvYmoNPDwvTWV0YWRhdGEgMiAwIFIvUGFnZXMgMSAwIFIvVHlwZS9DYXRhbG9nPj4NZW5kb2JqDTcgMCBvYmoNPDwvQ29udGVudHNbMTQgMCBSIDE1IDAgUiAxNiAwIFIgMTcgMCBSIDE4IDAgUiAxOSAwIFIgMjAgMCBSIDIxIDAgUl0vQ3JvcEJveFswIDAgNjEyIDc5Ml0vTWVkaWFCb3hbMCAwIDYxMiA3OTJdL1BhcmVudCAxIDAgUi9SZXNvdXJjZXMgOCAwIFIvUm90YXRlIDAvVHlwZS9QYWdlPj4NZW5kb2JqDTggMCBvYmoNPDwvQ29sb3JTcGFjZTw8L0NzMSAxMyAwIFI+Pi9Gb250PDwvVFQxIDkgMCBSL1RUMyAxMCAwIFIvVFQ1IDExIDAgUi9UVDYgMTIgMCBSPj4vUHJvY1NldFsvUERGL1RleHQvSW1hZ2VCL0ltYWdlQy9JbWFnZUldL1hPYmplY3Q8PC9JbTEgMzMgMCBSPj4+Pg1lbmRvYmoNOSAwIG9iag08PC9CYXNlRm9udC9aUFFQU0ErVHJlYnVjaGV0TVMvRW5jb2RpbmcvTWFjUm9tYW5FbmNvZGluZy9GaXJzdENoYXIgMzIvRm9udERlc2NyaXB0b3IgMjQgMCBSL0xhc3RDaGFyIDExOC9TdWJ0eXBlL1RydWVUeXBlL1R5cGUvRm9udC9XaWR0aHNbMzAxIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgNTk4IDYxMyAwIDAgMCAwIDI3OCAwIDAgNTA2IDAgMCAwIDAgMCAwIDAgMCAwIDAgODUyIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDU1NyA1NDUgMzcwIDAgMCAyODUgMCAwIDI5NSA4MzAgNTQ2IDUzNyA1NTcgMCAzODkgNDA1IDAgNTQ2IDQ5MF0+Pg1lbmRvYmoNMTAgMCBvYmoNPDwvQmFzZUZvbnQvTVVLRlJOK0NhbGlicmkvRmlyc3RDaGFyIDMzL0ZvbnREZXNjcmlwdG9yIDI2IDAgUi9MYXN0Q2hhciAzMy9TdWJ0eXBlL1RydWVUeXBlL1RvVW5pY29kZSAyNyAwIFIvVHlwZS9Gb250L1dpZHRoc1syMjZdPj4NZW5kb2JqDTExIDAgb2JqDTw8L0Jhc2VGb250L0hGQU1aRitDYWxpYnJpLUJvbGQvRmlyc3RDaGFyIDMzL0ZvbnREZXNjcmlwdG9yIDI5IDAgUi9MYXN0Q2hhciA0NS9TdWJ0eXBlL1RydWVUeXBlL1RvVW5pY29kZSAzMCAwIFIvVHlwZS9Gb250L1dpZHRoc1syMjYgNjA2IDQ3NCAzNTUgNTAzIDUzNyA0OTQgNTM3IDM5OSAyNDYgMjc2IDQzMCA1MDddPj4NZW5kb2JqDTEyIDAgb2JqDTw8L0Jhc2VGb250L1VHSkVDSCtIZWx2ZXRpY2EvRW5jb2RpbmcvTWFjUm9tYW5FbmNvZGluZy9GaXJzdENoYXIgMzIvRm9udERlc2NyaXB0b3IgMzIgMCBSL0xhc3RDaGFyIDEyMi9TdWJ0eXBlL1RydWVUeXBlL1R5cGUvRm9udC9XaWR0aHNbMjc4IDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAyNzggMzMzIDI3OCAwIDAgMCA1NTYgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDY2NyA2NjcgNzIyIDcyMiAwIDAgMCAwIDI3OCAwIDY2NyA1NTYgMCA3MjIgNzc4IDY2NyAwIDcyMiAwIDYxMSA3MjIgMCAwIDY2NyAwIDAgMCAwIDAgMCAwIDAgNTU2IDU1NiA1MDAgNTU2IDU1NiAyNzggNTU2IDU1NiAyMjIgMCA1MDAgMjIyIDgzMyA1NTYgNTU2IDU1NiAwIDMzMyA1MDAgMjc4IDU1NiA1MDAgNzIyIDUwMCA1MDAgNTAwXT4+DWVuZG9iag0xMyAwIG9iag1bL0lDQ0Jhc2VkIDIyIDAgUl0NZW5kb2JqDTE0IDAgb2JqDTw8L0ZpbHRlci9GbGF0ZURlY29kZS9MZW5ndGggNjcwPj5z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ACiiigAooooAKKKKACiiigApaKKAP//ZDQplbmRzdHJlYW0NZW5kb2JqDTEgMCBvYmoNPDwvQ291bnQgMS9LaWRzWzcgMCBSXS9UeXBlL1BhZ2VzPj4NZW5kb2JqDTIgMCBvYmoNPDwvTGVuZ3RoIDMzNjYvU3VidHlwZS9YTUwvVHlwZS9NZXRhZGF0YT4+c3RyZWFtDQo8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzAxNiA5MS4xNjM2MTYsIDIwMTgvMTAvMjktMTY6NTg6NDkgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6cGRmPSJodHRwOi8vbnMuYWRvYmUuY29tL3BkZi8xLjMvIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICAgICAgICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIj4KICAgICAgICAgPHhtcDpDcmVhdGVEYXRlPjIwMTgtMDMtMjFUMTM6NTg6MDdaPC94bXA6Q3JlYXRlRGF0ZT4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5Xb3JkPC94bXA6Q3JlYXRvclRvb2w+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDE5LTA1LTI2VDExOjE3OjE3LTA3OjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcDpNZXRhZGF0YURhdGU+MjAxOS0wNS0yNlQxMToxNzoxNy0wNzowMDwveG1wOk1ldGFkYXRhRGF0ZT4KICAgICAgICAgPHBkZjpLZXl3b3Jkcy8+CiAgICAgICAgIDxwZGY6UHJvZHVjZXI+TWFjIE9TIFggMTAuMTEuNiBRdWFydHogUERGQ29udGV4dDwvcGRmOlByb2R1Y2VyPgogICAgICAgICA8ZGM6Zm9ybWF0PmFwcGxpY2F0aW9uL3BkZjwvZGM6Zm9ybWF0PgogICAgICAgICA8ZGM6dGl0bGU+CiAgICAgICAgICAgIDxyZGY6QWx0PgogICAgICAgICAgICAgICA8cmRmOmxpIHhtbDpsYW5nPSJ4LWRlZmF1bHQiPk1pY3Jvc29mdCBXb3JkIC0gV29ybGRfV2lkZV9Db3JwX2xvcmVtLmRvY3g8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6QWx0PgogICAgICAgICA8L2RjOnRpdGxlPgogICAgICAgICA8eG1wTU06RG9jdW1lbnRJRD51dWlkOjJlNjBiMTYyLTY4MmQtNGZjOS1hYjFjLTcwMTQ0OTllMGQ0OTwveG1wTU06RG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+dXVpZDplYmRjZjYzOS02NWNjLTQ0YTgtODEyMi02ZDA2YWFjNzI3MDI8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz4NCmVuZHN0cmVhbQ1lbmRvYmoNMyAwIG9iag1bXQ1lbmRvYmoNNCAwIG9iag08PC9BQVBMOktleXdvcmRzIDMgMCBSL0NyZWF0aW9uRGF0ZShEOjIwMTgwMzIxMTM1ODA3WikvQ3JlYXRvcihXb3JkKS9LZXl3b3JkcygpL01vZERhdGUoRDoyMDE5MDUyNjExMTcxNy0wNycwMCcpL1Byb2R1Y2VyKE1hYyBPUyBYIDEwLjExLjYgUXVhcnR6IFBERkNvbnRleHQpL1RpdGxlKE1pY3Jvc29mdCBXb3JkIC0gV29ybGRfV2lkZV9Db3JwX2xvcmVtLmRvY3gpPj4NZW5kb2JqDXhyZWYNCjAgNQ0KMDAwMDAwMDAwMCA2NTUzNSBmDQowMDAwMDM4NzQzIDAwMDAwIG4NCjAwMDAwMzg3OTQgMDAwMDAgbg0KMDAwMDA0MjIzNyAwMDAwMCBuDQowMDAwMDQyMjU1IDAwMDAwIG4NCnRyYWlsZXINCjw8L1NpemUgNS9JRFs8OTNEREQ1RjRBQjk1NTU2NTVFMUFFQkU3Mjc4OTFGNzQ+PDUyRUNGNjUzRTlDQTM4NDNBMEI2MTY0ODI1RkZENjJDPl0+Pg0Kc3RhcnR4cmVmDQoxMTYNCiUlRU9GDQo=", - document_id="1", # a label used to reference the doc - file_extension="pdf", # many different document types are accepted - name="Lorem" # can be different from actual file name - ) - envelope_definition.documents = [document1] - envelope_definition.status = args["envelope_args"]["status"] - signer1 = Signer( - email=args["envelope_args"]["signer_email"], # represents your {signer_email} - name=args["envelope_args"]["signer_name"], # represents your {signer_name} - sms_authentication={ "senderProvidedNumbers": [phone_number]}, - id_check_configuration_name="SMS Auth $", - require_id_lookup="true", - recipient_id="1", - routing_order="1" - ) - # Create your signature tab - sign_here1 = SignHere( - name="SignHereTab", - x_position="75", - y_position="572", - tab_label="SignHereTab", - page_number="1", - document_id="1", - # A 1- to 8-digit integer or 32-character GUID to match recipient IDs on your own systems. - # This value is referenced in the Tabs element below to assign tabs on a per-recipient basis. - recipient_id="1" # represents your {RECIPIENT_ID} - ) - - # Add the tabs model (including the sign_here tabs) to the signer - # The Tabs object wants arrays of the different field/tab types - signer1.tabs = Tabs(sign_here_tabs=[sign_here1]) - - # Tabs are set per recipient - envelope_definition.recipients = Recipients(signers=[signer1]) - # Step 4: Call the eSignature REST API - envelope_api = EnvelopesApi(api_client) - results = envelope_api.create_envelope(args["account_id"], envelope_definition=envelope_definition) - envelope_id = results.envelope_id - app.logger.info(f"Envelope was created. EnvelopeId {envelope_id} ") - - return render_template("example_done.html", - title="Envelope sent", - h1="Envelope sent", - message=f"""The envelope has been created and sent!
- Envelope ID {envelope_id}.""" - ) - - except ApiException as err: - error_body_json = err and hasattr(err, "body") and err.body - # we can pull the DocuSign error code and message from the response body - error_body = json.loads(error_body_json) - error_code = error_body and "errorCode" in error_body and error_body["errorCode"] - error_message = error_body and "message" in error_body and error_body["message"] - # In production, may want to provide customized error messages and - # remediation advice to the user. - return render_template("error.html", - err=err, - error_code=error_code, - error_message=error_message - ) - - - - else: - flash("Sorry, you need to re-authenticate.") - # We could store the parameters of the requested operation - # so it could be restarted automatically. - # But since it should be rare to have a token issue here, - # we'll make the user re-enter the form data after - # authentication. - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - - - -def get_controller(): - """responds with the form for the example""" - - if views.ds_token_ok(): - return render_template("eg020_sms_authentication.html", - title="SMS recipient authentication", - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - signer_name=ds_config.DS_CONFIG["signer_name"], - signer_email=ds_config.DS_CONFIG["signer_email"] - ) - else: - # Save the current operation so it will be resumed after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - - diff --git a/app/eg021_phone_authentication.py b/app/eg021_phone_authentication.py deleted file mode 100644 index bd6b2a70..00000000 --- a/app/eg021_phone_authentication.py +++ /dev/null @@ -1,160 +0,0 @@ -""" Example 021: Recipient Phone Authentication""" - -from flask import render_template, url_for, redirect, session, flash, request -from os import path -from app import app, ds_config, views -import base64 -import re -import json -from docusign_esign import * -from docusign_esign.client.api_exception import ApiException - -eg = "eg021" # reference (and url) for this example -demo_docs_path = path.abspath(path.join(path.dirname(path.realpath(__file__)), "static/demo_documents")) - - -def controller(): - """Controller router using the HTTP method""" - if request.method == "GET": - return get_controller() - elif request.method == "POST": - return create_controller() - else: - return render_template("404.html"), 404 - - -def create_controller(): - """ - 1. Check the token - 2. Call the worker method - """ - minimum_buffer_min = 3 - if views.ds_token_ok(minimum_buffer_min): - - # More data validation would be a good idea here - # Strip anything other than characters listed - pattern = re.compile("([^\w \-\@\.\,])+") - phoneNumber = request.form.get("phoneNumber") - signer_email = pattern.sub("", request.form.get("signer_email")) - signer_name = pattern.sub("", request.form.get("signer_name")) - envelope_args = { - "signer_email": signer_email, - "signer_name": signer_name, - "status": "sent", - } - args = { - - # Step 1: Obtain your OAuth token - "account_id": session["ds_account_id"], # represents your {ACCOUNT_ID} - "base_path": session["ds_base_path"], - "ds_access_token": session["ds_access_token"], # represnts your {ACCESS_TOKEN} - "envelope_args": envelope_args - } - try: - - # Step 2: Construct your API headers - api_client = ApiClient() - api_client.host = args["base_path"] - api_client.set_default_header("Authorization", "Bearer " + args["ds_access_token"]) - - # Step 3: Construct your envelope JSON body - envelope_definition = EnvelopeDefinition( - email_subject="Please sign this document set" - ) - # Add a Document - document1 = Document( # create the DocuSign document object - document_base64="JVBERi0xLjMNJeLjz9MNCjUgMCBvYmoNPDwvTGluZWFyaXplZCAxL0wgNDI3MTAvTyA3L0UgMzg3NDMvTiAxL1QgNDI0OTEvSCBbIDg5NiAxODVdPj4NZW5kb2JqDSAgICAgICAgICAgICAgICAgICAgDQp4cmVmDQo1IDMwDQowMDAwMDAwMDE2IDAwMDAwIG4NCjAwMDAwMDEwODEgMDAwMDAgbg0KMDAwMDAwMTE0MSAwMDAwMCBuDQowMDAwMDAxMzE4IDAwMDAwIG4NCjAwMDAwMDE0NzkgMDAwMDAgbg0KMDAwMDAwMTg0OCAwMDAwMCBuDQowMDAwMDAxOTk2IDAwMDAwIG4NCjAwMDAwMDIxOTcgMDAwMDAgbg0KMDAwMDAwMjYyMSAwMDAwMCBuDQowMDAwMDAyNjU2IDAwMDAwIG4NCjAwMDAwMDMzOTYgMDAwMDAgbg0KMDAwMDAwMzkwMSAwMDAwMCBuDQowMDAwMDA0NDExIDAwMDAwIG4NCjAwMDAwMDUwMTEgMDAwMDAgbg0KMDAwMDAwNTUzMCAwMDAwMCBuDQowMDAwMDA2MDQ5IDAwMDAwIG4NCjAwMDAwMDY1ODcgMDAwMDAgbg0KMDAwMDAwNjk4MyAwMDAwMCBuDQowMDAwMDA5NjkwIDAwMDAwIG4NCjAwMDAwMTYzMjUgMDAwMDAgbg0KMDAwMDAxNjU0NyAwMDAwMCBuDQowMDAwMDE3MDg3IDAwMDAwIG4NCjAwMDAwMTczMDYgMDAwMDAgbg0KMDAwMDAxNzYwMCAwMDAwMCBuDQowMDAwMDE5NTcxIDAwMDAwIG4NCjAwMDAwMTk3OTUgMDAwMDAgbg0KMDAwMDAyMDE3MiAwMDAwMCBuDQowMDAwMDMwNTAxIDAwMDAwIG4NCjAwMDAwMzA3MzMgMDAwMDAgbg0KMDAwMDAwMDg5NiAwMDAwMCBuDQp0cmFpbGVyDQo8PC9TaXplIDM1L1Jvb3QgNiAwIFIvSW5mbyA0IDAgUi9JRFs8OTNEREQ1RjRBQjk1NTU2NTVFMUFFQkU3Mjc4OTFGNzQ+PDUyRUNGNjUzRTlDQTM4NDNBMEI2MTY0ODI1RkZENjJDPl0vUHJldiA0MjQ4MT4+DQpzdGFydHhyZWYNCjANCiUlRU9GDQogICAgICAgICAgICAgICAgDQozNCAwIG9iag08PC9GaWx0ZXIvRmxhdGVEZWNvZGUvSSAxMTYvTGVuZ3RoIDEwNC9TIDQwPj5zdHJlYW0NCmjeYmBgkGZgYN7DAASTHjGgAmYgZmHgWIAqKg3FDAzKDHxMFuwPghsKmWZIBDAwHWSPkN3Q6/iEfYJ8QZRXQboC94Y6hx0sPJUM+o5hC27whJ88ADWDhYFhSRiQZgTiRwABBgBLlxXzDQplbmRzdHJlYW0NZW5kb2JqDTYgMCBvYmoNPDwvTWV0YWRhdGEgMiAwIFIvUGFnZXMgMSAwIFIvVHlwZS9DYXRhbG9nPj4NZW5kb2JqDTcgMCBvYmoNPDwvQ29udGVudHNbMTQgMCBSIDE1IDAgUiAxNiAwIFIgMTcgMCBSIDE4IDAgUiAxOSAwIFIgMjAgMCBSIDIxIDAgUl0vQ3JvcEJveFswIDAgNjEyIDc5Ml0vTWVkaWFCb3hbMCAwIDYxMiA3OTJdL1BhcmVudCAxIDAgUi9SZXNvdXJjZXMgOCAwIFIvUm90YXRlIDAvVHlwZS9QYWdlPj4NZW5kb2JqDTggMCBvYmoNPDwvQ29sb3JTcGFjZTw8L0NzMSAxMyAwIFI+Pi9Gb250PDwvVFQxIDkgMCBSL1RUMyAxMCAwIFIvVFQ1IDExIDAgUi9UVDYgMTIgMCBSPj4vUHJvY1NldFsvUERGL1RleHQvSW1hZ2VCL0ltYWdlQy9JbWFnZUldL1hPYmplY3Q8PC9JbTEgMzMgMCBSPj4+Pg1lbmRvYmoNOSAwIG9iag08PC9CYXNlRm9udC9aUFFQU0ErVHJlYnVjaGV0TVMvRW5jb2RpbmcvTWFjUm9tYW5FbmNvZGluZy9GaXJzdENoYXIgMzIvRm9udERlc2NyaXB0b3IgMjQgMCBSL0xhc3RDaGFyIDExOC9TdWJ0eXBlL1RydWVUeXBlL1R5cGUvRm9udC9XaWR0aHNbMzAxIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgNTk4IDYxMyAwIDAgMCAwIDI3OCAwIDAgNTA2IDAgMCAwIDAgMCAwIDAgMCAwIDAgODUyIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDU1NyA1NDUgMzcwIDAgMCAyODUgMCAwIDI5NSA4MzAgNTQ2IDUzNyA1NTcgMCAzODkgNDA1IDAgNTQ2IDQ5MF0+Pg1lbmRvYmoNMTAgMCBvYmoNPDwvQmFzZUZvbnQvTVVLRlJOK0NhbGlicmkvRmlyc3RDaGFyIDMzL0ZvbnREZXNjcmlwdG9yIDI2IDAgUi9MYXN0Q2hhciAzMy9TdWJ0eXBlL1RydWVUeXBlL1RvVW5pY29kZSAyNyAwIFIvVHlwZS9Gb250L1dpZHRoc1syMjZdPj4NZW5kb2JqDTExIDAgb2JqDTw8L0Jhc2VGb250L0hGQU1aRitDYWxpYnJpLUJvbGQvRmlyc3RDaGFyIDMzL0ZvbnREZXNjcmlwdG9yIDI5IDAgUi9MYXN0Q2hhciA0NS9TdWJ0eXBlL1RydWVUeXBlL1RvVW5pY29kZSAzMCAwIFIvVHlwZS9Gb250L1dpZHRoc1syMjYgNjA2IDQ3NCAzNTUgNTAzIDUzNyA0OTQgNTM3IDM5OSAyNDYgMjc2IDQzMCA1MDddPj4NZW5kb2JqDTEyIDAgb2JqDTw8L0Jhc2VGb250L1VHSkVDSCtIZWx2ZXRpY2EvRW5jb2RpbmcvTWFjUm9tYW5FbmNvZGluZy9GaXJzdENoYXIgMzIvRm9udERlc2NyaXB0b3IgMzIgMCBSL0xhc3RDaGFyIDEyMi9TdWJ0eXBlL1RydWVUeXBlL1R5cGUvRm9udC9XaWR0aHNbMjc4IDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAyNzggMzMzIDI3OCAwIDAgMCA1NTYgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDY2NyA2NjcgNzIyIDcyMiAwIDAgMCAwIDI3OCAwIDY2NyA1NTYgMCA3MjIgNzc4IDY2NyAwIDcyMiAwIDYxMSA3MjIgMCAwIDY2NyAwIDAgMCAwIDAgMCAwIDAgNTU2IDU1NiA1MDAgNTU2IDU1NiAyNzggNTU2IDU1NiAyMjIgMCA1MDAgMjIyIDgzMyA1NTYgNTU2IDU1NiAwIDMzMyA1MDAgMjc4IDU1NiA1MDAgNzIyIDUwMCA1MDAgNTAwXT4+DWVuZG9iag0xMyAwIG9iag1bL0lDQ0Jhc2VkIDIyIDAgUl0NZW5kb2JqDTE0IDAgb2JqDTw8L0ZpbHRlci9GbGF0ZURlY29kZS9MZW5ndGggNjcwPj5z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ACiiigAooooAKKKKACiiigApaKKAP//ZDQplbmRzdHJlYW0NZW5kb2JqDTEgMCBvYmoNPDwvQ291bnQgMS9LaWRzWzcgMCBSXS9UeXBlL1BhZ2VzPj4NZW5kb2JqDTIgMCBvYmoNPDwvTGVuZ3RoIDMzNjYvU3VidHlwZS9YTUwvVHlwZS9NZXRhZGF0YT4+c3RyZWFtDQo8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzAxNiA5MS4xNjM2MTYsIDIwMTgvMTAvMjktMTY6NTg6NDkgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6cGRmPSJodHRwOi8vbnMuYWRvYmUuY29tL3BkZi8xLjMvIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICAgICAgICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIj4KICAgICAgICAgPHhtcDpDcmVhdGVEYXRlPjIwMTgtMDMtMjFUMTM6NTg6MDdaPC94bXA6Q3JlYXRlRGF0ZT4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5Xb3JkPC94bXA6Q3JlYXRvclRvb2w+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDE5LTA1LTI2VDExOjE3OjE3LTA3OjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcDpNZXRhZGF0YURhdGU+MjAxOS0wNS0yNlQxMToxNzoxNy0wNzowMDwveG1wOk1ldGFkYXRhRGF0ZT4KICAgICAgICAgPHBkZjpLZXl3b3Jkcy8+CiAgICAgICAgIDxwZGY6UHJvZHVjZXI+TWFjIE9TIFggMTAuMTEuNiBRdWFydHogUERGQ29udGV4dDwvcGRmOlByb2R1Y2VyPgogICAgICAgICA8ZGM6Zm9ybWF0PmFwcGxpY2F0aW9uL3BkZjwvZGM6Zm9ybWF0PgogICAgICAgICA8ZGM6dGl0bGU+CiAgICAgICAgICAgIDxyZGY6QWx0PgogICAgICAgICAgICAgICA8cmRmOmxpIHhtbDpsYW5nPSJ4LWRlZmF1bHQiPk1pY3Jvc29mdCBXb3JkIC0gV29ybGRfV2lkZV9Db3JwX2xvcmVtLmRvY3g8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6QWx0PgogICAgICAgICA8L2RjOnRpdGxlPgogICAgICAgICA8eG1wTU06RG9jdW1lbnRJRD51dWlkOjJlNjBiMTYyLTY4MmQtNGZjOS1hYjFjLTcwMTQ0OTllMGQ0OTwveG1wTU06RG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+dXVpZDplYmRjZjYzOS02NWNjLTQ0YTgtODEyMi02ZDA2YWFjNzI3MDI8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz4NCmVuZHN0cmVhbQ1lbmRvYmoNMyAwIG9iag1bXQ1lbmRvYmoNNCAwIG9iag08PC9BQVBMOktleXdvcmRzIDMgMCBSL0NyZWF0aW9uRGF0ZShEOjIwMTgwMzIxMTM1ODA3WikvQ3JlYXRvcihXb3JkKS9LZXl3b3JkcygpL01vZERhdGUoRDoyMDE5MDUyNjExMTcxNy0wNycwMCcpL1Byb2R1Y2VyKE1hYyBPUyBYIDEwLjExLjYgUXVhcnR6IFBERkNvbnRleHQpL1RpdGxlKE1pY3Jvc29mdCBXb3JkIC0gV29ybGRfV2lkZV9Db3JwX2xvcmVtLmRvY3gpPj4NZW5kb2JqDXhyZWYNCjAgNQ0KMDAwMDAwMDAwMCA2NTUzNSBmDQowMDAwMDM4NzQzIDAwMDAwIG4NCjAwMDAwMzg3OTQgMDAwMDAgbg0KMDAwMDA0MjIzNyAwMDAwMCBuDQowMDAwMDQyMjU1IDAwMDAwIG4NCnRyYWlsZXINCjw8L1NpemUgNS9JRFs8OTNEREQ1RjRBQjk1NTU2NTVFMUFFQkU3Mjc4OTFGNzQ+PDUyRUNGNjUzRTlDQTM4NDNBMEI2MTY0ODI1RkZENjJDPl0+Pg0Kc3RhcnR4cmVmDQoxMTYNCiUlRU9GDQo=", - document_id="1", # a label used to reference the doc - file_extension="pdf", # many different document types are accepted - name="Lorem" # can be different from actual file name - ) - envelope_definition.documents = [document1] - envelope_definition.status = args["envelope_args"]["status"] - signer1 = Signer( - email= args["envelope_args"]["signer_email"], # represents your {signer_email} - name= args["envelope_args"]["signer_name"], # represents your {signer_name} - phone_authentication= { "senderProvidedNumbers": [phoneNumber]}, - id_check_configuration_name="Phone Auth $", - require_id_lookup="true", - recipient_id="1", - routing_order="1" - ) - # Create your signature tab - sign_here1 = SignHere( - name="SignHereTab", - x_position="75", - y_position="572", - tab_label="SignHereTab", - page_number="1", - document_id="1", - # A 1- to 8-digit integer or 32-character GUID to match recipient IDs on your own systems. - # This value is referenced in the Tabs element below to assign tabs on a per-recipient basis. - recipient_id="1" # represents your {RECIPIENT_ID} - ) - - # Add the tabs model (including the sign_here tabs) to the signer - # The Tabs object wants arrays of the different field/tab types - signer1.tabs = Tabs(sign_here_tabs=[sign_here1]) - - # Tabs are set per recipient - envelope_definition.recipients = Recipients(signers=[signer1]) - # Step 4: Call the eSignature REST API - envelope_api = EnvelopesApi(api_client) - results = envelope_api.create_envelope(args["account_id"], envelope_definition=envelope_definition) - envelope_id = results.envelope_id - app.logger.info(f"Envelope was created. EnvelopeId {envelope_id} ") - - return render_template("example_done.html", - title="Envelope sent", - h1="Envelope sent", - message=f"""The envelope has been created and sent!
- Envelope ID {envelope_id}.""" - ) - - except ApiException as err: - error_body_json = err and hasattr(err, "body") and err.body - # we can pull the DocuSign error code and message from the response body - error_body = json.loads(error_body_json) - error_code = error_body and "errorCode" in error_body and error_body["errorCode"] - error_message = error_body and "message" in error_body and error_body["message"] - # In production, may want to provide customized error messages and - # remediation advice to the user. - return render_template("error.html", - err=err, - error_code=error_code, - error_message=error_message - ) - - - - else: - flash("Sorry, you need to re-authenticate.") - # We could store the parameters of the requested operation - # so it could be restarted automatically. - # But since it should be rare to have a token issue here, - # we'll make the user re-enter the form data after - # authentication. - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - - - -def get_controller(): - """responds with the form for the example""" - - if views.ds_token_ok(): - return render_template("eg021_phone_authentication.html", - title="Phone recipient authentication", - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - signer_name=ds_config.DS_CONFIG["signer_name"], - signer_email=ds_config.DS_CONFIG["signer_email"] - ) - else: - # Save the current operation so it will be resumed after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - - diff --git a/app/eg022_kba_authentication.py b/app/eg022_kba_authentication.py deleted file mode 100644 index fc8f243b..00000000 --- a/app/eg022_kba_authentication.py +++ /dev/null @@ -1,158 +0,0 @@ -""" Example 022: Knowledge Based authentication""" - -from flask import render_template, url_for, redirect, session, flash, request -from os import path -from app import app, ds_config, views -import base64 -import re -import json -from docusign_esign import * -from docusign_esign.client.api_exception import ApiException - -eg = "eg022" # reference (and url) for this example -demo_docs_path = path.abspath(path.join(path.dirname(path.realpath(__file__)), "static/demo_documents")) - - -def controller(): - """Controller router using the HTTP method""" - if request.method == "GET": - return get_controller() - elif request.method == "POST": - return create_controller() - else: - return render_template("404.html"), 404 - - -def create_controller(): - """ - 1. Check the token - 2. Call the worker method - """ - minimum_buffer_min = 3 - if views.ds_token_ok(minimum_buffer_min): - - # More data validation would be a good idea here - # Strip anything other than characters listed - pattern = re.compile("([^\w \-\@\.\,])+") - signer_email = pattern.sub("", request.form.get("signer_email")) - signer_name = pattern.sub("", request.form.get("signer_name")) - envelope_args = { - "signer_email": signer_email, - "signer_name": signer_name, - "status": "sent", - } - args = { - - # Step 1: Obtain your OAuth token - "account_id": session["ds_account_id"], # represents your {ACCOUNT_ID} - "base_path": session["ds_base_path"], - "ds_access_token": session["ds_access_token"], # represnts your {ACCESS_TOKEN} - "envelope_args": envelope_args - } - try: - - # Step 2: Construct your API headers - api_client = ApiClient() - api_client.host = args["base_path"] - api_client.set_default_header("Authorization", "Bearer " + args["ds_access_token"]) - - # Step 3: Construct your envelope JSON body - envelope_definition = EnvelopeDefinition( - email_subject="Please sign this document set" - ) - # Add a Document - document1 = Document( # create the DocuSign document object - document_base64="JVBERi0xLjMNJeLjz9MNCjUgMCBvYmoNPDwvTGluZWFyaXplZCAxL0wgNDI3MTAvTyA3L0UgMzg3NDMvTiAxL1QgNDI0OTEvSCBbIDg5NiAxODVdPj4NZW5kb2JqDSAgICAgICAgICAgICAgICAgICAgDQp4cmVmDQo1IDMwDQowMDAwMDAwMDE2IDAwMDAwIG4NCjAwMDAwMDEwODEgMDAwMDAgbg0KMDAwMDAwMTE0MSAwMDAwMCBuDQowMDAwMDAxMzE4IDAwMDAwIG4NCjAwMDAwMDE0NzkgMDAwMDAgbg0KMDAwMDAwMTg0OCAwMDAwMCBuDQowMDAwMDAxOTk2IDAwMDAwIG4NCjAwMDAwMDIxOTcgMDAwMDAgbg0KMDAwMDAwMjYyMSAwMDAwMCBuDQowMDAwMDAyNjU2IDAwMDAwIG4NCjAwMDAwMDMzOTYgMDAwMDAgbg0KMDAwMDAwMzkwMSAwMDAwMCBuDQowMDAwMDA0NDExIDAwMDAwIG4NCjAwMDAwMDUwMTEgMDAwMDAgbg0KMDAwMDAwNTUzMCAwMDAwMCBuDQowMDAwMDA2MDQ5IDAwMDAwIG4NCjAwMDAwMDY1ODcgMDAwMDAgbg0KMDAwMDAwNjk4MyAwMDAwMCBuDQowMDAwMDA5NjkwIDAwMDAwIG4NCjAwMDAwMTYzMjUgMDAwMDAgbg0KMDAwMDAxNjU0NyAwMDAwMCBuDQowMDAwMDE3MDg3IDAwMDAwIG4NCjAwMDAwMTczMDYgMDAwMDAgbg0KMDAwMDAxNzYwMCAwMDAwMCBuDQowMDAwMDE5NTcxIDAwMDAwIG4NCjAwMDAwMTk3OTUgMDAwMDAgbg0KMDAwMDAyMDE3MiAwMDAwMCBuDQowMDAwMDMwNTAxIDAwMDAwIG4NCjAwMDAwMzA3MzMgMDAwMDAgbg0KMDAwMDAwMDg5NiAwMDAwMCBuDQp0cmFpbGVyDQo8PC9TaXplIDM1L1Jvb3QgNiAwIFIvSW5mbyA0IDAgUi9JRFs8OTNEREQ1RjRBQjk1NTU2NTVFMUFFQkU3Mjc4OTFGNzQ+PDUyRUNGNjUzRTlDQTM4NDNBMEI2MTY0ODI1RkZENjJDPl0vUHJldiA0MjQ4MT4+DQpzdGFydHhyZWYNCjANCiUlRU9GDQogICAgICAgICAgICAgICAgDQozNCAwIG9iag08PC9GaWx0ZXIvRmxhdGVEZWNvZGUvSSAxMTYvTGVuZ3RoIDEwNC9TIDQwPj5zdHJlYW0NCmjeYmBgkGZgYN7DAASTHjGgAmYgZmHgWIAqKg3FDAzKDHxMFuwPghsKmWZIBDAwHWSPkN3Q6/iEfYJ8QZRXQboC94Y6hx0sPJUM+o5hC27whJ88ADWDhYFhSRiQZgTiRwABBgBLlxXzDQplbmRzdHJlYW0NZW5kb2JqDTYgMCBvYmoNPDwvTWV0YWRhdGEgMiAwIFIvUGFnZXMgMSAwIFIvVHlwZS9DYXRhbG9nPj4NZW5kb2JqDTcgMCBvYmoNPDwvQ29udGVudHNbMTQgMCBSIDE1IDAgUiAxNiAwIFIgMTcgMCBSIDE4IDAgUiAxOSAwIFIgMjAgMCBSIDIxIDAgUl0vQ3JvcEJveFswIDAgNjEyIDc5Ml0vTWVkaWFCb3hbMCAwIDYxMiA3OTJdL1BhcmVudCAxIDAgUi9SZXNvdXJjZXMgOCAwIFIvUm90YXRlIDAvVHlwZS9QYWdlPj4NZW5kb2JqDTggMCBvYmoNPDwvQ29sb3JTcGFjZTw8L0NzMSAxMyAwIFI+Pi9Gb250PDwvVFQxIDkgMCBSL1RUMyAxMCAwIFIvVFQ1IDExIDAgUi9UVDYgMTIgMCBSPj4vUHJvY1NldFsvUERGL1RleHQvSW1hZ2VCL0ltYWdlQy9JbWFnZUldL1hPYmplY3Q8PC9JbTEgMzMgMCBSPj4+Pg1lbmRvYmoNOSAwIG9iag08PC9CYXNlRm9udC9aUFFQU0ErVHJlYnVjaGV0TVMvRW5jb2RpbmcvTWFjUm9tYW5FbmNvZGluZy9GaXJzdENoYXIgMzIvRm9udERlc2NyaXB0b3IgMjQgMCBSL0xhc3RDaGFyIDExOC9TdWJ0eXBlL1RydWVUeXBlL1R5cGUvRm9udC9XaWR0aHNbMzAxIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgNTk4IDYxMyAwIDAgMCAwIDI3OCAwIDAgNTA2IDAgMCAwIDAgMCAwIDAgMCAwIDAgODUyIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDU1NyA1NDUgMzcwIDAgMCAyODUgMCAwIDI5NSA4MzAgNTQ2IDUzNyA1NTcgMCAzODkgNDA1IDAgNTQ2IDQ5MF0+Pg1lbmRvYmoNMTAgMCBvYmoNPDwvQmFzZUZvbnQvTVVLRlJOK0NhbGlicmkvRmlyc3RDaGFyIDMzL0ZvbnREZXNjcmlwdG9yIDI2IDAgUi9MYXN0Q2hhciAzMy9TdWJ0eXBlL1RydWVUeXBlL1RvVW5pY29kZSAyNyAwIFIvVHlwZS9Gb250L1dpZHRoc1syMjZdPj4NZW5kb2JqDTExIDAgb2JqDTw8L0Jhc2VGb250L0hGQU1aRitDYWxpYnJpLUJvbGQvRmlyc3RDaGFyIDMzL0ZvbnREZXNjcmlwdG9yIDI5IDAgUi9MYXN0Q2hhciA0NS9TdWJ0eXBlL1RydWVUeXBlL1RvVW5pY29kZSAzMCAwIFIvVHlwZS9Gb250L1dpZHRoc1syMjYgNjA2IDQ3NCAzNTUgNTAzIDUzNyA0OTQgNTM3IDM5OSAyNDYgMjc2IDQzMCA1MDddPj4NZW5kb2JqDTEyIDAgb2JqDTw8L0Jhc2VGb250L1VHSkVDSCtIZWx2ZXRpY2EvRW5jb2RpbmcvTWFjUm9tYW5FbmNvZGluZy9GaXJzdENoYXIgMzIvRm9udERlc2NyaXB0b3IgMzIgMCBSL0xhc3RDaGFyIDEyMi9TdWJ0eXBlL1RydWVUeXBlL1R5cGUvRm9udC9XaWR0aHNbMjc4IDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAyNzggMzMzIDI3OCAwIDAgMCA1NTYgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDY2NyA2NjcgNzIyIDcyMiAwIDAgMCAwIDI3OCAwIDY2NyA1NTYgMCA3MjIgNzc4IDY2NyAwIDcyMiAwIDYxMSA3MjIgMCAwIDY2NyAwIDAgMCAwIDAgMCAwIDAgNTU2IDU1NiA1MDAgNTU2IDU1NiAyNzggNTU2IDU1NiAyMjIgMCA1MDAgMjIyIDgzMyA1NTYgNTU2IDU1NiAwIDMzMyA1MDAgMjc4IDU1NiA1MDAgNzIyIDUwMCA1MDAgNTAwXT4+DWVuZG9iag0xMyAwIG9iag1bL0lDQ0Jhc2VkIDIyIDAgUl0NZW5kb2JqDTE0IDAgb2JqDTw8L0ZpbHRlci9GbGF0ZURlY29kZS9MZW5ndGggNjcwPj5zdHJlYW0NCkiJjFVNc9MwEL3rV2wpBRsaVd+SrxQOZbgw45kcCIeSpDSQr8Y0DP8eWbLl2LKdJhlrZa3e2327Up7gKzwBZeVPGgNaGTgsYQpbuLktKMwLIEAwZVJkhFmLayU0UVDM7T6CmSjXvUEVx1oSCirTWAg038CHHChxDmHMN3CT5xQo5A+QQAr5L/iUuzA6cJrgzBDZhiOYEEuRz8eBv0Ey3aXWTg7rBUxXiyXc2vl3yD9bNhSzcZZhySWr2MCzTRwdH6FDPo/DfjgRi4oZt5gtaCGdRzV4NF6hXYyoEkTWChtoRGFlkNI4Tz+gjtSjWjNMjVQRKjdnYK3QH1cpMEiOTm5vF+7pbV+GbRC/h54xggXn2tNXlW6RvrxtGBNYSSpaWE4gHaUS9czuwQULo8EKiqVUOiLg/DzBl6op3XO5SWHCHJ2d3aW2+sm+cJPnzVgInFGsiWk3QauhurVv9PLd23d8s6zqLEntgYeeKlSoEs62qbYHyRZC98JYVdCLYWRmMGenLSmGWlIGmV9dOhVfu+fV1Rs3Xrh2fOvsWeKG04VZaicoeecml6de1f73YyWhhmOpMt7EiwZ6uCdt6r59NTEK6/Lq8Kin3Ryfd+iqcD1LywwmNvaJH65Pr8DBTmDU4ExmbemHqdC5CmqKs/MwpSIodCmm3H16rZ6YSx6hcKZqHp+dH6r+UM0xTMur3dZ5WRvHYB3ua+tn5AUpEhwLSGKfVW2se3aB32VvlxYFSjbB6W9t7EIgv4uw80+1cxdh7muoQ1g7hlgWw7HcR29qAJT8qNeea6OoAwjexb9tFPE+uK9qqCJKvskvLD1GIgZZHyPpY32b2q2jpHax08HqgzoQQf0CdxO9q17UCkSNgsLSKiZzfyXwX4ABALkO5agNCmVuZHN0cmVhbQ1lbmRvYmoNMTUgMCBvYmoNPDwvRmlsdGVyL0ZsYXRlRGVjb2RlL0xlbmd0aCA0MzU+PnN0cmVhbQ0KSImUVT1zgkAQ7e9XbAlFLgiI2mYmTZpMZq7LpCD4RQyagOL47wPCLeTeQcxYuLO3t/fe27eqnuhR0Qt9kyf9kLz6Uwczn6aBL6cRJRk9KJpeT9ovoTK6VyqiCak1vZITu3RXXSPnC4L8oCMuSrY6WrmiCQqqUjO5IOfo0jXD1/gk4VTO3btUrFvpBitoUOijnKFA8aYJhHOpr83JWZt4CriTWyB+doEw6L/bwBt4Up0oTRqCa1hFLt7rYIepDchxdAUq3WN8YnjwIlC3oEL9D0iqY46CtXBKLDnZ3ngjNeLlSYXAFxYvk+lli3PRbkVq2i2FGpZetiUVyKhaKXKetQ/ZbTFc4n6JTaI2uHBL7tShPvfEaYpGZ9SUbDsawniMERXAOeH7Z5Ah54Eu4bUyBSSImjs1xhTdyRKh5ScY2uBO9TrhvuyBSDnmsTBYyDAMJv9x2vDW9zwkBiWLQSmL+DuWAX+IktvXxwvk7La/ApxJb9uL1KRcO7hFJQzh+3b57ZEMF5Kp82zzbAWz1K/KMb6+70svCsOW9d+jrAGqj0HxwtlcevNKPGFrQz8CDAB5QXlvDQplbmRzdHJlYW0NZW5kb2JqDTE2IDAgb2JqDTw8L0ZpbHRlci9GbGF0ZURlY29kZS9MZW5ndGggNDQwPj5zdHJlYW0NCkiJjFTJboMwEL37K+YIUuIQICzHRuqlt0o+VIp6oCVJqULSOpvy92WbMcqYFHHAmuX5zfOzZ0pFMAe1gRU4S124MPWkD84eF1tcQLUIIhmDc3JFEznYUtBEvjC1Hq7J2pRwPrBm96DriqE+xXdQL/Cs4BV+a8wQvPqrF/NkIZMgiiGME+kl4rOEpYJFk8efKmFG4ztTF9T3EFhagfnJaLDVAN/7oY5nV7QxfaG6S8b06HWgepqqMLJGrC3bmvQ88mOwnCIhEeWKaLfSlrP5oSSBEbtPdIuZz4KQY4gASq5bhrSQesG6qFZ2JWaLp9NdSIxy65pB572izoCCeyb2IYwCGSUwxi00wKZTqyaQpv1h7STbmpzJfps0baJXtGVq/XPb27YN+QStemW7aSIw6Ync9mccco9QNMrVVnRPV5e2myGGdGIXxGDzmiMmhSF1ObDtjHfzYaXwFgintKjPDUURfqcLROION9BvZlmwefjlEGPNNfy61uYOU+mFo55CfWbkuYVu9UnOvUCmNjYZk/2hdwmJPXKPvNto1jVmFjDz9nJe5c5GbFhBP6rkS6OejPAnwADWhXX1DQplbmRzdHJlYW0NZW5kb2JqDTE3IDAgb2JqDTw8L0ZpbHRlci9GbGF0ZURlY29kZS9MZW5ndGggNTMwPj5zdHJlYW0NCkiJjFVNU4MwEL3nV+yRHhqhobVc/bh4cZzBk+OhUmqroDXYOvrrBWSXbTYwDgcyydu3Xy+bixTmITRf90tLOEvTBUSQbuABgnwC01DPIFjjAupFFBqdQJAVf5sqeMfTwxi+Ilgh8J8TaJl2uEHYN1zoFnLCeI9nFQVq6VgFz7iZC7IevzoNwZ+zopxlXMJOnLCIdzXVKYgdWqJYsUo9QnoD1yncwUdDF7eNahdzs9BhaBKI40SHscpKuBjtaDCdQPriJzufQWxivVxCVqpxmoc+NepXmbOE2v8XqmMn2l642K1I/Cj4ygPWrnALTB72woOgQaVJzWZbQUOg3WBzq1od7WLTbZCNBXcHIfivto4jFVjpiWSsx8RgTKijmfl3E5uCjGkhTGqXHkUpVwqXond7UTUs1srpDxPHD+/UNDLnOvJNAXZYiOJ9eVBSWrKwbs3Z6GBM2DV7GMyPoYXO2BnZ0YgZHh6E3XQ6+0YinhMjf/INEWexx6tEGPvKKI7S9CAiJbzqWdbDia09oZJdJub/UdT2WxQXvMV9wh7mLoOolm96V7jTvzdq+NbVN8UksZ4vfFdFTM3broX9cLN2RAttmFHcPA2f7qDMsYN/oDmzJ0ap7+F7OEJz/F+Ict6LoMnJiGCYtTrdkMNAypQ8ZIVDqILsVQRk5StARSNQxZLcdDrtDUtyedWMZ/gVYAC6zcvPDQplbmRzdHJlYW0NZW5kb2JqDTE4IDAgb2JqDTw8L0ZpbHRlci9GbGF0ZURlY29kZS9MZW5ndGggNDQ5Pj5zdHJlYW0NCkiJjFRNU4MwEL3nV+yRHozQBGyvznjx4MdMbo4HbKFFSa2lyvjvpZQsIUuww4Gd3c3Le/sRdQ93Cp7hC0I+lxCevpMho5DHYiFALCWPE1hpuFUQt/Hux5SGa6USiEDl8AJBNoOr5jAEP48zaI298VS880DjiaIYggcTSs8GC3bG82mMY3cIgVdbY9Hksvews7Exnt/m1ldQHqk3cxCLkEeXiSwMqNZI61AVhA4SbBWHSy6YkTrg6qZvyLm+Dqg+MxJNkrSIIVTeHcNLDjp1S1sYpAni1sUFoYmh2iCldtkNQk0QSmJYYlYl4VVll1WGglEsNPYjmJOVyGdsiLD6RoK0A6SZDZvKmljmkC7HSufnI+0CuFj/znwyOvCgNBsM/Jt3LY/ufGnoPKgDx2tHGsSn+EVC8lAIL8vhWp60qne/UhnyBuoSsU+oZY0Nxnmu+qXxNJq1rZE3y7ER65+LkS2k74G7TYM1aa/oX8PURwhvtZhpmp36jjXZmZPNkDUi0nr0u7AmiFaJ2PChwkhN6nH4yN3ZwlhtiFUIQEYTI0gDu4nicdJxMVFFtSVAqeWZXrR5zJMFGx8/gD8BBgANznUrDQplbmRzdHJlYW0NZW5kb2JqDTE5IDAgb2JqDTw8L0ZpbHRlci9GbGF0ZURlY29kZS9MZW5ndGggNDQ5Pj5zdHJlYW0NCkiJjFW5csIwEO31FVvaBQLLB9BmJk1SZUYdk8IxZ/BBbELC38eAd6V4bcNQeEfSHu/t22WsdQQe6DUswFm7MJEKnDJ2YXS1shVaP2gUrrgZ5V4278EF5ckZOLo5IP/9Dq0cjQ0ajZdwKkpi8qZo0V1VUZo9HZ7p7HA7EsahRDgFnWQxKydZteoBZ4eh6NESDYKTUKQjy2IKLTASloLf1Ar4DvoFnjW8wdflMoDJ5Xcx/LmSYTiPwFehjGaQZOJJQ3i9x4/OYGz3MGfEUTWtSoW5Mbz8EsKMrKo/1JaxTk8IYnFXAiZejOKi5BTGEls/Y1MFvudJ9RhXrytG1oF1OkeIdtWjaSS9Wn6E7Zu6b26NSldtV9HBinFssfu/l82bDzMzosVUyl/T9G4ZvvakCcvtwAroGqreGbdDxbyoJYvO22AI4zN46qDlPq1ikNbq2F5h5XFIcEFYu/nKb2QnkgyGZeeMXNCfPcGiifTULHg42MISHqPM3t45ioTBSxmt+OTMBWlvjsEhVPNpjQTuAxAdKii7O3zbu7zBJxJuuWGxuvd8z16iJCX9yVXVEFJV7+XAi4bhiq7mw58AAwAldHafDQplbmRzdHJlYW0NZW5kb2JqDTIwIDAgb2JqDTw8L0ZpbHRlci9GbGF0ZURlY29kZS9MZW5ndGggNDY4Pj5zdHJlYW0NCkiJlFRNc4IwEL3nV+wRDqYQwodXbS+9OZNbxwNF7FgLWqg69tc3KAkhiwwdZ0zIbt6+3X2bFXyDRxkHr/k1G8bmlPvMBzaPqc8gK2AhILzZ24WIAp6EiMAHsYU3cI4uzORlcL7UJlWbHxdu67ZdD8pQFXA7Is5OHZX2pVwdfOhbCFjfPrjEgqEurEG8wouAFcozCELKoyiZnKcj+YrPYbCYAYt9yvkQDIiC9Mq17CjbdcMFfLecyVgNzjkCukras4RJcgboRmEVuPj6pFbwOKAOo8FJd1YcUX4XtdFOVW5x6WEVOmK1z+3gtZHReYcKZliPShK6GidDUY+1wZnUBkviXlOJ3VRTG7PH2uDNQDGedGBEKsSjnucxEBnSigmblpsRYC6BeRS0wGRYelNZhoHEaOZ/Itj42F+NZpao+aqTFySUal/T1ghjHWoGLoxoMonns2ZVIqLdAGQpUktviJiRcDXGzQ89GkVB9A+GOv+sm7UaTXepCI7Oo/xfMPW97Bu0/OtcYVX13aQx1UuNsKu7I34xJFPS54UrODju5vOWYmqWR44C5zbnnV0YqSZiYGkldGUuHr+mmZ0PQYro3p/6hDLRztrp12gUWQP8CTAADON86g0KZW5kc3RyZWFtDWVuZG9iag0yMSAwIG9iag08PC9GaWx0ZXIvRmxhdGVEZWNvZGUvTGVuZ3RoIDMyNj4+c3RyZWFtDQpIiYySP0/DMBDFd3+Kx9YMuLFrO54RDGXhjywxIIbKBNoqaSFNi/j2xEnOgbZIUSLl7t3553ex3S1uHB7wiZRLhTQ8IcgkpJLcKPgSVw66rdDHlZg6ZyDg3vCMid8n7LJZh8muTtAG2wSdUuYUVehrr6SsYik2+TphbRCV2E3LfWRvKDgMm7xT1Ets6PL5MerrxAn5X5IQN4szUgtBBvOH2FwMq47AK7J1OsUiDvFN7EeS7nph3n95ghe4cHrs9PRmNuVWSD36DMMsbs3aq9CVd/7snRCaa0k82/HsL57ueRcJc+t/r5awlls7ChNtncMYydVoTPerAiibcWMbQIoSQiuuTZsUlBiluVVN3jXGdIknbCBkeHXjPzOWVXkrTu/zyucf9X5RoFo1e4QT6KxkigvzF9U4ns5LgestgqMfAQYAXae4kA0KZW5kc3RyZWFtDWVuZG9iag0yMiAwIG9iag08PC9BbHRlcm5hdGUvRGV2aWNlUkdCL0ZpbHRlci9GbGF0ZURlY29kZS9MZW5ndGggMjYxMi9OIDM+PnN0cmVhbQ0KeAGdlndUU9kWh8+9N73QEiIgJfQaegkg0jtIFQRRiUmAUAKGhCZ2RAVGFBEpVmRUwAFHhyJjRRQLg4Ji1wnyEFDGwVFEReXdjGsJ7601896a/cdZ39nnt9fZZ+9917oAUPyCBMJ0WAGANKFYFO7rwVwSE8vE9wIYEAEOWAHA4WZmBEf4RALU/L09mZmoSMaz9u4ugGS72yy/UCZz1v9/kSI3QyQGAApF1TY8fiYX5QKUU7PFGTL/BMr0lSkyhjEyFqEJoqwi48SvbPan5iu7yZiXJuShGlnOGbw0noy7UN6aJeGjjAShXJgl4GejfAdlvVRJmgDl9yjT0/icTAAwFJlfzOcmoWyJMkUUGe6J8gIACJTEObxyDov5OWieAHimZ+SKBIlJYqYR15hp5ejIZvrxs1P5YjErlMNN4Yh4TM/0tAyOMBeAr2+WRQElWW2ZaJHtrRzt7VnW5mj5v9nfHn5T/T3IevtV8Sbsz55BjJ5Z32zsrC+9FgD2JFqbHbO+lVUAtG0GQOXhrE/vIADyBQC03pzzHoZsXpLE4gwnC4vs7GxzAZ9rLivoN/ufgm/Kv4Y595nL7vtWO6YXP4EjSRUzZUXlpqemS0TMzAwOl89k/fcQ/+PAOWnNycMsnJ/AF/GF6FVR6JQJhIlou4U8gViQLmQKhH/V4X8YNicHGX6daxRodV8AfYU5ULhJB8hvPQBDIwMkbj96An3rWxAxCsi+vGitka9zjzJ6/uf6Hwtcim7hTEEiU+b2DI9kciWiLBmj34RswQISkAd0oAo0gS4wAixgDRyAM3AD3iAAhIBIEAOWAy5IAmlABLJBPtgACkEx2AF2g2pwANSBetAEToI2cAZcBFfADXALDIBHQAqGwUswAd6BaQiC8BAVokGqkBakD5lC1hAbWgh5Q0FQOBQDxUOJkBCSQPnQJqgYKoOqoUNQPfQjdBq6CF2D+qAH0CA0Bv0BfYQRmALTYQ3YALaA2bA7HAhHwsvgRHgVnAcXwNvhSrgWPg63whfhG/AALIVfwpMIQMgIA9FGWAgb8URCkFgkAREha5EipAKpRZqQDqQbuY1IkXHkAwaHoWGYGBbGGeOHWYzhYlZh1mJKMNWYY5hWTBfmNmYQM4H5gqVi1bGmWCesP3YJNhGbjS3EVmCPYFuwl7ED2GHsOxwOx8AZ4hxwfrgYXDJuNa4Etw/XjLuA68MN4SbxeLwq3hTvgg/Bc/BifCG+Cn8cfx7fjx/GvyeQCVoEa4IPIZYgJGwkVBAaCOcI/YQRwjRRgahPdCKGEHnEXGIpsY7YQbxJHCZOkxRJhiQXUiQpmbSBVElqIl0mPSa9IZPJOmRHchhZQF5PriSfIF8lD5I/UJQoJhRPShxFQtlOOUq5QHlAeUOlUg2obtRYqpi6nVpPvUR9Sn0vR5Mzl/OX48mtk6uRa5Xrl3slT5TXl3eXXy6fJ18hf0r+pvy4AlHBQMFTgaOwVqFG4bTCPYVJRZqilWKIYppiiWKD4jXFUSW8koGStxJPqUDpsNIlpSEaQtOledK4tE20Otpl2jAdRzek+9OT6cX0H+i99AllJWVb5SjlHOUa5bPKUgbCMGD4M1IZpYyTjLuMj/M05rnP48/bNq9pXv+8KZX5Km4qfJUilWaVAZWPqkxVb9UU1Z2qbapP1DBqJmphatlq+9Uuq43Pp893ns+dXzT/5PyH6rC6iXq4+mr1w+o96pMamhq+GhkaVRqXNMY1GZpumsma5ZrnNMe0aFoLtQRa5VrntV4wlZnuzFRmJbOLOaGtru2nLdE+pN2rPa1jqLNYZ6NOs84TXZIuWzdBt1y3U3dCT0svWC9fr1HvoT5Rn62fpL9Hv1t/ysDQINpgi0GbwaihiqG/YZ5ho+FjI6qRq9Eqo1qjO8Y4Y7ZxivE+41smsImdSZJJjclNU9jU3lRgus+0zwxr5mgmNKs1u8eisNxZWaxG1qA5wzzIfKN5m/krCz2LWIudFt0WXyztLFMt6ywfWSlZBVhttOqw+sPaxJprXWN9x4Zq42Ozzqbd5rWtqS3fdr/tfTuaXbDdFrtOu8/2DvYi+yb7MQc9h3iHvQ732HR2KLuEfdUR6+jhuM7xjOMHJ3snsdNJp9+dWc4pzg3OowsMF/AX1C0YctFx4bgccpEuZC6MX3hwodRV25XjWuv6zE3Xjed2xG3E3dg92f24+ysPSw+RR4vHlKeT5xrPC16Il69XkVevt5L3Yu9q76c+Oj6JPo0+E752vqt9L/hh/QL9dvrd89fw5/rX+08EOASsCegKpARGBFYHPgsyCRIFdQTDwQHBu4IfL9JfJFzUFgJC/EN2hTwJNQxdFfpzGC4sNKwm7Hm4VXh+eHcELWJFREPEu0iPyNLIR4uNFksWd0bJR8VF1UdNRXtFl0VLl1gsWbPkRoxajCCmPRYfGxV7JHZyqffS3UuH4+ziCuPuLjNclrPs2nK15anLz66QX8FZcSoeGx8d3xD/iRPCqeVMrvRfuXflBNeTu4f7kufGK+eN8V34ZfyRBJeEsoTRRJfEXYljSa5JFUnjAk9BteB1sl/ygeSplJCUoykzqdGpzWmEtPi000IlYYqwK10zPSe9L8M0ozBDuspp1e5VE6JA0ZFMKHNZZruYjv5M9UiMJJslg1kLs2qy3mdHZZ/KUcwR5vTkmuRuyx3J88n7fjVmNXd1Z752/ob8wTXuaw6thdauXNu5Tnddwbrh9b7rj20gbUjZ8MtGy41lG99uit7UUaBRsL5gaLPv5sZCuUJR4b0tzlsObMVsFWzt3WazrWrblyJe0fViy+KK4k8l3JLr31l9V/ndzPaE7b2l9qX7d+B2CHfc3em681iZYlle2dCu4F2t5czyovK3u1fsvlZhW3FgD2mPZI+0MqiyvUqvakfVp+qk6oEaj5rmvep7t+2d2sfb17/fbX/TAY0DxQc+HhQcvH/I91BrrUFtxWHc4azDz+ui6rq/Z39ff0TtSPGRz0eFR6XHwo911TvU1zeoN5Q2wo2SxrHjccdv/eD1Q3sTq+lQM6O5+AQ4ITnx4sf4H++eDDzZeYp9qukn/Z/2ttBailqh1tzWibakNml7THvf6YDTnR3OHS0/m/989Iz2mZqzymdLz5HOFZybOZ93fvJCxoXxi4kXhzpXdD66tOTSna6wrt7LgZevXvG5cqnbvfv8VZerZ645XTt9nX297Yb9jdYeu56WX+x+aem172296XCz/ZbjrY6+BX3n+l37L972un3ljv+dGwOLBvruLr57/17cPel93v3RB6kPXj/Mejj9aP1j7OOiJwpPKp6qP6391fjXZqm99Oyg12DPs4hnj4a4Qy//lfmvT8MFz6nPK0a0RupHrUfPjPmM3Xqx9MXwy4yX0+OFvyn+tveV0auffnf7vWdiycTwa9HrmT9K3qi+OfrW9m3nZOjk03dp76anit6rvj/2gf2h+2P0x5Hp7E/4T5WfjT93fAn88ngmbWbm3/eE8/sNCmVuZHN0cmVhbQ1lbmRvYmoNMjMgMCBvYmoNPDwvRmlsdGVyL0ZsYXRlRGVjb2RlL0xlbmd0aCA2NTUwL0xlbmd0aDEgMTAzMTY+PnN0cmVhbQ0KeAG1Wgt4VNdxPufc5z60une12l0QQrushWSvsF4IAcHmgrSSYC2QBFgrjCwJEBKOsYUNQWnshtjOB5Ed5Kb5IImdOs3LbvKlXm2oEW6CnZchjnHsxHFcN23dlhrqIJf4I6mLpVX/OSthyNfH16S50tyZ87xzZubMzDnSnrv29jMv288U5mzb1TfE5OPbCPTctg/tieTKusmYGNoxNLArVzaHGdNGBm7/8I5cOb+FsfCnBvv7tufKbBJ4ySAqcmW+GPiawV17MI4en4pX2+13bptpz5+H8nW7+oZnvs9+gXLkjr5d/cB4ysvxumbozrv3yCIro/abhu7qn+nPU4yp38i1zb59jHHQAdbKDJZkGhPMYpVsE1ZygU9jvVy2a5g49HpRT/6KX7MiLBPPN/qOzSH83e99/UfTbVNnzSeMB1F0y/7UgHmN+7PLGDP/dbpteth84nILtdITcNwbn/kKd75iFzZ9eZx7MzUlfz7OC51MScmdJ6ujdwB2AW4HfBBwG2AnYBAwANgB6AdsB2wDbAX0AXoBPYBbAd2ALYBbAJsBXYAUoBNwM2ATYCNgA6AD0A5oA6wHrAO0Am4CJAFrAWsALYBmQBMgAWgEjPP6zJ0m0JLMHYTqMrsILc7cTqg280FCNZnbCFVndhKqygwSqswMELo+s4PQokw/oYrMdkLxzDZC12W2Ero200eoPNNLqCzTQ2hh5lZCpZluQtdkthCKZW4htCCzmVA000UokkkRKsl0EpqfuZlQcWYToXmZjYSKMhsIzc10EJqTaScUzrQRCmXWEwpm1hEqzLQSCmRuIlSQSRLyZ9YSsjNrCFmZFkL5mWZCvkwTobxMgpDXmW40ozs7q0tSgJsB7R3VJU2N1SUJwPp11SWtgMiRqiPOkbYjatVBnv8QH33gsQeefOCZB378gDY6+Njgk4NK786hnWL0Fj66mQ918tG2x9qebHum7cdt2mj7Y+1PtiujHY91PNmhrLxn/T2i7SO9Hxn6iDK0jg+N8qrR3tGhUYUd4vh1Dg0dEuxQ1SHnUNuhXhR0a8gZEr17eO/dfKiR5zaW3zad+vyVLwZ5/hdLvijCUHoA4APkAbwAD8ANcAFMgAHQARpABSgAAeAA5x6G9xthM/qLgBl91WdGf5ZnRl/xmtGfeszoT9xm9GWXGX3JNKM/Nszoi7oZPa2Z0RdUM/ojxYw+L8zoD7kZPcXMaMyXWOBNRN2JiJko0RPz1USxSMxjiblm2AyaAdNvWqbP9Jpu0zR1UzWFyczkuDHdkUybbbekxjg/1JX2J1ly4+rjjPPpj38y/js+d6/mxcl00YZU+nBxVzJdA4IVjwXZ6q5kBKVY+nD75lS6qrgrzhM7N6zmybbUmInWhi05HLSGbhyrr0/sjKTZxlTa6e1qHKtiQ9+sYVVszlB46G757NmTw1e8f0d+/y/D9sThsLQ3AcfZHEBArYTXZNNnAecJslvQ9j3GsogKylLQP4UP3QB8Cj7x//s5xU7j5zA7ip/cc4Kdws9D7AvsEdTTk6th7Gv4oWc7u5fdhx6H0WeWfoT95DKNelHFl/Aw/zJ/lTWJMK/g34Ubf5X9kv2Sv8w/yjfyAp7gg7yCfVLU8S5lpaaBPsruwKhb+Qv8BfU1dgdKr2LWHn4RbcPiJf6w8lG2X+xHC/H6lewXWQ07Dj5+78f8X/Ux+wnSBz2kjz/I8wfSh7NkYzK6dk1Lc1OisWH1KmfljTes+MDyZUvrl9RVXr+oonxh6TWxBSXhgG3l53ncLtPQNVURnFUkYk29kfTC3rS6MNbSsojKsT5U9F1R0ZuOoKrp6j7pCI3rQ9NVPR303PFbPZ1cT+dyT25FVrAViyoiiVgkfboxFhnnm9tToD/ZGOuKpCck3SppdaEs5KEQjWJEJBEebIykeW8kkW760OBIordxUQUf87gbYg397kUVbMztAekBlS6PDY3x8hu5JER5YvmYYGYefTatlCb6tqfb2lOJxqJotEvWsQY5V1pvSBtyrsjONHhmD0bGKp4deWjcYlt7497tse19W1JppQ+DRpTEyMiBtB1PXxtrTF/7R2fCEGB/uiLWmEjHY2As2XH5AzytlVqxyMivGZiPTZwH11fU9M3U6KXWrxk10hIviynN+2ZpBt7AIdYXjRIvD447bCsK6f3tqVw5wrYWZZhTGe9Ki15qeXa2pXATteyfbbk8vDcGySZiid6Z3w8NhtP7t0YWVUCz8rc0rZaiPZJWFvZu3TZIuK9/JNaIFUKW0gM3gnD6ZoSZGKuqRP++XixiJ4mhPZWujA2lA7HVOWmjApOUwren5JBcbSIdaEiz3m0zo9KVCYyFiSRGSDHEIM0Va08dZ7XTb4wtjhR9s5YtZl3ERzrYAKUsTIyktu9Il/QWbYd97oikiqJppwvi64ql+rtISzErfe0b+BweKFCOwtp+q/dsZyw7bZSakZQoUrpIW6iINOEVW70CDVZazxVJo6tXRFK8iM12w1dmehB11TwoKKUNLRgMjKENLUVRGLd8/geWinILABtp8zJPKpjQ3ucp953/lrVcb2Lo2kiiv/EKBq+aFAXJ4Mxs/zWfgmQxIwywYJI6W2gNiyoE6AiazbTAOmUVaTGMYN0WScX6Y10x2JDTliLlkKylfpMbYklEfantmT05YsaSG0aoNrY0V8UiI2vSDObkYCst9S/O1TbBiY2MNMUiTSO9I33j0/u3xiJWbGQsmRwZSsD5sLYUbGB8+ukHi9JND3Wlrd5Bvhy2OxJbs30ktiG1AgqAZbVd3qxp0bAxNcOQ/LK0GPSBB1k9FuMH28ccfnDD5tRxi7HIwY2pjOCioXd1V9cinJwYothO4B+yNvUcOyyWs+NqHjusv8zy1L0sqXazVeLXgBbWJrag/svsgNoDSLJVaD+gzGOHlRtA97B7lV/iNMlwHqKzFwOts3HgCOucqZHVv8dL4OQ2+9ApEmnIbPEqrF9VurJgMJO5ZEUue/GAyzzmY/mos5iNt58VzAwoZ+Wc8++ICnG/skm1tBbtW/oa/VnjBXOXq87tdt/jiXqe8a5Bb8H281uV/eqt4M9gC52g9qjyefVRg4VYPbFsPMpRZKxyamqCV/Z0A1VXFdhRuzRqR/crbGq/YFmGKdgUtEESbJs+q35Y+wkr5D1O5mwhZ4bJlU6Diz+2+B95eKHOlYPsM+wJCPgUe42dw2cNb8hb5q33qswb8Xr1pDekJ2yvZYmkrefn4x3xePC2vF68/Xl5eHt9Pj1ph6k31eAdrGdnmRCH8LXhfL4vwEW/i7uVzcptinJd/vJ8oXi8R/jj/Bg/yX/Oz3Kd8bNezk0WZgeZQiwc8GLB49MvOfm2rSdZwOsQ9vqU8elzR+l7IN45almSOHc0P18SF1AjqOmcU0rMKmsCrjC10VskXWt8roBFg+gtkgGFrYzHV07E4xYAj3Wafnu6u3u6a3q67VpQIPHb082tV2cz4gmQPd3PUQuguko2GwtjC4Qd8NfW1If0aITZFovWqDc09L74nX/9t2+/eOcdf5l9O/uP2acQ4kL/pt3/9abs0eylS9kffuqz3+B/xjfwFp4hC0B2qn4GmbLJ/LzOqQg4JOdwHm4nhtFq5h/MF/mJQWPYEKTGAwYvN7gRKCgQSWN8+i25fBAXHA9pyXDTaFkOkaYML+nIECS/GXp8+qKTTzoz3I0GD/EyrnASMQkJxPmjNDWIi3JqEJccD4mSq/QBlLNHaWYQb0u1gJikHhiNL8g53nNq6AO8IGwNWsOW0pm3L0+wTd5+716vstnmSpnGRaGXu70tXLjsFpWbqmArV1oTNbUAKX2S9RUaiMe7p16ZVUdc6mhCqqKbx6NR+5pakj+3g7U1ftuKLuDfzv4Nn7ue1/Idk881bPvJheyiedpxd/bB7POT5zTtveNu/gEextGPI/dm/GM4wSjsxHGmTT/rVErZarxc40pjmJWzpayFpdgg0wehEto9qoHtg+1WTjKDzb4iZQbijJQZiLdyMmNSZihfPEp6AfG24yHpsQhpRJZtEh3jJDo2j8SG2jeOyj0wPv2i43G5UNWsMpOTiLj1m8tSIKKne/ddE90wSR6vtWvt46e045easSpYlXIRq9LYnzhBUw2r4pT6mnpOfVdVmdLI1GYsQVqP/F5O6ajJKR3EjNIvLyCndNljhvOLOaUzRXJOVpUnuW/WlZYZZie49eb73FpvWhNgt7qKGI0WHj4lDoPZ946C27zp8/r3wa2X948JikrOPMMtFE3lLtXlEabQhNfj1nTDbQjhy7P9yypPn7b+jn4hEzy2P7SsuqpoTP89BjtLmnFWaFF4M0Rd7mvxDfsO+k75NLrDw5L4sMr3Md6CRjdqDENR9SbN0+Qdn37X2ep260lTww6o9zZ793nVpfD6w5riHWbasOod5nuZsldtVjnmYfe51PtMIVx7MZdhlpn1Zqf5uKkxc1AMC8HXgAv3ISZGTeOQyiAArxRAbqHWBP1I/4N40D2xbE5lmGq6u+lNjVPwUXEQENKcSpIUOS25pchvdbOe7q4uLcZ5DJtD/qqTK7Nfyj69MlvzEl/IVzfxG3n8pYDyzqRPOz3JlelJRZmCtSenJ5TzOPH72Bw+3wkWweHAjlvz5nhbm/N4XoR2Td6sRwLxm9wWyDOpH5XlFgBxJrcF8rxk+Cj/Exok8Y6zgywoLyCdVmDdAZ0v1Vt0wXSTHJA+l2bS59JO0mUc0mUc0hGZ9KTupbH63NJgXTARVK6zl9trbUVJKYOKCIY9GBO0aJZgh2JTtPBRld3hanFx4XMjgFx4ipoVjwub9BjtUZeRxyD0OH56IF0ZMa72ShDnVPwKrzS1wkJ85vHSOh2hoW4xq60JwdYVBAq90IrWLKlXrt/27R9e4pGTX735xImWez/7bd67CJF5/TYeufArvmkd/9WlImXJ7WfS2XuXRSg6rJo+r85TG9gctoBPOweukVJPhbgybvAR43PG14ynjeeN1w1ddGp80Bw2D5qfMZ8wtXJzqdlipsz3q8bNU+Zrppd1hvfB8S2gaGjKGGkijoOuoDWbUpamRbI0S9Y1z+ciFK4PN4cHwgfCR8KPh4+FT4ZdYfJQ5AZA/L10eSB+gZCMwJXTpyRekYECNWekholwPkgzh/n8dXzdAYsvtVqsFGKEalk0HUI0uLKknqx5pCArSFyBKWjYkhq2YgeMI8bjhsLKtHqtWevUVGF0XGcuN9eaitJpDpj7TMUwQ7jXG59+9ihNAoJYwYpQ49TJdXrntzXzTj4gUxKNcYeY53Bh9JbBjGupuXxuR0ByE6AISY1EONJ5B6T9BgaQ6qwNbA4ITSZIWofXEzBgNhNx2IpF3m+i215WKV31hPUz4O7dPd20g3e/H96wkSesv6POeDBu4vuXyd1kcpRz8DiPBkIIcMg2DNhX2cI6q34JLCxYGOALDN2ILi5bqOZNTg7cMvqlXW0Vt9z10POf/PyfP/yDf7nvj7PXfPTmDo9ob1kvtG/1p3o+URG57hNHprnrC6Mfu+f0Sr6zY92eu1s3wjetgtFNY5cXsi8fZyEIMB8aCZHcFpEo9/q5UrgUmyblUlwen9fwaK1Gq6e1xcuZV5D2vH6SJJzi20dpu4I459hkYN4Iyc8bJEWj9lcONjKoACnG6+owfR35oqPA687XkUfDpZM/W4GsNycJinQTENIK6znrue7nqqvgx3g8zuXWCoRqC2M2fupqF5NExPPLazp3a6+/fuLRR5//6oYebUXggf6i4i9M7lZGv3D6rfnYV22I+t/Rvs88yN3/5qkCqf4CLPMpYtL1EfiDi7NJ57vSsFFzSXowEP/hyERZsX3e1n02Z7ZKzs+W67d9tH64mNz6QZyXw0C8mXN81AMZM4UND4lgJoMen37VkbvANin42wV57Rbi7bOOFCi7R5c8wtm5LR3yQUZKLul9GyI54YTwnCUdkvRDcV4D44CtLKxbvESBeKI2T1YnEtVVicb6P+Xt2vcTVVSsbry0ApJ572U6QRyePi8MWIDJTh5nblgArc0NRuRGJ8K5jvRpuEPuMne9u9nd6R5w73Pjti2kl+mKLlpzaeXPucqQHF5OKN+eTSjfcZDzYJ/lEkqVJkO/F5wQmQaX2TsvIMlw7H68MYdT5PGAch3TTmqCu825Ju14Ve8wPDgzYrdhi1wlCpJELi2KxwvggwPB2sLI4RPb2rKjvFI99t7Om7fhGpizA4zpxfCvJXzcWRp1yNqjMgM35uF0Eh2MirJofbQzeiR6Mqqx1voSXmKRQEpMMvaSebSQEmIwRNZeIl1piUpGVCLVjLaJnJpL4Nx06pt1immhJWZrPTJ5OZshTceYS7MheZ+ZzZCzGXI2AyEQKTtmk74URNYJk3QMnaY1rFbDDtll9llbZWRakLW0MeRx9DHbcrmkVSJrk7YqJ0XHXMJHRE4ptpvWYZukFFm7QRqrDAt2JDSvbF79PIWHpaMMO/SRMAKHnmwOdyJG7AurLOzQB/GGfMLB/EBQ6SjydgRL3KbbdhcZQcNP5ksP7XBK4k6T4UrrvUqFZM3vB9d498RVBVJuzgdE7cWIrwb2/2LyBkTUhsgNLMErWGhrPX+bGA6e2Ld12ceKTtyz8o6vvLkl8pdbvvqU+OrUpiWT58R/rL8lVTf5llp5z8OjN3T8IDO1OGcXymuwCxxWnHCBjLpGfihf8H0FnLXaUpeQzyvSSYB4V26TnBzJMmy3dAPvy9Emm7DnSeledgBSl3I0yVCOtsjQbbPVcIVcZS6FIR2Z0ZlL6gzl38iNlGugr7ikzlDOaT3XQEpwyc+jfImUi7Jf7fDZbheSVR/FKFLBlSK/WuAUuWZkDBFfKdqIbStzBz686tDCE/etuPVnvFfc+fVPrFs2eUatHPmz7MYpXAvlMhc3JOjBXcQKpzQEY0EkwV4QyWMa50tD3F4X0uv1Zn1AV3VkAMjqKJ2gFYGYlOEDBCUPsiaXTqDmLSl0EG9I+VEf50MkQN277gA7wh5nx9hJXEfoswc3XPnIzIJJ38wipAQGF463zC+YzCOZNHImswwWvE3huMFQBnx8sICLhG+Tr9+nrC3YXHBbgeKTOYGvw4WIccEJEscFHcrMjUPu9sGDMEGXFLm7ib8i6SsQOe4e5NUD0kl5hkVsv0IBlE9eNvLu3PkW7ovySJxqWWwBK6Assg4HXH0V97z+ZjY7+ca/TLNTvPjjR7Jn7ntEzH2XV2f/NjuZncr+jF/PWXbXa3/ND71C+jiQ3aJWQB/5rIi3OsFiadNni3lwXbP7392i2X3ELeDdz0imQbwl5Q4iJ3cQuYgI4k0pd+rjzCe5u22bq628NSS4IUKiTBwQZ8W/C50JGkT+BgTOA6RjIa0YZWmTKEvjFdJSZXcyXBBZp5aEJgQFAiHoM2JeKm8wD9cgA4YQiQBfO4cHZMScydE68uZAH9I4iHCQoInknA6D+xQd54xcCxFOgFryPIYcbUhtGrPaoU2Rux6SGrpCPfK0LXOQGSV176YdEi8lj1MIZ+O3A1DSQsUOBmsjuS3DEydO3P7ZM9lp9k7jI0H/nnq+9Ynj5Xs+kI1qP03dnj2Tffti9kdVSsXUw0XV/OEffWspRWCc3Snn97EnnfluJA2Ul4etcuuUpZ70/dwnfLqrVW8dRPRArJDyBZELryBmwqshw6shw6tsJnGCmJTKo34yRlCNU0pCh3+7IqDrTHNLT+4WHV4TJ2+v9BjWxOk4LsqulArlrjLWspl0bFYcIXH6xpX9R06cGDxd26sMx5++f+pzauXXn/FjjTjXiEmsMcrOOvGYtEWzJFwiDF/IJ8pjLbGDMYUt9/K6Yq6csrmtrRso3lcsimmdxCyIN5zN5FOLQyy0DvnnEe/j3mPes3SNqVIP74Jzc3hZEaf7srJ8Xh7BleTcvXOFYsyZqwSUFn/KP+gf9p/ya/78ep47DOyT95MGzvs0fcAuwK7tiATCRAQ68iOefBJC7iz4A6T3P+imiwfk8vKELaUCckLmYEjiZ1N3hl0er68P1tbULS67XkEyhuhE14d6YSAUCuZytFUNj/Wlv3b/LQ3RZx/Zm1my+67G7r0H773r+W/+hXN08NO33bTixvVdtQ98em16s1M9UHfjsk/d8fCXKIO5d/q88s/467ufveeUBaQkcf3qRlqOq9+Q2Yzz0OOmesA8Zp41FRP3uvIcdElKEQeinP0QkdufZpC8I8pvy/MOiBdmu16QloOai04h7UeTtZ7zv+sXhj/kL/MrzD8bBkFckjEKxG+cGG02v5em9eMgh7dJevPDMnW8aSYa4NikNX+B0uFzuX0eU5/JE347RF3hInGUumuCtiDORnagFudrZHqxOl2njNfmH/j5rY/OOXEi8p0t6b9WK6c2vXt7s7jw3sufXrnz+afFMyQ7L2T3D2oN/t4wMXPv5FMF92i60PGnY6GoWOsxYlxVTYPuUuKX75z8V9w32VcPmul7dVdnsN7kJv2W42BO53Q6kBsKTmTry2QY7NT36Vq5WCpaRAoXQZp+LxP3ctzX7V3Kx7ko4/VccLWqMKQn1YgHshtXeUgtU+vVAXWfqjF1N/fo4JwCO1204XRUCQ+17AZ5QzQFu8Q10W5IS97/0OWPcNVk617iRTx6MqA2TfnF2/JvEnjh/zbeYx+VxG+/AqhQ2EJWxirwn3HLWTNuR9ewm9h6nKnaWQfbgP+Wuxn3pV1yIIdd5v6qo4Nim9va2zasim+8q3/r3m2D/XtaN/wndtLHZQ0KZW5kc3RyZWFtDWVuZG9iag0yNCAwIG9iag08PC9Bc2NlbnQgOTM5L0F2Z1dpZHRoIDQ1NC9DYXBIZWlnaHQgNzI4L0Rlc2NlbnQgLTIyMi9GbGFncyAzMi9Gb250QkJveFstODYgLTI2MiAxMDgyIDk0M10vRm9udEZpbGUyIDIzIDAgUi9Gb250TmFtZS9aUFFQU0ErVHJlYnVjaGV0TVMvSXRhbGljQW5nbGUgMC9NYXhXaWR0aCAxMTE0L1N0ZW1WIDAvVHlwZS9Gb250RGVzY3JpcHRvci9YSGVpZ2h0IDUzMz4+DWVuZG9iag0yNSAwIG9iag08PC9GaWx0ZXIvRmxhdGVEZWNvZGUvTGVuZ3RoIDQ1OC9MZW5ndGgxIDY1Mj4+c3RyZWFtDQp4ASspKk1l4GBoYGBmYEjOTSxgAAPGBCAllZ5TmQbltwDpFxmpiSkQPsMfIG2WARSAypsAaZWM3JIKKD8CSHPk5CfD5GuAfLbcxAqo+Qx3gHyFvMTcVIh6phwQH8KmFsnHwMAINIuJUYFBgOEwAzsDE5DWZ2iDms8ClAXJszH1i2ieSInnt/nKIMkBltz9uuYMiHGx95T7719/uznfcJgBuZxAEyAAqI993t9bDAxcC37/+rWA8w3YJKgkmGJiAVl/HsyG2MPAwMPABsQMDIpQm0GSJUDIwMDKwPCvmPkSKx8wFtgZLBl8GfyAugUVBcFYhI+JnV2ETVlJj8lUXc3M2NjIjsnURE1ZiY8JLGZiZm7HbGwkx8QMVAkRsWMC8RmZL/2JYvb/y8ZUp2wfZswqJ8UvwsvGyiQjIaRroyoQHK1qoyfLzszOxszKwa5h7qTkneOqdItdUFZUTFaIg0NIVkxUVpD9721Wvl+fWPl+O7Pk/J7CzGYdY6/CPIOLg4mFjW2HnISklrWiZxi/sAALt7CAoBgHu5Agj4ZLzN82URmQGTKiohCz/vqC/MvIIAQNKzYGYAj5hnq7BflpOyfmZCYVZQIA3NJaww0KZW5kc3RyZWFtDWVuZG9iag0yNiAwIG9iag08PC9Bc2NlbnQgOTUyL0F2Z1dpZHRoIDUyMS9DYXBIZWlnaHQgNjQ0L0Rlc2NlbnQgLTI2OS9GbGFncyA0L0ZvbnRCQm94Wy01MDMgLTMwNyAxMjQwIDEwMjZdL0ZvbnRGaWxlMiAyNSAwIFIvRm9udE5hbWUvTVVLRlJOK0NhbGlicmkvSXRhbGljQW5nbGUgMC9NYXhXaWR0aCAxMzI4L1N0ZW1WIDAvVHlwZS9Gb250RGVzY3JpcHRvci9YSGVpZ2h0IDQ3Nj4+DWVuZG9iag0yNyAwIG9iag08PC9GaWx0ZXIvRmxhdGVEZWNvZGUvTGVuZ3RoIDIyND4+c3RyZWFtDQp4AV2QwW7DIBBE73zFHpNDBM4tEkKqUkXyoU1Upx+AYW0hxQvC+OC/LxA3lXrYAzPzYFh+bt9bcgn4LXrTYYLBkY04+yUahB5HR6w5gnUmbaeqmUkHxjPcrXPCqaXBg5QMgH9lZE5xhd2b9T3ui3aNFqOjEXbf564q3RLCAyekBIIpBRaHfN2HDp96QuAVPbQ2+y6th0z9Je5rQMiNMtE8KxlvcQ7aYNQ0IpNCKHm5KIZk/1kb0A9b8tgoWUYIcar5X6eg5YuvSmaJMbepe6hFSwFH+FpV8KE8WOcHcEZwGQ0KZW5kc3RyZWFtDWVuZG9iag0yOCAwIG9iag08PC9GaWx0ZXIvRmxhdGVEZWNvZGUvTGVuZ3RoIDE4ODcvTGVuZ3RoMSAyNTM2Pj5zdHJlYW0NCngBrVZbbBtZGT4zZ26eGY89sWfGcXz3eJyME8fxZWzHiXNpNzfn1qZJt/cmaXpNSNWmTVO6FQ9LuUihiItW+wCUFTzsUxd1tUgtC2hBPEDFslsk2CwvCB5Y7fJAF7QiqRPOeJwsIB45kuc//7n93/n/7z+/V65cWwAM+ByAAMwvzV4GtYZ9FYn0ucW1s3X9zwDQG+cXZs9YOniGpHEeDdTns0iq55dWbtT155EsLC7P787/HOmBpdkb9fPBH5Ae+szs0oK13vZTJJut/v/rKwCAobMELASc4E1AAxzJdnC6fj6BZs15Cr/70qPHZ045uv4BGpna5KMPbz02O++s/7KytVFdt33ETKK1NnSC1dA++tvVDQDYe1sbm3dsH9VOqk/WhECY5t8GgHgPKPBjMEhUQIWgwQj8FWjCPgGXkH4b/z24DTvAMJ4D9+BlgKM930C7LVwA8IACKtLDwI7GcKTZAAkIFCkGsOg+HFphrxtdASvgAcZix7Dv4zp+Av8e/iEcgS/CV+Ff0QoSgO2r8H1SQHtpUARjYByZEcNi7ecWcJqmqGgkiefimpHJpMt4LqtFIwJeG8sa+TLMpAM4RCutkTJu6hh8/9kEfK6q4mvh0lQHiSViStDFMDAYsMcyIUdlLGo0e0mCoSDJ0HGjPzq9OhL5NeuJ+/xxD4uk34dk9S1S2HxKCluHif1bP8L/Uny+rFJrdg4nbcy3mgOS2uHrrtgddlJoUrw+mhEFVh+arb7sjSksq8S8vph5VqxaQj5SdjaJn5FuEAEaADFZtq4Vh2FagNGIphl5zLqLQkdhmPgBT8mFjkwxwBOHt70HCbs/l0hm3RSP3aWc0XKmNBAXqbewH2LLc6oukdDmtGNEVXBxBKXoUeKWKHEQcrLrFyYZcDC48wG8Dn8HMqAXWXebxjUtl6v7Nou8lsllk8iHe34kTD9KtDkiueVM2sjD6+6E3tYi5tdnBlcPp7rXXl89LMb7Uj3zoxknJ3IU6xs4uVy68M3TrZ+c7p4xGgd7ckeSQcFJ005hsNQfG14cGr9aUQ29R3f7Ij7BqylB1R8NuFqm7xzfaFAz4UKvkUVoKwjtY3IJeapooq1j+ne0aVkRTdQmRqjVGCG5A0gv43n4mPW0BELNjdxzLx0/u36kOTP3tVOVm12cPxWLpXz8pjFvdAwmpIaW/VlvR8YIRTgHSxCsg5sfOThx58H86o/vDHWXsD+xTo6iOCdbze4f6ji4kCtcnEo7IvlmhHAEIXwD+TMB0PuCualouG69hsgtWJzdRRg3aghp+EaLWv1jU+lEX/+Z4ZTDxjMQJxh759GV/tUHN0rl669evPyds6m/w2OnUoPtjTi2mWwtnuiLuBQX3RBulIOyQ/AoYtfNhy+s/uTzA/3X7p0MXVxTu6faAcqfpu2vw1fgE1BGOXTKQhWVZanmPi1OUSiMihKA1gDKKIQuj0iAksj8ms6UlXBaxqwEqwW9jLuyWjwuoE2IAmUcviI7L8iu7OyXDiXGJd6VSb43unog0bly/9qV755rF8OpYKLdSET1/NwXD+pjYaxJlLbfnByOFWINk4NaIeYqDfU88AZd1MLx4njKDU+nkp7u8PjaVEIS7Krsj+EMjO072dV/bSat9h7JhbvyaUWZaC/NxqNzw+OfnW5jba3b/xyabEwUg/snPHq+OtOWwklXNBRwprOKhjyBg0s7m9g6OQ4k9EJ9yp89wrhQvCLmxdGNsZt8jRZ+fle6yoemS93Th7oirIMlSfSBNxE3EBMcLJYa7SwMj5YQLTFwe2cTvos4kP4PG2YSUbS4S9o9o5KBzJlsfZf3daixDh/vUotaai63a5f1tgRDusKOvDx17IWxyJ51rNo3kvMP7Kve38Nza7d3bnKy69yXZ+to8Ee1DEd8tDLGzN3/haT2zlD4I0Q9lnE3BhokvQ3hqfthF0e0XCj47IGQhyMJHFbUpJelGVpUu1qrv921D/eQLKf7NAekbSwv6cg3wzsf4E8RmmGThzU0hEUxK0fRW0PVdUTLT9GiHKbwp8XzX5lKHxtKyTzB8DYu0TttRHJxd6x77MBYdyx98guH9IneVhdDQEjzjE0rVlKRdMiplScOTJQ1LDC6Mh53KB6prdUflejGgFfwNnsDiZAv0tp7tKf30qjON0gOhxRUmiJuWvJIgjfqDuohX7i194jJn3s7H2MP4X3gAvE9/LV8MSO4F9p6dmMPhbDR0mKEed6Swn/rUNYLqsOhFvREp+p0qp3VIb1oDhR1vWTKkllFt9+BM+TbiLPAZVaEJL5bEOhnpNOfCLdkmwgKnyGcvraQnvES5HbV7mRJxtkoUnftotUzmYAqNXyNVECyzsuwWT5NsBkRFVMr1/Mxq9hIonkl+BrFCbaqwQgcRaLe336j+EUKZwQek0mHJx7U2j3ME5uDI8/44mZpq5VJDo5c5UhR1zxBWWBeJ0iIoXjYtp5wHuQ2s2GgAf3MRiFfgqGBvrGjA4l9s4sX5q5caOtfXjT/tP0Lyd6s8g0KZW5kc3RyZWFtDWVuZG9iag0yOSAwIG9iag08PC9Bc2NlbnQgOTUyL0F2Z1dpZHRoIDUzNi9DYXBIZWlnaHQgNjQ2L0Rlc2NlbnQgLTI2OS9GbGFncyA0L0ZvbnRCQm94Wy01MTkgLTMwNiAxMjQwIDEwMzldL0ZvbnRGaWxlMiAyOCAwIFIvRm9udE5hbWUvSEZBTVpGK0NhbGlicmktQm9sZC9JdGFsaWNBbmdsZSAwL01heFdpZHRoIDEzMjgvU3RlbVYgMC9UeXBlL0ZvbnREZXNjcmlwdG9yL1hIZWlnaHQgNDgzPj4NZW5kb2JqDTMwIDAgb2JqDTw8L0ZpbHRlci9GbGF0ZURlY29kZS9MZW5ndGggMzA3Pj5zdHJlYW0NCngBXZHLasMwEEX3/got00Xw2M6jAWMoKQEv+qBuP0CWxsFQy0J2Fv773lHSFLo4i6OrGUaj9Fg/166fVfoeRtPwrLre2cDTeAmGVcvn3iVZrmxv5pvFMzNon6QobpZp5qF23ajKMlEq/UDJNIdFrZ7s2PKDnL0Fy6F3Z7X6OjbxpLl4/80Du1lRUlXKcod2L9q/6oFVGkvXtUXez8saVX83PhfPChOhIruOZEbLk9eGg3ZnTkqiqjydqoSd/RdlxbWi7W5X86wqBSI6VEmZ51BAtMlECygg2u1FN1BAtM9Ft1CAdCu6gwLoRnQPBdDY6hEKoCzpAQrQqhDVUIA0jtFCAVGhJTVQQJR3ohYKkKIzHvn7Gnmv/Mt9j+YSAlYYPy9uV7bWO77/rx+9NIj8ALTZlwMNCmVuZHN0cmVhbQ1lbmRvYmoNMzEgMCBvYmoNPDwvRmlsdGVyL0ZsYXRlRGVjb2RlL0xlbmd0aCAxMDI0My9MZW5ndGgxIDE1MjM2Pj5zdHJlYW0NCngBvXt7fFTF9fjM3Ofe3ewr+35vNrubzZMk5EUCWUNePBKRZ4IGwyMQEBQwBKHiNyqIRIoK8lCoFR88xSwhygLFL6UgYq2CpajUWv2K1j7ys+0PbRV293vmbkghn3778Y9+urtnZs7M3Htnzpxz5pwzdzuWLmtDKagLMWjC9JmL5yL5U6pFiOmYvWjm4iSeOgfyd2Z3dniSOJcB7QvnLp63KImLTyEkOectXDFwveEJhNI+aW+bSa+jn2sAxe1QIWMID4c8vX1Rx31JXN8HeWThPbMH2g20ffqimfcNPB99BLjn7pmL2pL9S/4Eefrie+7tGMBpv/sXL20b6I+bYHzvIgy12WgjUqC7kIAI0sK3BSHhS8mJWGil7fAZM+oj152aiq+RTpTxOxsel/Pzk3/xxN/brgWVT4rfQoXien+a86F4CCEVhvZ+5ZODLfJ1kGRH0aSsKBoDUAlQBJCVdYsFdeFd6AmA5wAYNB8/hlYArAN4GoAdLO0F7Ah+rJcVw0fxCmTDY8NK1j3ZYHVbJKX7vSjm+551f2j57Bi2wup9iq29KUhxi4Sfwz9Gc5Abv4T8eCWqRxn4mUOhhe5WaNqLFgN0ATByivHeXleB+3WcjfwshmsCyMXi19y/y89xf54fJbjXfTIYZSH7qQuwsMZ9wvms+7+d89yvA+xPNu0LQY/X3HudC92bXFH8TK97ozOK4Zonk9kyJ1z6mntRaIt7Tr7cPn5LlOzvdZdB+9Sw0l1c6nUXOS+784JREQOe4xzvzsz/hTsdLoRuHripP6xzO5yb3COgyeWsCY4AOIb34e0oE2/v9Y91H4UiTPfQmFDplij+waH6jHx/FK8MF9dnbAnVB/2h8W5/qDYYhPLUN4XVwu3CLUKBkCVkCAHBK9gFg6gXtaJaVImSKIpCFL/cW+nmj+H9qBLIsv+QyItcFL8ClewxfECuPHBYZEUiItEQTXwCzIuRIYr392lpCQqv8XKJj+IDh5JVB8JulpZYuUFLaBkSSBHBIkFjUQT/MMqjNabOSkulfpSurLb6/0pa5Zbradb//bFgZ2TLuElNkX3O5kgBLSSczde7W64X/s+8Yxk0tVVlZY2buOJQ5+IFc2vafDWtvpo2gNbIY53tlkjXLI/n4ILFtMETYQKts2a303xmW2Sxr606ssBX7TnYKV83pHkube70VR9Ec2smNx2cG26r7u0Md9b4ZlY3H5pVtbTlpmetG3zW0qp/8qwqerOl9Fmz5OuGPKuFNs+iz2qhz2qhz5oVniU/i06+Zv6kqns7gDs9NfPHeSIZkyJjbpveFPHMbK6O4l1QWb0McSeQljuOMrguZGPzkBuhxIcAl2gen5L4gjuDtPFFib8w5bCoRyiQeGUFOoF+iLajHsSjPVDOQDPQNnQWLwDZvgP1oYvYhXJB97Ioisajt3EicR7NRS9C/w50Em1GB5EKrlmEjNC6AfsTKwEPQ3kWWp14HqWjUvQIOo7K4K4bUH9ib+IQtE5EU9A+tB+u/zn2kYNsauKVxGUkotvgnquh5XxifKIH6UEvVqEJULsavY79zKVEO7KgchjdDvRjtBP9FP0JP4T7Eu2JzsS5xKfAqhbkQJPguwr34U+ZHvaRxI7EHxJxoEQGyoSntqJN6AW4fw98T4BqrcF34Q68CW8mYfIQ6WPXcOZ4DOgQQnXwrUf3oEeBAkfQKfRX9C3+ilgYLdPBnE4UJf4/UqJxMEs6kzbUCd+18N0AczqGeTwMj8YT8Cr8FN6Mf0kyyRTSRJaT+8gXTCNzB7OC+SV7L9vLree28cr414ljiTOJXyEzcqLb0VL0AMzuJDqHrqDvMAP3cmA/LsdVeAZ8u/B2cgTvxEfIBHwCnyP78G/xZ/grfJVwREWMJIt0kE1kPzlJ3mHmM5uZp5nfMl+zozjC7eQ+5/3Cr+Oz4uvi7yTKE58m/g4qVkReWJkq1IjuRDNhtovRcPRfMIsD8O2BVTuFTqOz8vcz7ED96O9ABYT12IYLcAN8G/GteC6ej5/FR+H7ujyWbwgsBFEQHTETB5lEZpFFpIv8inQxdiaTGctMZ3rg+yZzkbnKXGU5NpU1snXsGLSeXcQ+A99d7B62l32XK+NGcY3cVK6LW8etZ2Zz57mL/AP8Br6X/4r/M6jF8cI9wnpYnbPAsz8FXv7Hh8XpMPoCdDeajavxLLQFVmMnnom6gbvm4EeBXotRRqKFeYCpI8OAG15HPwBufQatQuuYO9DOxAfMPvQ+cMpCuGUX2s1WISe3FVbnITQMuGjgGw5lhjKCAX+6L83rAZXvsNusFrPJaEjV67QpKqWkEAWeYxmCUXaNr7bVEwm0RtiAr74+h+K+mVAx84aKVhBlT6T25j4RD71uJjTd1DMMPecO6RlO9gwP9sRaTwWqyMn21Pg8kV9U+zxRPP22Jij/sNrX7In0y+UGufyEXE6BstcLF3hqLO3Vnghu9dREajvbu2taq3Oy8ZEwkEPKyaaKI4yU9MYRNHrmKlCwaDTtUROx+aprIlYflKGN8dfMnBOZcFtTTbXd622GOqia2ATPyMmeH4FxosdUc3xzHouG0axWWpp5R1OEmdkcIa30XrqsiNlXHTGv/NzyD/R6qWb9DY0R4q+d2dZdGwm3PgbEpWgrxWauB2zcJA/clqxpborgNQODoGNcACOlw03uCf7WBZ6Iwlfla+9e0ArERRObem1hm6x8I2hCU681bJWRnOwjlgfKvTD7Izm35NxC83Kv5YFk/ruHk/XvnaC55YFTn0A+buIgATClgG8MjDPimS0/xAeDLaVJWynqnl0KdIJPM4ZpzofxjI4Q4BnGH+H8Y2ZGuiZdH0Z7dXJwrQuqexVWm7wJVTVD/9Zu7QhYKeiv9Xm6v4bdutXX/6eba2YO1PB+7deINtKFHuSVCJ55vdxJN0s/zLrd4mun69spryngPkvNDRWAU9LQMUcMsIFPaPJGPM1QAdZk9rgoUkxoOojxhuYoTqyJomrnEbBRmTtnQHM2ZbX51fB8QHLAGs3O9EIpN9tTC0+upbzi6fZ0j5nT7an1tAMzsX45h4a27uY8oOCkJqATmgxPDDfbB4ttzc0j4D559D5wCXTvboY7LBi4A+RyVV4MOg3Lhs2UCUxouq0p0lVtj4Srm2EVgH1PTGiKnADObW6GXvmDI4URr5pvGRhzAYw5PxPaC5N3AdulC27R3N1N7zmpyeeNnOjutndTeUviUYyGVoQHKqKIdqEkj+KuCXAtZD6vXV4Dr88Lw2qmNB0OLH2do8Bm/9cULh4cN1xZAqMtlilc+m+icNn3ofCI70Xh8sGR3kThChhzOaXwyP8chUfdROHKf03h8OC4YZC3wGjDMoWr/k0UHv19KFz9vShcMzjSmyhcC2OuoRSu+89RuP4mCo/51xQeOzhuGOQ4GO1YmcLj/00Ubvg+FG78XhS+dXCkN1F4Aoz5Vkrh2/5zFJ54E4Un/WsKTx4cNwxyCox2skzhqf8mCk/7PhRu+l4Ubh4c6U0Ung5jbqYUvn2QwmF7BN2oh7uGqF30b1fMd9xAcrCUOD2qImXgOJehfWQfmgJ5D3svCnNTkQtgK/hi0wFegvqzgO+Ath2A7+DL0ATAe6Dcx36GvJDvAzwT2icCdIKDXg55KUA9XOuAfCTAanwGrYa2LsjX8fugDHUAtG8nPH8dtNHxmAHvgrIS7qunOYARgMayrseaVOABnQDcA/4IuP7/9EPAe4DL5A/3T3tcr+QhqiXCnn/9I0FBCT4igpiQGmkgh2gD0oG3Rz+pcnpjYgDP0gTekQVZkQ3ZwcNDYIu7wKfzgPeSBpgPvEw/CqAgeHkhsM+zBi4vRsWoHbzPXeCtvE6qyHvMcGY88wbz/9hq9iRn4Z7nzvEW/ilhhvCmOFZ8TdGleFvqkE5Jf1XOUq5WIdUdqgsptSnr1QvVP9GYNC9rp2u7dR7dYrg7AZ8JsefA12ZgdqOTcTQxL4pYAFEbRegcAMWhzHwEZcgFyBnIFR+ho3AVQlOzjsKdOMiH5RfqvLogQBW7IXrtf7jj342Osg1XIS4DK7Avfg53oUtArZywCfnU0hxR0prNNmG4NAeJVs3sNktWo/ZKQ0Wsv7GmrfoLVNnQf6E/f5i5uKS4aHgg6CsqNBp4YV+NQ4PJooutnedVU3IyBaVw6a3lfUZKLIym4I/JOLIV5uMJSyiPwTYOWVmIKFUd8h6thzBM42XtFyivAW6b6jV6p+Bv4hLZSmMIGHwtJI+PQYFwKs5kJA4Gh+fQ6+d46eCyGq80xAaHlj+spNDo6zl//hIEJuj1YaCli/sRrOWucGMxW8tO4+5y3u1a6VqN1xIxU5xuvct6v/V+x6tWDqVhDetQW72CwwrxQM6t0aSlSkWpnMe9zJum8v6XUGq6J00d1DzoLk1Lr/NpO09ZLvRf6dd+3X8ZVVbEKir7dfqyPL25DEOuLyvTQYJa8oeNXhF2sFaVXxdQ6tUZSGEQMrCVTdFKGVg0QgKRUa0W00DMg6ilWF+Jk7T1pQm84IOyt0BvNAi8BvNQAQQau+anJx4cPnHLqiN1AfYwU7UMZ3zz2YraV9fNKp1jY9TXQkewfvE944om3bVq0/pxa451not/88LLK+vaxhfnT1uwD+jCAI8jbiTwGEESSMyl8IR63ITbMfMos5XdJu2VooqoxGdIGAk8j4moUEAiIYHD6zHDegyS5NdDnYHj/HrooFRyjEJieQ4rCWYQcQliFDeHFeCi8gqJ4QDbE9anpMDacc/iZyWrKmWnd/0MWD1r4xVLQyxmlVewttqCKs0VlRUNMSCnrqySEjJJyby1uVmrtOPAkmZP2CPsqea1uZaBCgYqmFPNWQN912orKgSA/GG4pQW1YCVOLcQ+xsv4MLPht/1rPiXGS5tjx378NnmCTCfrYsuZ2d+NxtF4vSxxW4EuLJQk0AwZ6KFw6fSU6boFZEHKAt1KstwrjEmp1xGn6Naw7lSgYVB0mYnSFRTZfPt8Tb7Plqkw+jNM1lBmFN95yNs5V2ZQOp9G7TcNwCyoMlbZD1wSk5lE5g29xcaJVj8fECxsFuZsYhZwBGWHBx+EGeACKmjBgM+ru6HIeD00HgD8YErmIUxOPVB797Kqh+I/wgcON+Y/Pn5VfNnPyHKQyvCtoYYlpbOb18Q/jm1iJvhKHn+iwBEvi01fMPrO50a4Y1e51GduX/5Yc14wq7h174Z7XwaumJ64xC3hPkdUGx4Ml9u5rXgLx7ixm30Ir+XWpXKTROYRp05n5Ec4GdUIo8JFXC4rk0/Ktfk6m0eRb7W6PTu9C5IEaOgfmD7MHFVW9ssk0IK8g2iMQA6zPzWg9tsDSpOiAKUYtAVYr9NoBQdgHGIKMCYsI1lUBUijh0S08QWYxZBQkcHaCm0FCJCc0ooHW3CLiM2+XOxLQzqtvhAIWFIIouP1BAM6LYiTj3Xh4bqT3tO9H8a//stXH9070nXStrEn/n4CvfL5y0dxXQb3efzSsQ274u/GT8fj8f/e2/zklz86vv0X+GVcc+5/QH4Iegn4ZDbwSQrsHfPC7rW6LXpSICpdGoJcZlHMT7XZUvxqq9V20du57rqWoixAGSAmTzyATTq/McALnMAKjEAEjpe0IszWBIlCryzAggEiTTDFrKxMOi8/nQnVDVoC3CCzgM4gEFj6c223dIwtt2k+/Ev8x2+SSThv9+am7fFHYj37jMF7mh+bVId1OPfqNi71/ZPx8384Hu+VdeNZUJAbZa4309OZo6AuEcrNglgs3V5I3rD81EKd7+zZs3TLgMYdMOdG6K9ER8Mrec7PBcV6oUlYzj3KbGOiEHb7naDcxexiCcdliCHFHsW3hIPJiZyCuUAwx/FwtqAgJINh/HqWVfBUeUAVx0I4i0azBF4hcoSVWAYTSeDFu/gf8F/yDG9LwZJfiUBpADGp0mi8QiXK2qj9ogUURgUojApZ65rLxLUNuVncKu1pqh5Y7biJ9608rRUrRNAGaOmSFrykBaaEvQrsxYLOt+MkeRunxn5EOuKxWPyPJ7njseHk7Vjk2iby6adxmUYwZ3YczJlD+eFURBjiYjmRsQmY+GET4+GsZBKI+T8GBWOCEcHoKmEzg43X6N1xhnx57TYg4V97gG8oDTPhfgrQMJ1hQwku5YmAzTiI63ATkAsTEsXbw2bQqsARIlACDl8kRpIwL8LToe1VjrWpqH7dHpYUyKpUPeftXDxIFJAz+nSqC2VRA9pAkQXtuXbVaSACCAeoQx2sK4bfjj+SL47/NqZ5nYzgjl+dzu76bjT70tXbYXx0j5iQ+BX3JegAjWwZdYez14Jhdwb/jLwpnpX40aJxhIaxjxAUDuJwKPX5jM1lyVdana4Phoj9oNDLbF+AbCkB7Ff4uYBJbSlABqQvwDYRSloeSmaVsQCnEkiskr0A6VhIZDmnCf3AHonMJp1WIAMCrfcifZEWUWk36L0Mu/3Yxt2n4pvjB04eeOp1CMHb/xj/yx8vxz/5Gzaquc+/+1n8XPzwpQT65AM8FmdewNrvnscrvoZweEX8TPzdK/GD3AxYJ7A72L8DHSQY38xw0XzVfP0K1Uo9W29oMrQbVhpYQXTptFoJqzUuOMiSRMLrVazCYMhnbSaNwo+sRlMUKw95N1+XfnkHiOmAUUEDwvamBbJAhsFAaEn1FoA250GqfUhW9d6C4qIesvnUny9+HC84w3TdV3VvvAOvf2Q3d/w3b76ciG1ij4xwx5mlT1BdBMdv3H0yTwXRU2G9kDIG13PNuImbz80x3MeJpmNwaGBFduwIV/m8nkCrfol+mYHRu9wGh5HxukwGNqBP97uQQmEXXEoScNhFj9/o9puYfM18uy0kBvxByZoRuujdfPOGdgVswQug0kAEY8nplOmS5g/dtVuAC7PoNoxhNsktjPEW0P2KF1zYjWHrMhtBT+fhgDxpH1O3/oWlI+fGbWfInj2L3l00a+o0TmCU+twrkopVCXPKVsbLzzCOxRt/VOYCE3Fn/ozY6j2FvqVdpyeHag3e1IqpXz+Rb491g67ywvqB3gJbeHjYhnkXEggrKsD+QFcJ4+fYq7xVpAYItW2vwDJcGTAhZdmFMRthKXTeIvZsXPdWXMcd7/nur5wamILSe1/iQy4P7k19h4qwz8wFuVItIyHCjdAqTIzJZFD4VTYL9husZstz3s1J6RzYA68LQwWsO9YZzCa6PxWBQMoMzQSsoJo6Kpp/Gbs9/60xj8TXx9evGUNGc8evdTy34LkDM37MrL92Jv6XjfFvsLQRa5gymGsmrP9wGI8S/SS8OAMXkzoyjZnGzmPmsZ3kPvFR/AirDCpLSAlXKrZzHKgRLGtgThREhQAGHGhmBRT9ekkpEUwY7AeHSUk4UQmqWODpQQNYcUiUeBYmKSpFBRYUthQGg0qOYtUh74YBU67BckrbaP0GsiSfU1uuApQR8ARVzKL2BAeaSM60N2RJveT1KXCh/PNhbPszUcdTv8XLcUd/PJVwf4t3kL+Abn6HFMSGxzTkDtBPExMfyac7Gji3q0C/CZdmDsOSVmlXOYKF9dr5igVaoUzUqxSMvUBIVzi1Kmd5FskNlR8uJ+UFmX69VuBERzDN7IjiblhGp1sIOnOVxFmkrBAqKhwGIZS5J902yh5yjNUES60jR/0EbwXGOoK3oOSODvYctWsux05dX1WwbMC6oxOmzJ/bn9tPTViQCVn1ZRSXGNMQtvpxscaLLC67F5k8Bi/2pqES4kU2p9kLjAcJ1Xhg2Mjq7kFQeLglXeaTkViNZTfAeJOPMAoXUrHSGaATPEINlk8wEKRZoGh4cUkqVi9tvLN5i7e9YNGs/Em4b5RR9fDKH5Z7pT3c31443rnM7Fe5dJnZgZZMk6Lknfs3Hz+6tfvd6dljdj1pdPDqFEfePLxQzLbk3DFpfOakN7bX12+LbXWkMcwaFV/lC9cvePXRzS+m4stUNjoTH7N+7iT43C60OJy7S9jteN/BpIkaFwFX3uzkBJ3kciqVhqBo89hytbk4hHRgKq71Hm+5voldvixbiwjsRPjpwI+SqWfRm3jJxBsCWC9BYhTMAZyqcAWSFhIlE2y4lBR6nYHIFDD60qmT6kvjjVTQOnvKX2x989tvLq2cXFC2i8x98skf/uBIoO4kdzL2x4bb4v3xK/F4pNzXsG7Vl6/v/fi181tnHJTlHU40mXNsoxwh2B3O223F2yx7xH0WZqyo225gGAPvtAkpToPSLtjtZm1Qj5kg0dmcUtBsdcBrHsIh79JVAxwj78/9ZWXUBxhqCQ9HVtGvMkoBpE7VwiypDWwFDGxgr2wDK00pAbCBIVFY+AC1gb3/xAaW+QWZkhawAFOXuaKQsgOBfbJQIBc/M/dolz7w8thhj25c/LC1x/XnY+99h/UXHGxj5P3ZD+9Z9NzOj9Yt/9VpXPgFHMeO4GBdSxOXmH5YVyVyouXhghJ1nXqaeje71875RQPROLVIdDqFVIk4zUouNzVXG9LpbW5l0GZ1udd6l1bdOP3YZfCXb15bm8WhkBDGFiXMzQEJspIAkuxiACYIvwfp8urpRAbWE0wAM7Viiui0UNFwfeE3G3eu2rlr5aN7cfekYSMPPF/58j2H4t999TG+88v3z/78Z+feIiXDXeOI87tRm2c34Zzv/oCngQ6pT1xibXBC7KBxHqwKr9gqPm3b7WY4NdFwBqNarzEawqqwQQzZ8Djla8wZ/AZzxv6B+KHiovsD35fmL33KM7ozenKHyHnTNc+YnOllvCCYvE6HIDlNSr+w1bHbcRhkgPWbNH4HZ5VUgg5iCM4gZwum5wpBqzUQvODdlWT+hliS9S/EZK9Xdn7zWgb5hFoNNMYgi0Mt8rEcA8fvmGN5N3g2em2q1qBleZU/zZ4egAiWM4BdToVZCCClUR3AKWqfzQtVHCSiBfgKIhBAaKpkZF0j65vMrMwHwUpGS8B3pvuzyeh1gUhRF0oNW4HAy04VKpS37DQe7NW+i6XFeu21r7gntv5w8jDDQeHW/Ikrbpn4ZvwP2PI/2K3MGHvg/j0c9rF1d025beHY51843VJcV/5k7gSHFvvgHQSCq+KBZbUPHerGH9H9FdMoHDFz70FcriGcJTh5yclgjaHMlMLrJSts4eoUXcisF/QatVtN1NcMVov1mnfeA0kWi7WUnaJ2lfbGDb1SjlnpS4oLC8DkyAWW4Y0QJ4ItHuJXRa/6Kvt06WaHVTnR09vXu3kzVzX8DkJeJHjKKxuuzWF2bNgD42LQyHg58yXwihvlwFsrh8MNxYYx4hhFk9iseFS1177HuTe4K+uIXRkWGVNaSH1KSoMtheVDTqukd0qaXCE3l3MwuabcnBBnG6ZSB1NGBYIOa96wGwTkSn8ZVX6xy1/DOg9oCNCC8rIn1z3bl2FzKXXpfm3A5woEUIYNEp1S7UUatSrF70wL4KA9BHpCBYbxwEaS3EqSUkQlp6gQHEfemxYIFsIS0+WVd4t0HagHBPpyQGuAYYLJ/TMKi3ZVLI6fPfAn9eGU4MiH3w0HmOJtq16JX8XCUVz94n+9XuvfdP/JW7Pj59mqUb7Ra68VvN15aftL9cGKjVN/M3HC37ATp+Dc+M4TvXc+8+rxntmrSY68zquBqFSnmNCkcDZIjWgWzGKQDaYuE5aJYmoKSYUAos7JC0aVlBKSwKIyhpAJbCp4+++Qd1ZSpwzGVsAKlXeLMkwFRN4MwFdObow+HTU/YdF1vtV94cJpD/1+Us4RV/7axa/1gfL/6DZv2QvNz8ZuIy90ljQ9czH2JuVDAm8GIVwOdhWNwxaHHcLnLDAnz0jUjAS+DQkMKGzFvn+M5FSs4tQg20GgVHYBfTrgtNWH4cNmXr3IHX9bnnsXzJ36F0q0OzynmeARIrYSEDAzP42bx63g7xPWckeYs8wliHgmHWeGrCZPAVMypAxCbywHL4Dwi/RANdl55pK+M1ihiGV48J4lcJsVRAohJRhpvd5ZR7ApabVQgsm+84DrXCmbZ2CxyBbaKu1PwVm0ZLWAE31iwHPGVB0spY4zmGc+6jh3HcDvfBGfiw9+Ee/degCM0/34TPye2Czi6I7fLc9vHdCOxhcZFArDKg7EbUkIMRC5vYFkEBhLhpWT/rJvXV+fHG2gegDoz/vZOoi+rwmXC6Kg5jVm0aw2a4JiEFRovXWqcp5S5fNLNqfPKhHW7Pc6zc4UXkC83eFnUqUMWChdCF5MxL22EH0fMwx7TK4fhMMazIjilBuZ6LL2Sv+V64FksFvBEegHXXs9mJvkKOMAR5mvW1zAWAN8dQOH9YaHNy/pasxOr3i+7YPGzGN3NSx4+rAttHju7j42b9ut6SMr02unTtoxeUOshHx514QNu2JPkmOLCsY9+y7lPJnvmH7QM/RMYkY4/zB/hicsb+CDhk6+Q+AMKmKwaMGSQrxFKdkEmw2pQgqbA+daQlZktYM5e5N4JLeUpDaBefXryq6LCI0FGG+YCpUR0PFqDHKCV+8fv6/98oTsw85hD4RDY0tz7H14N4x/xsQfT3ueysqsijkppqqiJfNj78JgYaXLEx+yXrCTVPJ5yhPhwm3iFu3TppfYPeIu7V5TVHxTfJ/9XP17g2qEyDstgsqpV1oFq9VIghqbXRE0Wm32KFaAtTSwGyajhoN6UN72suGYKaBMVcDOpSMBLJihxKVASTKoAghrIRFNYBwxakjkvY0mNGqQrpedUGoMmgr1EBokXrAcZIPokzXDxh99acuWF+CFxGvxv/0mfg3rf8d3YM2uLTOeuta7/zJzKf4nMA9j8Vdw1jUwwsPUJuqMT2H9MHU1nC50hLP3irvNJEP0OHRq3mkUNLza6VCmqUnQYkuXwNL1htI0Vl/6P7V0ZXOInhfIc3SY7IizBdgAssPEOBMk2KoOIMYsz0meFjWIqHWbXDPZvsWFSf6El8ToPg0ugM5H3tjtrz16rMYPaTy3pzh8+w9eix/ueGbFxGHlfSt++V7XHQePzXnm/mm7mIMbxmRUxH8Pc3x+y51FrjGx31AZBDkmG0EGdejWcCDIBFJKmDqWVYtaolboFKqgSNlQJ4m2VExtPmTVp0ZxDQhWcjsGZQPsByGxyobKUzHwCoH3ZAOG6meZ9Qb3Y51v3X7ji3dxFqfWrn10I4jKkeLthHmdIT1LY9uoXFQl3mdeY8fB3puHc8OPlyq2cVv0Txu2Gbdl8hnp/mCxt9Zbl14XnJo+LTg3fV5ghWpFygp1p68jvcPfEdjl2pOdyoApxOWwuanIZrSbHRZjjiE3Q6OcDxGOYj/xp6VIbFaq5Q2HM1VgnbnPZCnzBIVaSwSU582zuS0mS9A8KiMgBDNs+Wp3UDsKBXOtw/J7B+03UCHJ/btMCyU63bI8SEHkqBFHvUOqUpbIqzwe55CA0W8LeNVuL1LAq9iYyQb/ksuEklMPdXaDxYs9mjQv8qapU8Sg5MUBv0LCOawX3r+HxKVzeLHVBIlsxslBcDmRWeQ649OYn7zNy+wiR1vALaQ7o+CjkZekeyTHY2gQCqwC/JXor94zZ9vI4L2Pr7ul49dH/nrXaLKPC4x6eu78mozG5Ser5n/48VdnBHwYT5g+bNq022vSwfJNyxzz4LafbJjePrKgrjFcm2lNdeZl1zz1+LkPnyPfAi+ZE18RBTcdtMPEV1NypRNqHMWVYT9rKjMzvFrS2UBdw1upIWRUGzWMmyHMNRNE0MG2G/Cehth2eVRJxyr6tbHL8k5LLTo5oDLgAweKqHm357X9+wPG/BSXwT06+MD0J5/kpsd/tSlWU5qqxGSDQnxwHjm9Sd7vuxKfMR+DPNPz4BnhEVHDmwaiSBUN1lSrIYNfzrwPmy3i1BLiUyQOdJdFsFjAJcuVQiqlzYZDdLDvXbcG5HAPZX9Y/qQdV1lBGSIZ67gp8uMrke3qIIzXj0ttwx7+SbW/bx/xDZ+36fNJObiHhdOiicNb90z/EVFfPf/syMzJT09cRz6wUflUguL9A5uHwB4J51bh05igeaidtDPz+LXso9xutIeI8LYxqWHHco+w67gz7JucOCbj3gwaQQZVK5vN8Hp7NLG4DxwJDxvFDx9mmEV6iAPBCe3DYRcPVgY8iYMwEMYQPOIZBKaHJNLF6iFHMbWSVh/CPbw1eZb3yScDp3nUvoDTPP3AcagAASBt4+UGIZlljbttRdhPQnqGYVEIwt3gx9x0czjz6eHQP+5bVhYrK0ueEw7emRO0WfCDSBq4LC1LUiF6BAbKR9iFs07HF56IL2Pzrm1j2q+eBwph+hYAtxNKKuwJP1DH7lPA8uNaYYxyLdMtrpHeIqeYN4Sz4hvSWaVyrrBAbJPmKzuFFWKntEK5RuhWSrQvqWOWo/s4ZlqGKQM8U7Ycl7OP48dZXsFiRkkYjldxCEL2SkaQ1EAjONXZLjLsKYkoTikR3q6yplCaw+EFPfiUJ5VMB6cGxgdQDSJIlEIqDmgjwFvjepVKya3VZsEPlqtPAe8AS1H8WDhVD6EBiNFxtCMvKESFBCv7WFgNRyyMUgXTli+V429rtatOWSACZ8kS4YhELqxdpT01WENjtUuWLAFrz04K7ZSWSiDn+++cf+u9X/fFzx679Mtj8Z8DSfuY8deOMHVXzzMjr/0MCDrAh59CUQlvTByUxMoIX3gQ8ZURphBHVHmRlIvwLr+k0x8kYlkZPVeyYzPYktScdP3+m29/Hd+KV3wR/yYev4xXsHnxtXgFF7sa+zXeGL+b+KnuN8bHyL4XfVPjrfDd3cZHLbstDLWXS/X1+ib9PGE5s1xYb9gGb79sM241bTXvQXtM2no0zlhnPmtkq7k3OLKW2wUvbOzm9pi59AzOYjSbwJ43qpQap6imhojJDgtGec5stPSoHjeBPXIhKSHA2g2XLTctVFKsYQkLrHkWGuakexssTVhvhMCwaZHebLZwGFPhsUDQk5KeZiLkQOX8YUvArG7BhTycexFZ6RZRR7u4ZBQuAcozjPdM4OFZVTu6dgRCrrxMbUGelhuljne8DYFzNm9e/Mn4n16Jz+3jxRdTeK9FfCqdbQRWf4jSCt63YfpAj9EzpoXhqhK+Hk1DTXgaD5oBz+OXcwqQZj5EpZqeK0EQAZMy8BrgrL8M2EcSuFGCTcWMpYdLvYPGmGxGQuCGnl2UyYl8Ol9GT+blsyXcUoK9RV4jhkMvPJz8INbHjIqtI93XuvC7Gxi0c1MMpG8MjE/+JNpQW7I0JM0GnIEzJy3YHMbBt2+y6a6P8uE8tBAVoRL450c5qkY1qFb+L8UY+PsQ/cdEI7pV/k/HRPifxhQ0lU4bNaPp8K7RHfR/b/ChWgDLJZ6+GzSlbmzN6Pqs+raFnW0d82fPlHvIzZDAOSj8SwGhCwCXAa7A5SyAASAdAOiMqwEmA8wB6ABYDfAUwIsAfQCnAC4AXAa4AovDAhgA0gGGA1QDTAaYA9ABsBrgKYAXAfoATgFcALgMcAUIwwIYANIBhgNUA0wGmJMY+MA40WAZI88QnFL3xnbqid+I5w7BC4bgtwzBq4bgo4fgML6b7g+Owk34uCH4+CF44xD81iH4hCH4xCE40Oam500ZgjcNwSkH3EiPWUPw2UPwOUNwmadvoP/cIe3zhuDtQ/D5Q/C7huALh+Dy/09veB71vm8c/z1DcPqG2Y3tS4fg9w7BO4bgy4bgnUPw5UPw+4bgK4bgKyn+v1djb5wNCmVuZHN0cmVhbQ1lbmRvYmoNMzIgMCBvYmoNPDwvQXNjZW50IDc3MC9BdmdXaWR0aCA0NDEvQ2FwSGVpZ2h0IDcxNy9EZXNjZW50IC0yMzAvRmxhZ3MgMzIvRm9udEJCb3hbLTk1MSAtNDgxIDE0NDUgMTEyMl0vRm9udEZpbGUyIDMxIDAgUi9Gb250TmFtZS9VR0pFQ0grSGVsdmV0aWNhL0l0YWxpY0FuZ2xlIDAvTWF4V2lkdGggMTUwMC9TdGVtSCA4NS9TdGVtViA5OC9UeXBlL0ZvbnREZXNjcmlwdG9yL1hIZWlnaHQgNTIzPj4NZW5kb2JqDTMzIDAgb2JqDTw8L0JpdHNQZXJDb21wb25lbnQgOC9Db2xvclNwYWNlIDEzIDAgUi9GaWx0ZXIvRENURGVjb2RlL0hlaWdodCAyNzYvSW50ZW50L1BlcmNlcHR1YWwvSW50ZXJwb2xhdGUgdHJ1ZS9MZW5ndGggNzgyMS9TdWJ0eXBlL0ltYWdlL1R5cGUvWE9iamVjdC9XaWR0aCAzMDA+PnN0cmVhbQ0K/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAoHBwkHBgoJCAkLCwoMDxkQDw4ODx4WFxIZJCAmJSMgIyIoLTkwKCo2KyIjMkQyNjs9QEBAJjBGS0U+Sjk/QD3/2wBDAQsLCw8NDx0QEB09KSMpPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT3/wAARCAEUASwDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD2aiiigAooooAKKKKACiiigAooooAKKKKACiiigAopM1HLcRQqWlkRFAySzACgCWkrCvPGmh2ZKtfxyN/diy5/Ssqb4maYn+qt7qU/7oX+daxo1JbRM3Vgt2dlRXBP8UY/4NMlP+9IBUY+KBzzpf8A5G/+tVfVavYn6xT7noVJXCp8T4P+WmmTD/dcGrdv8SNJlKiWO6hz13R5A/EUPD1V9karU31OvpaybLxRo9+wW31CAt/dZtp/I1qK6uMqQR6g5rFxa3RomnsOopM0ZpDFopKKAFooooAKKKKACiiigAooooAKKKKACiiigAooooAKKKKACiiigAoopKAFopM1navrtlosHmXsoUn7ka8u/wBBTSbdkJtJXZoE4+lYereMNL0lmjeXz5x/yyh+Y/iegri9b8Zahqm5I2NlangIrfOw9z/hXOB0X7ig9+fWuynhOszmniOkTpNT+IGq3eVtEW0ibgbRuf8AM1zdzLPfPvu5pZm9XYmlhPnXCK5OCKszwBGGw54yR6Cu6lThFe6jknOUt2UlhUdBgU/YKnijV9wBY4HXb1oMJx8oYn6VrdGZB5Yo2rUrQtng5I4xSNZSInQlj19qHILEZCDqaTYpGQeKa1uyfeBFCq6Z21HOVyg0AParNlqOo6aQ1jeTQgc7Q2V/I8UxDvHOBS4xVtKS11JV0zq9M+I11EQmp2ySrnmSL5W/LpXZ6Xr+n6woNncKzd424YfhXj5TjimLuhkV1LI68h1OCPxriq4SL1Wh1QxMlo9T3TPpS96820Tx/c2hWHVFNxD/AM9VHzr9fWvQLHULbUrZZ7OZJom7qensfQ1wVKUqe52QqRnsWqKSgVmWLRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABRRRQAUUUUAFITSE1xnirxY0TSafpcgMw4lmH/LP2Hv/ACq4U3N2RM5qCuy34j8YR6WxtLEJPfdCM/LF9ff2rg5Wmublrm7d57h/vOx/Qegp0NvtGTkk9STkmrKJ6V6tKjGmtNzz6lRzeplXFtJGhkcg5647VVALdK2r5QLcoer9Kpw2uO1VJGdyosBbHBq/ZK0MvXKkYOeanSADtUojApIGxGsf34ZZULH+7xxUv2YCdBnCt8uAelNwAKsw2ksgDGNlGMgnrTvYW5CbeQOQEU4PSpYdshKOuJAeferttD+6ZxEwduoY9aq3iSLcKXQqD93b1P40uYVhs1iko5HI7is+WzEblT1rft4yYgSxZT93IwQKq3lm4/eRgsO4xyKLgkYgtNzgDHPHSpZLPHZsfSr9mFecocE4OBVxoQRVRlYUjmhHknCkY9ajkjyMV0ElqDnAFUJrTGeK0TvuIyDERnIyKtaZqd5o12LmykKHo6nlXHoR/WkmRo3z/D3FQvtZSBw3oamUE1YuMmnc9X8OeJ7TX4T5f7q5QfvIWPI9x6itsV4Xb3MtpOk9vI0U0Zyrr1Br1Hwt4qj1yHybjbFeoMsoPEg9V/wry61Dk96Ox6FKtzaPc6Simj9KdXMbhRRRQAUUUUAFFFFABRRRQAUUUUAFFFFABSE4orK8Ra0mh6Y05AaVvliT+83+eacU5OyE2krsyPGHiM2EZ0+xb/TJR8zA/wCqX1+p7VxUFuFHTnvmhBJPM89wS80jFnY9yauIuTXr0qSpxsebUqOo7iLHkdKsQW+SKlhhzV1IQsLljtABJPpVuRFjBvF3XRXAwmBnHWmqgFSyRCOVl378Y5z1ptTcQAUtJS0CLukrE9/GJsY5Iz0zXT+WCMkZ964rvWlYa1cWmyLCyRlgAH7fjWc4t6o0hJLRmnrMSx6dI0cZDZ6pxt9zWPYXu2XyriTMLckuM4Na/iK+a3j+zRpkTpkuT29MVznf1pU9Y6hPSWhuwXcM87QIR8o+U/3vXFWTHgetcyshjkV0IDKcj2rb07WI55lguhtdzhSOhP8ASnJNahFpk8enxSyhxGBt5yOKlls05KnFWcrGp296rSyk1Ck2aciMyf8AdkhuMVQnlHNatwomXa2KyLi2eMkbcr61vBmMoWKEyrJlSMqagjs2lZQOSv8AEfSrnlkEHHcVcWBYyxXPzHJFa6E6nOyoySFWXHNOt5pLa4jmgcpJE25WHY1t3UUUkRE5CgDhvSsm0kVHdCoZSeSR1qLJ6FX6nqfhjxDHr2neYQqXUXyzRjsfUexrbrxrS9Qn0jVEvLUjKnDR9A691r1zT7+HUrKG6tm3RSruU/0rzcRQ9m7rZnfQre0VnuizmlpB1pa5jcKKKKACiiigAooooAKKKKACiikBoARmwCTwAM5ry/X9TOua00ikm3hJjiX+bfjXXeNNVNjpH2eFiJ7o+WuOoX+I1xFtFtUYFd+Dp/bZx4mp9glSOrUMOSKbFGSa0raHpxXZJ2OW1x0EPA4qjqt1uY20f3VP7wjufStabdBaSyKOVUkVzGSSWYkk8kmso6hLQSijvRVkhRRRQAUdQRnrRRSAlnup7op9okL+Wu1c9hUOSzBVGWY4AHemvIAOtVjMzSBYslycAL1/ChaD1bNqDSorjKC8zcYyYo1yF9i3SnX0UGg2olgDXN0zrtZhyo7gY6fWqNpDqWkyJKIz50wKJBtz+LHoK6K0jnRWlu3Xe5+4o4T6HvWUm77m0UuxDp+qDVIWcQvAynBV+tTlc1KFVju2jd645pStK5disYs1BdRboGAq+VqvOrZUKOCeaaYmjFWHdIox3q00foKs/ZvLmZhjHYUMlaqRk4mXd2q3MTRv+B9D61zuHhmIkGGBwRXWyIapXFlDO4d0y4GM1ZKMSc7JlXGCOa67wPqwtbw2Mjf6PcnMWf4ZO4/H+dcddTNNcM7AA/dwPaprKRgw2NtdSGU+hHSoqr2kXFlQfJJSR7ZS1naHqa6tpUF0v3mGHHow4NaGa8hpp2Z6ad1dC0UUUhhRRRQAUUUUAFFFFABSUtUdYvRp+k3VyescZI+vb9aaV3YTdlc4DxHetqniOdgcw2/7mPHTj7x/Oool6VUskbbliSxOTn1PWtOFMkV7MYqEVFHlSfM22TwR57VpwR4HSq9vF0rRjXispyKiipqsbtpc3lqSdo6ema5c/wAq7pV+Ujtg5ripreWFY5JFKxzZaM+ozSpS6BUjbUiopSKStTMKKKQnFAAeKjklApssuKoTTkkgUDsSSzk8KGJ9AM10WjaDPaTxXk04WXbgx7c4B9/WsvwpGLjUpC8bMqAMGHRWB4rsz65rKcuhtCPUik4Yk9e3pSqC5y3SjaXcE9KlAAGBWZoJSYp2KMUgGkU0rUmKMU7gVmSomSrbLUTLxVpktFGRKrsuG5q/ImarSR1rGRjJWOPvIjFdSIeu7NMhbZMpqxqrZ1GX/ZIWqgPOaofQ77wLf+VfT2TH5JV82Me46/piu5615JpV61neWd2DxE43e46H9DXrSsGAI6GvOxUbTv3OzDSvG3YdRRRXMdAUUUUAFFFFABRRRQAVy/ju4KaRDbg8zzAH6DmunriPHUofU7KDJ+RGc/icVvh43qIyru0GYlutaECciqsC8CtK2XpXpSZ5xcgTgVcRahiXGKtAHPBx+Fcs2axQ9dqrlvu45rldUJl022dVISCR4sHqOeM11Egd2CA+5BqjPp7XMd/a9DIRIrY4HH+IpQai7suabVkckTSUuDyCMEHBHpQckYUfMeB9a6jlsIAWOFBJ9AM1FIX5CKzH0AOa762sbbT4kEMSq+0bmI5PFRvFF5/nbF80LtDAc4rFVr9Df2Om55rcu6E71ZM9NwxmptH0l9YncF/LhjwZGAyeewrvpoIrjAmiSRQeAwziuf8AD6pY+IdRsQpRW+aNfYf/AFjVc91oHJZm1Z2UNjCIbSFYogc47sfUmptrZ56U/HNLisbmlhoUdqOlOoxQMbRTsUYp3AbSU/FGKQEZFMYcVKRTWHFUmJlZ1qvItXGFQyCtIszkjiNW41Of/eH8qqdBWr4gtPJvRMCSJevsRWTWqING2/eWhUdxj6V6r4euvtmh2cxOWMQB+o4/pXlOn8qRXongeXfojx/88pmH58/1rmxavBM2wztJo6WikFLXnncFFFFABRRRQAUUUUAJXAeLm3+JSOyQqP5mvQK898Uc+KJv+uafyrqwn8Q58T8BBAp4rTtl6VQgHStO2HSu2ZxIvRLxVlBjtUEQqyorlkbwQqKFJJ5Y9TT1P73HtmjHFRK/7wGs9zXY4zWdi6zdCMYUPz9e9R6UQ+s2qN035GfUV0Gu6Qt6z3FsAs4HzL2f/A1xck8trdKRuSWJgdrDGMV2QkpQscso2lc9BlueD3NMDeYMr261hr4l02VFZ5zE7nlGUkqf8KxLjUrvVda8mwuJo4XYKoQ44HVqyjTNXM7V3SJd8jBV9ScVUjFjLqa3KSRG8VDHjd8xU+1W0ZCirknbgZbnPvUV1p1reqftEKyEdHA+ZfoetIonXI4brS1nW1vqtnKITLFdWgxteVtsoH4daW813T9Puvs95OY3wDkocEHuDRbsK5oUU2KWOVA8UiSIejK3B/GnmkMSiloxQAlLRRQA0imkVIRTSKYiBhULirDConHFWiZI57xKg/s9XPVZR+tcyeldvf2q3ltJAx2h/wCLGce9cZdW0lpcSQyrgqePcdjWyMSzp/3WrvfATf6Pfp6SqfzH/wBauE05d2R6mu88DDaNQ/30/kayxK/ds0oP94kdYKWkpa8w9EKKKKACiiigAooooASuA8Upt8Tuf70SH+dd+elcR4zj2a1bSf8APSHH5H/69dOFf7w58Sv3ZTh4ArStjnFZkB4FaVr0Fd0ziiacNWVFVoelWVrkkdEBWbYv1qDPrUk54A/GoGbFSi2NkfHFZWo2FpqIX7TEGZejA4YfjV2V+tQda1joQznNX8NIIRNpqkMg+aIHO4eo96ytJvo9LvzNcQO7qpVVB27c9TXc9Kr3mnW2oRsk0S5ZcBwPmU+ua0U9LMjl6op6f4htr+5WARyRO2du7kN7VsoSDwa43QrU2/iQwTkF4Q2D6n2rsVPzDFKaS2HF3JY4Y4i3lrt3nc2O5pjWNq0qytbwtIgwrFckVI8kcMZeaREQdWZsCs5fEVhJJMqTIY4Vy0pOBnsoHVqz1L0NBoY3UAxoFB3YAwM1JjNV7S/tb62E1vOjJ0yTgg+nNTGRBII96eYRkJuGT+FAC4ooUhlDKcg9DS0AJilxRRQAhppp9NNAEbCoXAxUzVDJVoiRTuDtjcjsDWNc6cmp2MLFtsyKQr+vsa1r87LaQ+1ULFT5Uh7ZwK6I7GDKljafZ7c78eZnkius8EDC6gfV0H6GucVdkGMY7muq8ExbdMuJOu+c4P0AFZYl2pMvDa1UdIKWkpa8s9MKKKKAEooopiCiiigArlvG0Gba1uAPuSbCfYj/AOtXUVl+IrRr3QrmNRlwu9fqOa0pS5ZpkVVzQaONg5APpWjbt0FZVpIHUEHOea0YGwRXpyPNRsW5+XFXENULduBV1K45nRAjuBiQH2qrK1XZ1Lr8oyRWZK+Se1ESmMY5NIKbnmpY4i65Xse9abEiBGP3RS7GXqKuDGAB0FGPyqbjsY9/p0d4EYMYZo23RzIPmU/1FJb22qSyTLcXSxsi/uGiUYc/7QP8q1zGh/hpgjjjO9mChRkk9AKfMFjhdd1u61FEs7iFITCf3qAdXHGfpWOe2O1XdZuI7rWbuaFg8bSHaw6EetU66YqyMW7sQHB7j8aeJH8wSGR/MHR9x3D8aZS0xHUaH4qdfLttQK+UBgTk8/8AAvWuna9tQVH2qHLcj5hXmFIAPSs5U02WqjR6sPm6YPoRRXH+ENVaK6OnzOzRyDMQ67T3/Cuw/iNYyjZ2NE7q4vammnY4pppARNUL1O1QSHiriRIzdUbFvj1OKbZxERRLjrzU11FHOAHBODkDNKo2RtJ6DA+tb30sYlC6IXf6DNdp4XtzbeH7VWyGZTIc+5zXDzRvcyJAnLzOEH4mvSoYhDCkajAQBR9BXNi5Wiom+EV25ElFJS1wncFFFGaBiUUUUCuFFFFAXCg8jBxjvRQaAPOLi1Onatc2pGFR9ye6nkVbhbOK0/GFiV8nUEX7n7uUj+6eh/P+dY9uwIFepTnzwTPNqR5JtGtbvWjG2RWLHLs5NaMMuRWc4jgy2zYFZ95Fk7159cVZL5puazWhre5nqrH+E1egQrCA3rmkkk8vb8ucnGfSpRyKpsAx6EfjRRiipGIa5Xxhqs0TJp8LlVkTdNj+IHoK6zNcN4wluH1QQzKqwoAYiFxuyOcnvVwV2TN6HPjsOw4FLRnFFdJiFFFFABRSUUAWrO/nsC5tZPKaQbWkVQWx6A9q0U8VanHMGWZTEMDy3Xdx9euaxaM0nFMd2jq38cNuKxWIx2LSf0q7YeKrK8VVnPkXBbbsPIJ9jXD1e0ixk1DU4IoxlUYPIcfdUGocIpDUmegvx1qvIeKnlYEk9jVWRs1MQkyEjJovCIbRYectzU8Ee85PSqGqT7p3I6JxWi1djOWiLHhm0+1a6JWX5LZd3/AjwP0zXcVj+GNPNhpSmQDzpz5j/j0H5VsV5+InzzdjuoQ5IJMKKKKxN7hS0lAoAKKQ9TSUCHUU2igB1J3pKKAGXNul1byQyDKSKVIrz9oJNPvZLWb70ZwD/eHY16J2rB8T6U13bC7gX9/AOQP4l7j8K6MPU5JWezMK9PmV1ujD3HyzjrjipbS8BjHmfI6naQfWqdtMHQc1ZKJKpDqCD1rvcTiTNASe9SIc1lIlxBKnluZI+6v1A+taSOCKxlGxpGRY4I55FOqJWp4NZmqY6ijNFIYVS1fTE1fT5LdgPMAzE391u1XqKAseTOjI7RyDa6EqwPYikrrvF+iSySjULSHduGJwg546NiuRGCK6oyUkYSVmLRRkYoqhBRRRQAUUmaCcUAOVWdlROXdgqj3Neg6Rpcej2Xkod0p+aWQjlj/gK5Dw7Ym+1mLJ/dwYlc/ToPxNd1I2Sazm7uxS0VyORqgwWbAHWnscmp4Ygo3H71LZEbseqCJB7VnaPp51LVcyA+TGd7e/PAq3dTsBsQbnY7UUdz6VvaVp40+zCHBlY7pGHdqyqVOSL7s1p0+eXki8owOcfhS03vRXCdw6im0UAOoptLQAHqaTNB6migQZozRRQAZozRRQAZooooA47X9J/s24N3bgi2kPzKP+WZ/wNVoZAy9a7eSNZY2jkUMjDBU9CK4vUtMk0a4LLue0c/K39z2P+Nd9CrzLllucdalyvmjsSqcVMjVUilBFWAfStmjBMsq1TK1VFapFes3E0UrFoGlqJXzTwazsaKQ+lpB0pe9IdxASO9ZGt+H7LUbWabyhFcIpYSxjBOBnn1rWZgvJIA96qaxcfZ9FvZVZQwibbn1PFCunoDtbU8xXBAI6GlpAMAD0FLXaYBSUUDjmkAvvVuy0i91H5rWBig4LtwoP1NanhbSYb6SW5u03xRYCIR8rN/XFdcXAG0ABR0A6ColJrRAUdI01NJtPLDBpX+aRwOp9PoKsMxNPJz0qSKHHzN1qA3Ehh5DN+VFxMsUZJOAKdLKI1JJAA9ak03TWvHW5uVKxA5jQ/wAXufaockleRUYuTsifRdPbcLy6H7wj92h/hHr9TW1mkxS1xSk5O7O2EVFWQUZooqSgzRmiigAzS5pKKAEPU0maU9TSUwDNGaKKADNGaKKADNGaKKAFzTJIkmjaOVA6MMMpHBFOooA5HVNDl0zdNaBpLbOSo5ZP8RVaC4DAe9dsRmsTUvDizu01iVilPJQ/db/A12UsR0n95y1aHWBnKc09TVAtPaS+VdRmOQdj3+h71YScEda6LdTmuWw+KVrhIhmRsCoBID3qtfQzThDGqsq9Rnmp5R3HS30kjHa21e2OtM+1SYxvOKpMkkX+sRl+tIJKrlHzF4zuwwzkj3rJ8SXDDTUTdxJIAR7CrHm4rC1q6a4uzFwI4jgD1PrQo2Yc1zNHc0tJnFWILG4uFDxx/If4icVoBXJA61raRpaXDebfb0hB+VFGGf8AwFWbCyW2jPmbHcnOducewq6kbyEAZwT1pNCubsSpFAiQoEjA+VR2FOWMyH0qWGALEm7IwBjPWpGkVB1rBy7Dt3EWJUHODUU1wIh654AHOaE+0XpMdmm493P3V/GtWw0iO0IkkJmnPV2HA+g7VnKajvuaRg57bFWx0gystxejpykJ6D3NbQ46UUVyyk5O7OuMFBWQZozRRUlBmjNFFABmjNFFABmlzSUtADWcbj9aTeKRh8x+tJiqshXHbxRvFNxRiiwh28UbxTcUYosA7eKN4puKMUWAdvFG8U3FGKLAO3ijeKbijFFgGXMMN3HsniWRfQ9qw7nw+6ZazkDD/nnJ/Q10GKTbVwnKGzInBS3ORdWtztuI3hI7uOPzqUAkZVgfpXUtGrrtZQw9CM1Qn0SzlcusZif1jYit1iE/iRg6DWzOX1GYlBFGxJz8wHSs8bh1BFdLceFiCWt74IT2kAxWbNpWoQMQHtZlH918H8q6I1YPZmEqc10MwsyDdg4Fc/MJJ5ywjwD6d665ba+B5tc/RxUJ0ecvuW0dD14YY/nVOS7iSl2MjS9Kct5z5U44UitdbTB+Y8D0qylnfNjdGF92YVah0SeUbmngT8aTnFdR8sn0KIjiQep96kRt2ApCjuT2rYh8LQHma5aT2XitO30mygIMcKkju3NYyxEVtqaRoTe5kRNcXOFt4mfAxvfgVeg0UffvXMh/uLwv/wBetUKB04oxXPKq3todEaMVvqNjCRIERQqjoAKfvpuKMVkajt4o3im4oxRZBcdvFG8U3FGKLAO3ijeKbijFFgHbxRvFNxRiiwDt4o3im4pQKLAPYfMfrSYpxHJoxU3KsNxRinYoxRcLDcUYp2KMUXCw3FGKdijFFwsNxRinYoxRcLDcUYp2KZJMkfU89hTAXFMaZEHJGfQVXkkeU46D2poj9qpR7kN9h7XJP3Fx9aYWZurn6Cn7KRvkxxmq0ROpWliGd3X61HgDGAKssS3Hb0pvl57VXMTykOSKrF3DfNmr/l1BcQsHyehouPlEimDDBwDT9qN2qDy6erMgx15p3DlJRGByuQfrUiyyr1c4piHfyTin4x70mw5Wiyk7AfMufxqVZVcY6H0NQocjpTigeoaRSuTgUYqAF4/9pfSpklV/Y+lS9C0xcUYp2KMUrjG4oxTsUYpXCw3FGKdijFFwsNxRinYoxRcLDcUuKXFGKLhYfijFOpKkobijFOopgNxRinUUANxRinUUANxQeBk06kIzQBC8jHhRUXk556n3qzspdlUnYlq5WEXtTvLqfZRto5g5SDy6DCCMVPtpdtHMPlKpg54FHkn0q1to20cwuUq+T7UyaDcnA5q7tHpSFQR0o5g5TL8j2o8j2rR8oUnlCjmDlKHke1PRCOGGRVzyqPL9qOYOUiRAw4p4TFSKmKdto5g5SLbTfLGc1Pto20rhykakjg9KkHNG2lAxSGhMUYp1FAxuKMU6igBuKMU6igBuKXFLRQAtFFFIYlFFFABRRRQAUUUUAGKWiigBKKKKACiiigApaKKACiiigAooooAKSiigAooooAWiiigBKKKKACiiigAooooAKKKKACiiigApaKKAP//ZDQplbmRzdHJlYW0NZW5kb2JqDTEgMCBvYmoNPDwvQ291bnQgMS9LaWRzWzcgMCBSXS9UeXBlL1BhZ2VzPj4NZW5kb2JqDTIgMCBvYmoNPDwvTGVuZ3RoIDMzNjYvU3VidHlwZS9YTUwvVHlwZS9NZXRhZGF0YT4+c3RyZWFtDQo8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzAxNiA5MS4xNjM2MTYsIDIwMTgvMTAvMjktMTY6NTg6NDkgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6cGRmPSJodHRwOi8vbnMuYWRvYmUuY29tL3BkZi8xLjMvIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICAgICAgICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIj4KICAgICAgICAgPHhtcDpDcmVhdGVEYXRlPjIwMTgtMDMtMjFUMTM6NTg6MDdaPC94bXA6Q3JlYXRlRGF0ZT4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5Xb3JkPC94bXA6Q3JlYXRvclRvb2w+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDE5LTA1LTI2VDExOjE3OjE3LTA3OjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcDpNZXRhZGF0YURhdGU+MjAxOS0wNS0yNlQxMToxNzoxNy0wNzowMDwveG1wOk1ldGFkYXRhRGF0ZT4KICAgICAgICAgPHBkZjpLZXl3b3Jkcy8+CiAgICAgICAgIDxwZGY6UHJvZHVjZXI+TWFjIE9TIFggMTAuMTEuNiBRdWFydHogUERGQ29udGV4dDwvcGRmOlByb2R1Y2VyPgogICAgICAgICA8ZGM6Zm9ybWF0PmFwcGxpY2F0aW9uL3BkZjwvZGM6Zm9ybWF0PgogICAgICAgICA8ZGM6dGl0bGU+CiAgICAgICAgICAgIDxyZGY6QWx0PgogICAgICAgICAgICAgICA8cmRmOmxpIHhtbDpsYW5nPSJ4LWRlZmF1bHQiPk1pY3Jvc29mdCBXb3JkIC0gV29ybGRfV2lkZV9Db3JwX2xvcmVtLmRvY3g8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6QWx0PgogICAgICAgICA8L2RjOnRpdGxlPgogICAgICAgICA8eG1wTU06RG9jdW1lbnRJRD51dWlkOjJlNjBiMTYyLTY4MmQtNGZjOS1hYjFjLTcwMTQ0OTllMGQ0OTwveG1wTU06RG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+dXVpZDplYmRjZjYzOS02NWNjLTQ0YTgtODEyMi02ZDA2YWFjNzI3MDI8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz4NCmVuZHN0cmVhbQ1lbmRvYmoNMyAwIG9iag1bXQ1lbmRvYmoNNCAwIG9iag08PC9BQVBMOktleXdvcmRzIDMgMCBSL0NyZWF0aW9uRGF0ZShEOjIwMTgwMzIxMTM1ODA3WikvQ3JlYXRvcihXb3JkKS9LZXl3b3JkcygpL01vZERhdGUoRDoyMDE5MDUyNjExMTcxNy0wNycwMCcpL1Byb2R1Y2VyKE1hYyBPUyBYIDEwLjExLjYgUXVhcnR6IFBERkNvbnRleHQpL1RpdGxlKE1pY3Jvc29mdCBXb3JkIC0gV29ybGRfV2lkZV9Db3JwX2xvcmVtLmRvY3gpPj4NZW5kb2JqDXhyZWYNCjAgNQ0KMDAwMDAwMDAwMCA2NTUzNSBmDQowMDAwMDM4NzQzIDAwMDAwIG4NCjAwMDAwMzg3OTQgMDAwMDAgbg0KMDAwMDA0MjIzNyAwMDAwMCBuDQowMDAwMDQyMjU1IDAwMDAwIG4NCnRyYWlsZXINCjw8L1NpemUgNS9JRFs8OTNEREQ1RjRBQjk1NTU2NTVFMUFFQkU3Mjc4OTFGNzQ+PDUyRUNGNjUzRTlDQTM4NDNBMEI2MTY0ODI1RkZENjJDPl0+Pg0Kc3RhcnR4cmVmDQoxMTYNCiUlRU9GDQo=", - document_id="1", # a label used to reference the doc - file_extension="pdf", # many different document types are accepted - name="Lorem" # can be different from actual file name - ) - envelope_definition.documents = [document1] - envelope_definition.status = args["envelope_args"]["status"] - signer1 = Signer( - email=args["envelope_args"]["signer_email"], # represents your {signer_email} - name=args["envelope_args"]["signer_name"], # represents your {signer_name} - id_check_configuration_name="ID Check", # configuration name for KBA based Verification - require_id_lookup="true", - recipient_id="1", - routing_order="1" - ) - # Create your signature tab - sign_here1 = SignHere( - name="SignHereTab", - x_position="75", - y_position="572", - tab_label="SignHereTab", - page_number="1", - document_id="1", - # A 1- to 8-digit integer or 32-character GUID to match recipient IDs on your own systems. - # This value is referenced in the Tabs element below to assign tabs on a per-recipient basis. - recipient_id="1" # represents your {RECIPIENT_ID} - ) - - # Add the tabs model (including the sign_here tabs) to the signer - # The Tabs object wants arrays of the different field/tab types - signer1.tabs = Tabs(sign_here_tabs=[sign_here1]) - - # Tabs are set per recipient - envelope_definition.recipients = Recipients(signers=[signer1]) - # Step 4: Call the eSignature REST API - envelope_api = EnvelopesApi(api_client) - results = envelope_api.create_envelope(args["account_id"], envelope_definition=envelope_definition) - envelope_id = results.envelope_id - app.logger.info(f"Envelope was created. EnvelopeId {envelope_id} ") - - return render_template("example_done.html", - title="Envelope sent", - h1="Envelope sent", - message=f"""The envelope has been created and sent!
- Envelope ID {envelope_id}.""" - ) - - except ApiException as err: - error_body_json = err and hasattr(err, "body") and err.body - # we can pull the DocuSign error code and message from the response body - error_body = json.loads(error_body_json) - error_code = error_body and "errorCode" in error_body and error_body["errorCode"] - error_message = error_body and "message" in error_body and error_body["message"] - # In production, may want to provide customized error messages and - # remediation advice to the user. - return render_template("error.html", - err=err, - error_code=error_code, - error_message=error_message - ) - - - - else: - flash("Sorry, you need to re-authenticate.") - # We could store the parameters of the requested operation - # so it could be restarted automatically. - # But since it should be rare to have a token issue here, - # we'll make the user re-enter the form data after - # authentication. - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - - - -def get_controller(): - """responds with the form for the example""" - - if views.ds_token_ok(): - return render_template("eg022_kba_authentication.html", - title="Kba recipient authentication", - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - signer_name=ds_config.DS_CONFIG["signer_name"], - signer_email=ds_config.DS_CONFIG["signer_email"] - ) - else: - # Save the current operation so it will be resumed after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - - diff --git a/app/eg023_idv_authentication.py b/app/eg023_idv_authentication.py deleted file mode 100644 index 1393c620..00000000 --- a/app/eg023_idv_authentication.py +++ /dev/null @@ -1,157 +0,0 @@ -""" Example 023: ID Verificiation Based authentication""" - -from flask import render_template, url_for, redirect, session, flash, request -from os import path -from app import app, ds_config, views -import base64 -import re -import json -from docusign_esign import * -from docusign_esign.client.api_exception import ApiException - -eg = "eg023" # Reference (and URL) for this example -demo_docs_path = path.abspath(path.join(path.dirname(path.realpath(__file__)), "static/demo_documents")) - -def controller(): - """Controller router using the HTTP method""" - if request.method == "GET": - return get_controller() - elif request.method == "POST": - return create_controller() - else: - return render_template("404.html"), 404 - -def create_controller(): - """ - 1. Check the token - 2. Call the worker method - """ - minimum_buffer_min = 3 - if views.ds_token_ok(minimum_buffer_min): - - # More data validation would be a good idea here - # Strip anything other than the characters listed - pattern = re.compile("([^\w \-\@\.\,])+") - signer_email = pattern.sub("", request.form.get("signer_email")) - signer_name = pattern.sub("", request.form.get("signer_name")) - envelope_args = { - "signer_email": signer_email, - "signer_name": signer_name, - "status": "sent", - } - args = { - - # Step 1: Obtain your OAuth token - "account_id": session["ds_account_id"], # represents your {ACCOUNT_ID} - "base_path": session["ds_base_path"], - "ds_access_token": session["ds_access_token"], # represnts your {ACCESS_TOKEN} - "envelope_args": envelope_args - } - try: - - # Step 2: Construct your API headers - api_client = ApiClient() - api_client.host = args["base_path"] - api_client.set_default_header("Authorization", "Bearer " + args["ds_access_token"]) - - # Step 3: Retrieve the workflow ID - workflow_details = AccountsApi(api_client) - workflow_response = workflow_details.get_account_identity_verification(args["account_id"]) - workflow_id = workflow_response.identity_verification[0].workflow_id - app.logger.info("We found the following workflowID: " + workflow_id) - - # Step 4: Construct your envelope JSON body - envelope_definition = EnvelopeDefinition( - email_subject="Please sign this document set" - ) - # Add a document - document1 = Document( # Create the DocuSign Document object - document_base64="JVBERi0xLjMNJeLjz9MNCjUgMCBvYmoNPDwvTGluZWFyaXplZCAxL0wgNDI3MTAvTyA3L0UgMzg3NDMvTiAxL1QgNDI0OTEvSCBbIDg5NiAxODVdPj4NZW5kb2JqDSAgICAgICAgICAgICAgICAgICAgDQp4cmVmDQo1IDMwDQowMDAwMDAwMDE2IDAwMDAwIG4NCjAwMDAwMDEwODEgMDAwMDAgbg0KMDAwMDAwMTE0MSAwMDAwMCBuDQowMDAwMDAxMzE4IDAwMDAwIG4NCjAwMDAwMDE0NzkgMDAwMDAgbg0KMDAwMDAwMTg0OCAwMDAwMCBuDQowMDAwMDAxOTk2IDAwMDAwIG4NCjAwMDAwMDIxOTcgMDAwMDAgbg0KMDAwMDAwMjYyMSAwMDAwMCBuDQowMDAwMDAyNjU2IDAwMDAwIG4NCjAwMDAwMDMzOTYgMDAwMDAgbg0KMDAwMDAwMzkwMSAwMDAwMCBuDQowMDAwMDA0NDExIDAwMDAwIG4NCjAwMDAwMDUwMTEgMDAwMDAgbg0KMDAwMDAwNTUzMCAwMDAwMCBuDQowMDAwMDA2MDQ5IDAwMDAwIG4NCjAwMDAwMDY1ODcgMDAwMDAgbg0KMDAwMDAwNjk4MyAwMDAwMCBuDQowMDAwMDA5NjkwIDAwMDAwIG4NCjAwMDAwMTYzMjUgMDAwMDAgbg0KMDAwMDAxNjU0NyAwMDAwMCBuDQowMDAwMDE3MDg3IDAwMDAwIG4NCjAwMDAwMTczMDYgMDAwMDAgbg0KMDAwMDAxNzYwMCAwMDAwMCBuDQowMDAwMDE5NTcxIDAwMDAwIG4NCjAwMDAwMTk3OTUgMDAwMDAgbg0KMDAwMDAyMDE3MiAwMDAwMCBuDQowMDAwMDMwNTAxIDAwMDAwIG4NCjAwMDAwMzA3MzMgMDAwMDAgbg0KMDAwMDAwMDg5NiAwMDAwMCBuDQp0cmFpbGVyDQo8PC9TaXplIDM1L1Jvb3QgNiAwIFIvSW5mbyA0IDAgUi9JRFs8OTNEREQ1RjRBQjk1NTU2NTVFMUFFQkU3Mjc4OTFGNzQ+PDUyRUNGNjUzRTlDQTM4NDNBMEI2MTY0ODI1RkZENjJDPl0vUHJldiA0MjQ4MT4+DQpzdGFydHhyZWYNCjANCiUlRU9GDQogICAgICAgICAgICAgICAgDQozNCAwIG9iag08PC9GaWx0ZXIvRmxhdGVEZWNvZGUvSSAxMTYvTGVuZ3RoIDEwNC9TIDQwPj5zdHJlYW0NCmjeYmBgkGZgYN7DAASTHjGgAmYgZmHgWIAqKg3FDAzKDHxMFuwPghsKmWZIBDAwHWSPkN3Q6/iEfYJ8QZRXQboC94Y6hx0sPJUM+o5hC27whJ88ADWDhYFhSRiQZgTiRwABBgBLlxXzDQplbmRzdHJlYW0NZW5kb2JqDTYgMCBvYmoNPDwvTWV0YWRhdGEgMiAwIFIvUGFnZXMgMSAwIFIvVHlwZS9DYXRhbG9nPj4NZW5kb2JqDTcgMCBvYmoNPDwvQ29udGVudHNbMTQgMCBSIDE1IDAgUiAxNiAwIFIgMTcgMCBSIDE4IDAgUiAxOSAwIFIgMjAgMCBSIDIxIDAgUl0vQ3JvcEJveFswIDAgNjEyIDc5Ml0vTWVkaWFCb3hbMCAwIDYxMiA3OTJdL1BhcmVudCAxIDAgUi9SZXNvdXJjZXMgOCAwIFIvUm90YXRlIDAvVHlwZS9QYWdlPj4NZW5kb2JqDTggMCBvYmoNPDwvQ29sb3JTcGFjZTw8L0NzMSAxMyAwIFI+Pi9Gb250PDwvVFQxIDkgMCBSL1RUMyAxMCAwIFIvVFQ1IDExIDAgUi9UVDYgMTIgMCBSPj4vUHJvY1NldFsvUERGL1RleHQvSW1hZ2VCL0ltYWdlQy9JbWFnZUldL1hPYmplY3Q8PC9JbTEgMzMgMCBSPj4+Pg1lbmRvYmoNOSAwIG9iag08PC9CYXNlRm9udC9aUFFQU0ErVHJlYnVjaGV0TVMvRW5jb2RpbmcvTWFjUm9tYW5FbmNvZGluZy9GaXJzdENoYXIgMzIvRm9udERlc2NyaXB0b3IgMjQgMCBSL0xhc3RDaGFyIDExOC9TdWJ0eXBlL1RydWVUeXBlL1R5cGUvRm9udC9XaWR0aHNbMzAxIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgNTk4IDYxMyAwIDAgMCAwIDI3OCAwIDAgNTA2IDAgMCAwIDAgMCAwIDAgMCAwIDAgODUyIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDU1NyA1NDUgMzcwIDAgMCAyODUgMCAwIDI5NSA4MzAgNTQ2IDUzNyA1NTcgMCAzODkgNDA1IDAgNTQ2IDQ5MF0+Pg1lbmRvYmoNMTAgMCBvYmoNPDwvQmFzZUZvbnQvTVVLRlJOK0NhbGlicmkvRmlyc3RDaGFyIDMzL0ZvbnREZXNjcmlwdG9yIDI2IDAgUi9MYXN0Q2hhciAzMy9TdWJ0eXBlL1RydWVUeXBlL1RvVW5pY29kZSAyNyAwIFIvVHlwZS9Gb250L1dpZHRoc1syMjZdPj4NZW5kb2JqDTExIDAgb2JqDTw8L0Jhc2VGb250L0hGQU1aRitDYWxpYnJpLUJvbGQvRmlyc3RDaGFyIDMzL0ZvbnREZXNjcmlwdG9yIDI5IDAgUi9MYXN0Q2hhciA0NS9TdWJ0eXBlL1RydWVUeXBlL1RvVW5pY29kZSAzMCAwIFIvVHlwZS9Gb250L1dpZHRoc1syMjYgNjA2IDQ3NCAzNTUgNTAzIDUzNyA0OTQgNTM3IDM5OSAyNDYgMjc2IDQzMCA1MDddPj4NZW5kb2JqDTEyIDAgb2JqDTw8L0Jhc2VGb250L1VHSkVDSCtIZWx2ZXRpY2EvRW5jb2RpbmcvTWFjUm9tYW5FbmNvZGluZy9GaXJzdENoYXIgMzIvRm9udERlc2NyaXB0b3IgMzIgMCBSL0xhc3RDaGFyIDEyMi9TdWJ0eXBlL1RydWVUeXBlL1R5cGUvRm9udC9XaWR0aHNbMjc4IDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAyNzggMzMzIDI3OCAwIDAgMCA1NTYgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDY2NyA2NjcgNzIyIDcyMiAwIDAgMCAwIDI3OCAwIDY2NyA1NTYgMCA3MjIgNzc4IDY2NyAwIDcyMiAwIDYxMSA3MjIgMCAwIDY2NyAwIDAgMCAwIDAgMCAwIDAgNTU2IDU1NiA1MDAgNTU2IDU1NiAyNzggNTU2IDU1NiAyMjIgMCA1MDAgMjIyIDgzMyA1NTYgNTU2IDU1NiAwIDMzMyA1MDAgMjc4IDU1NiA1MDAgNzIyIDUwMCA1MDAgNTAwXT4+DWVuZG9iag0xMyAwIG9iag1bL0lDQ0Jhc2VkIDIyIDAgUl0NZW5kb2JqDTE0IDAgb2JqDTw8L0ZpbHRlci9GbGF0ZURlY29kZS9MZW5ndGggNjcwPj5z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ACiiigAooooAKKKKACiiigApaKKAP//ZDQplbmRzdHJlYW0NZW5kb2JqDTEgMCBvYmoNPDwvQ291bnQgMS9LaWRzWzcgMCBSXS9UeXBlL1BhZ2VzPj4NZW5kb2JqDTIgMCBvYmoNPDwvTGVuZ3RoIDMzNjYvU3VidHlwZS9YTUwvVHlwZS9NZXRhZGF0YT4+c3RyZWFtDQo8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzAxNiA5MS4xNjM2MTYsIDIwMTgvMTAvMjktMTY6NTg6NDkgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgICAgICAgICAgeG1sbnM6cGRmPSJodHRwOi8vbnMuYWRvYmUuY29tL3BkZi8xLjMvIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICAgICAgICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIj4KICAgICAgICAgPHhtcDpDcmVhdGVEYXRlPjIwMTgtMDMtMjFUMTM6NTg6MDdaPC94bXA6Q3JlYXRlRGF0ZT4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5Xb3JkPC94bXA6Q3JlYXRvclRvb2w+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDE5LTA1LTI2VDExOjE3OjE3LTA3OjAwPC94bXA6TW9kaWZ5RGF0ZT4KICAgICAgICAgPHhtcDpNZXRhZGF0YURhdGU+MjAxOS0wNS0yNlQxMToxNzoxNy0wNzowMDwveG1wOk1ldGFkYXRhRGF0ZT4KICAgICAgICAgPHBkZjpLZXl3b3Jkcy8+CiAgICAgICAgIDxwZGY6UHJvZHVjZXI+TWFjIE9TIFggMTAuMTEuNiBRdWFydHogUERGQ29udGV4dDwvcGRmOlByb2R1Y2VyPgogICAgICAgICA8ZGM6Zm9ybWF0PmFwcGxpY2F0aW9uL3BkZjwvZGM6Zm9ybWF0PgogICAgICAgICA8ZGM6dGl0bGU+CiAgICAgICAgICAgIDxyZGY6QWx0PgogICAgICAgICAgICAgICA8cmRmOmxpIHhtbDpsYW5nPSJ4LWRlZmF1bHQiPk1pY3Jvc29mdCBXb3JkIC0gV29ybGRfV2lkZV9Db3JwX2xvcmVtLmRvY3g8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6QWx0PgogICAgICAgICA8L2RjOnRpdGxlPgogICAgICAgICA8eG1wTU06RG9jdW1lbnRJRD51dWlkOjJlNjBiMTYyLTY4MmQtNGZjOS1hYjFjLTcwMTQ0OTllMGQ0OTwveG1wTU06RG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+dXVpZDplYmRjZjYzOS02NWNjLTQ0YTgtODEyMi02ZDA2YWFjNzI3MDI8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz4NCmVuZHN0cmVhbQ1lbmRvYmoNMyAwIG9iag1bXQ1lbmRvYmoNNCAwIG9iag08PC9BQVBMOktleXdvcmRzIDMgMCBSL0NyZWF0aW9uRGF0ZShEOjIwMTgwMzIxMTM1ODA3WikvQ3JlYXRvcihXb3JkKS9LZXl3b3JkcygpL01vZERhdGUoRDoyMDE5MDUyNjExMTcxNy0wNycwMCcpL1Byb2R1Y2VyKE1hYyBPUyBYIDEwLjExLjYgUXVhcnR6IFBERkNvbnRleHQpL1RpdGxlKE1pY3Jvc29mdCBXb3JkIC0gV29ybGRfV2lkZV9Db3JwX2xvcmVtLmRvY3gpPj4NZW5kb2JqDXhyZWYNCjAgNQ0KMDAwMDAwMDAwMCA2NTUzNSBmDQowMDAwMDM4NzQzIDAwMDAwIG4NCjAwMDAwMzg3OTQgMDAwMDAgbg0KMDAwMDA0MjIzNyAwMDAwMCBuDQowMDAwMDQyMjU1IDAwMDAwIG4NCnRyYWlsZXINCjw8L1NpemUgNS9JRFs8OTNEREQ1RjRBQjk1NTU2NTVFMUFFQkU3Mjc4OTFGNzQ+PDUyRUNGNjUzRTlDQTM4NDNBMEI2MTY0ODI1RkZENjJDPl0+Pg0Kc3RhcnR4cmVmDQoxMTYNCiUlRU9GDQo=", - document_id="1", # A label used to reference the doc - file_extension="pdf", # Many different document types are accepted - name="Lorem" # Can be different from actual file name - ) - envelope_definition.documents = [document1] - envelope_definition.status = args["envelope_args"]["status"] - - # Create your signature tab - sign_here1 = SignHere( - name="SignHereTab", - x_position="75", - y_position="572", - tab_label="SignHereTab", - page_number="1", - document_id="1", - # A 1- to 8-digit integer or 32-character GUID to match recipient IDs on your own systems. - # This value is referenced in the Tabs element below to assign tabs on a per-recipient basis. - recipient_id="1" # represents your {RECIPIENT_ID} - ) - - signer1 = Signer( - email=args["envelope_args"]["signer_email"], # Represents your {signer_email} - name=args["envelope_args"]["signer_name"], # Represents your {signer_name} - role_name = "", - note = "", - status = "created", - delivery_method = "email", - recipient_id = "1", # Represents your {RECIPIENT_ID} - routing_order="1", - identity_verification = { "workflowId" : workflow_id, "steps": "null" }, - tabs = Tabs(sign_here_tabs=[sign_here1]) - ) - - # Tabs are set per recipient - envelope_definition.recipients = Recipients(signers=[signer1]) - - # Step 5: Call the eSignature REST API - envelopes_api = EnvelopesApi(api_client) - results = envelopes_api.create_envelope(args["account_id"], envelope_definition=envelope_definition) - envelope_id = results.envelope_id - app.logger.info(f"Envelope was created. EnvelopeId {envelope_id} ") - - return render_template("example_done.html", - title="Envelope sent", - h1="Envelope sent", - message=f"""The envelope has been created and sent!
- Envelope ID {envelope_id}.""" - ) - - except ApiException as err: - error_body_json = err and hasattr(err, "body") and err.body - # We can pull the DocuSign error code and message from the response body - error_body = json.loads(error_body_json) - error_code = error_body and "errorCode" in error_body and error_body["errorCode"] - error_message = error_body and "message" in error_body and error_body["message"] - # In production, you may want to provide customized error messages and - # remediation advice to the user - return render_template("error.html", - err=err, - error_code=error_code, - error_message=error_message - ) - - else: - flash("Sorry, you need to re-authenticate.") - # We could store the parameters of the requested operation so it could be restarted - # automatically. But since it should be rare to have a token issue here, - # we'll make the user re-enter the form data after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) - -def get_controller(): - """responds with the form for the example""" - - if views.ds_token_ok(): - return render_template("eg023_idv_authentication.html", - title="IDV authentication", - source_file=path.basename(__file__), - source_url=ds_config.DS_CONFIG["github_example_url"] + path.basename(__file__), - documentation=ds_config.DS_CONFIG["documentation"] + eg, - show_doc=ds_config.DS_CONFIG["documentation"], - signer_name=ds_config.DS_CONFIG["signer_name"], - signer_email=ds_config.DS_CONFIG["signer_email"] - ) - else: - # Save the current operation so it will be resumed after authentication - session["eg"] = url_for(eg) - return redirect(url_for("ds_must_authenticate")) \ No newline at end of file diff --git a/app/error_handlers.py b/app/error_handlers.py new file mode 100644 index 00000000..eed19ab7 --- /dev/null +++ b/app/error_handlers.py @@ -0,0 +1,19 @@ +import json + +from flask import render_template + + +def process_error(err): + error_body_json = err and hasattr(err, "body") and err.body + # we can pull the DocuSign error code and message from the response body + error_body = json.loads(error_body_json) + error_code = error_body and "errorCode" in error_body and error_body["errorCode"] + error_message = error_body and "message" in error_body and error_body["message"] + # In production, may want to provide customized error messages and + # remediation advice to the user. + return render_template( + "error.html", + err=err, + error_code=error_code, + error_message=error_message + ) diff --git a/app/examples/__init__.py b/app/examples/__init__.py new file mode 100644 index 00000000..7e0d3fba --- /dev/null +++ b/app/examples/__init__.py @@ -0,0 +1,31 @@ +from .eg001_embedded_signing import eg001 +from .eg002_signing_via_email import eg002 +from .eg003_list_envelopes import eg003 +from .eg004_envelope_info import eg004 +from .eg005_envelope_recipients import eg005 +from .eg006_envelope_docs import eg006 +from .eg007_envelope_get_doc import eg007 +from .eg008_create_template import eg008 +from .eg009_use_template import eg009 +from .eg010_send_binary_docs import eg010 +from .eg011_embedded_sending import eg011 +from .eg012_embedded_console import eg012 +from .eg013_add_doc_to_template import eg013 +from .eg014_collect_payment import eg014 +from .eg015_envelope_tab_data import eg015 +from .eg016_set_tab_values import eg016 +from .eg017_set_template_tab_values import eg017 +from .eg018_envelope_custom_field_data import eg018 +from .eg019_access_code_authentication import eg019 +from .eg020_sms_authentication import eg020 +from .eg021_phone_authentication import eg021 +from .eg022_kba_authentication import eg022 +from .eg023_idv_authentication import eg023 +from .eg024_permissions_creating import eg024 +from .eg025_permissions_set_user_group import eg025 +from .eg026_permissions_change_single_setting import eg026 +from .eg027_permissions_delete import eg027 +from .eg028_brand_creating import eg028 +from .eg029_brands_apply_to_envelope import eg029 +from .eg030_brands_apply_to_template import eg030 +from .eg031_bulk_send import eg031 diff --git a/app/examples/eg001_embedded_signing/__init__.py b/app/examples/eg001_embedded_signing/__init__.py new file mode 100644 index 00000000..11c5cf04 --- /dev/null +++ b/app/examples/eg001_embedded_signing/__init__.py @@ -0,0 +1 @@ +from .views import eg001 diff --git a/app/examples/eg001_embedded_signing/controller.py b/app/examples/eg001_embedded_signing/controller.py new file mode 100644 index 00000000..29ba1074 --- /dev/null +++ b/app/examples/eg001_embedded_signing/controller.py @@ -0,0 +1,134 @@ +import base64 +from os import path + +from docusign_esign import EnvelopesApi, RecipientViewRequest, Document, Signer, EnvelopeDefinition, SignHere, Tabs, \ + Recipients +from flask import session, url_for, request + +from ...consts import authentication_method, demo_docs_path, pattern, signer_client_id +from ...docusign import create_api_client +from ...ds_config import DS_CONFIG + + +class Eg001Controller: + @staticmethod + def get_args(): + """Get request and session arguments""" + # More data validation would be a good idea here + # Strip anything other than characters listed + # 1. Parse request arguments + signer_email = pattern.sub("", request.form.get("signer_email")) + signer_name = pattern.sub("", request.form.get("signer_name")) + envelope_args = { + "signer_email": signer_email, + "signer_name": signer_name, + "signer_client_id": signer_client_id, + "ds_return_url": url_for("ds.ds_return", _external=True), + } + args = { + "account_id": session["ds_account_id"], + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], + "envelope_args": envelope_args + } + return args + + @classmethod + def worker(cls, args): + """ + 1. Create the envelope request object + 2. Send the envelope + 3. Create the Recipient View request object + 4. Obtain the recipient_view_url for the signing ceremony + """ + envelope_args = args["envelope_args"] + # 1. Create the envelope request object + envelope_definition = cls.make_envelope(envelope_args) + + # 2. call Envelopes::create API method + # Exceptions will be caught by the calling function + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + envelope_api = EnvelopesApi(api_client) + results = envelope_api.create_envelope(account_id=args["account_id"], envelope_definition=envelope_definition) + + envelope_id = results.envelope_id + + # 3. Create the Recipient View request object + recipient_view_request = RecipientViewRequest( + authentication_method=authentication_method, + client_user_id=envelope_args["signer_client_id"], + recipient_id="1", + return_url=envelope_args["ds_return_url"], + user_name=envelope_args["signer_name"], + email=envelope_args["signer_email"] + ) + # 4. Obtain the recipient_view_url for the signing ceremony + # Exceptions will be caught by the calling function + results = envelope_api.create_recipient_view( + account_id=args["account_id"], + envelope_id=envelope_id, + recipient_view_request=recipient_view_request + ) + + return {"envelope_id": envelope_id, "redirect_url": results.url} + + @classmethod + def make_envelope(cls, args): + """ + Creates envelope + args -- parameters for the envelope: + signer_email, signer_name, signer_client_id + returns an envelope definition + """ + + # document 1 (pdf) has tag /sn1/ + # + # The envelope has one recipient. + # recipient 1 - signer + with open(path.join(demo_docs_path, DS_CONFIG["doc_pdf"]), "rb") as file: + content_bytes = file.read() + base64_file_content = base64.b64encode(content_bytes).decode("ascii") + + # Create the document model + document = Document( # create the DocuSign document object + document_base64=base64_file_content, + name="Example document", # can be different from actual file name + file_extension="pdf", # many different document types are accepted + document_id=1 # a label used to reference the doc + ) + + # Create the signer recipient model + signer = Signer( + # The signer + email=args["signer_email"], + name=args["signer_name"], + recipient_id="1", + routing_order="1", + # Setting the client_user_id marks the signer as embedded + client_user_id=args["signer_client_id"] + ) + + # Create a sign_here tab (field on the document) + sign_here = SignHere( + # DocuSign SignHere field/tab + anchor_string="/sn1/", + anchor_units="pixels", + anchor_y_offset="10", + anchor_x_offset="20" + ) + + # Add the tabs model (including the sign_here tab) to the signer + # The Tabs object wants arrays of the different field/tab types + signer.tabs = Tabs(sign_here_tabs=[sign_here]) + + # Next, create the top level envelope definition and populate it. + envelope_definition = EnvelopeDefinition( + email_subject="Please sign this document sent from the Python SDK", + documents=[document], + # The Recipients object wants arrays for each recipient type + recipients=Recipients(signers=[signer]), + status="sent" # requests that the envelope be created and sent. + ) + + return envelope_definition diff --git a/app/examples/eg001_embedded_signing/views.py b/app/examples/eg001_embedded_signing/views.py new file mode 100644 index 00000000..7599778d --- /dev/null +++ b/app/examples/eg001_embedded_signing/views.py @@ -0,0 +1,53 @@ +"""Example 001: Embedded Signing Ceremony""" + +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import render_template, redirect, Blueprint + +from .controller import Eg001Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg001" # reference (and url) for this example +eg001 = Blueprint("eg001", __name__) + + +@eg001.route("/eg001", methods=["POST"]) +@authenticate(eg=eg) +def embedded_signing(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Redirect the user to the signing ceremony + """ + try: + # 1. Get required arguments + args = Eg001Controller.get_args() + # 2. Call the worker method + results = Eg001Controller.worker(args) + except ApiException as err: + return process_error(err) + + # 3. Redirect the user to the Signing Ceremony + # Don"t use an iFrame! + # State can be stored/recovered using the framework"s session or a + # query parameter on the returnUrl (see the makeRecipientViewRequest method) + return redirect(results["redirect_url"]) + + +@eg001.route("/eg001", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """responds with the form for the example""" + return render_template( + "eg001_embedded_signing.html", + title="Embedded Signing Ceremony", + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + signer_name=DS_CONFIG["signer_name"], + signer_email=DS_CONFIG["signer_email"] + ) diff --git a/app/examples/eg002_signing_via_email/__init__.py b/app/examples/eg002_signing_via_email/__init__.py new file mode 100644 index 00000000..1b2ca36d --- /dev/null +++ b/app/examples/eg002_signing_via_email/__init__.py @@ -0,0 +1 @@ +from .views import eg002 diff --git a/app/examples/eg002_signing_via_email/controller.py b/app/examples/eg002_signing_via_email/controller.py new file mode 100644 index 00000000..c512ed77 --- /dev/null +++ b/app/examples/eg002_signing_via_email/controller.py @@ -0,0 +1,199 @@ +import base64 +from os import path + +from docusign_esign import EnvelopesApi, EnvelopeDefinition, Document, Signer, CarbonCopy, SignHere, Tabs, Recipients +from flask import session, request + +from ...consts import demo_docs_path, pattern +from ...docusign import create_api_client +from ...ds_config import DS_CONFIG + + +class Eg002Controller: + @staticmethod + def get_args(): + """Get request and session arguments""" + + # More data validation would be a good idea here + # Strip anything other than characters listed + signer_email = pattern.sub("", request.form.get("signer_email")) + signer_name = pattern.sub("", request.form.get("signer_name")) + cc_email = pattern.sub("", request.form.get("cc_email")) + cc_name = pattern.sub("", request.form.get("cc_name")) + envelope_args = { + "signer_email": signer_email, + "signer_name": signer_name, + "cc_email": cc_email, + "cc_name": cc_name, + "status": "sent", + } + args = { + "account_id": session["ds_account_id"], + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], + "envelope_args": envelope_args + } + return args + + @classmethod + def worker(cls, args): + """ + 1. Create the envelope request object + 2. Send the envelope + """ + + envelope_args = args["envelope_args"] + # 1. Create the envelope request object + envelope_definition = cls.make_envelope(envelope_args) + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + # 2. call Envelopes::create API method + # Exceptions will be caught by the calling function + envelopes_api = EnvelopesApi(api_client) + results = envelopes_api.create_envelope(account_id=args["account_id"], envelope_definition=envelope_definition) + + envelope_id = results.envelope_id + + return {"envelope_id": envelope_id} + + @classmethod + def make_envelope(cls, args): + """ + Creates envelope + Document 1: An HTML document. + Document 2: A Word .docx document. + Document 3: A PDF document. + DocuSign will convert all of the documents to the PDF format. + The recipients" field tags are placed using anchor strings. + """ + + # document 1 (html) has sign here anchor tag **signature_1** + # document 2 (docx) has sign here anchor tag /sn1/ + # document 3 (pdf) has sign here anchor tag /sn1/ + # + # The envelope has two recipients. + # recipient 1 - signer + # recipient 2 - cc + # The envelope will be sent first to the signer. + # After it is signed, a copy is sent to the cc person. + + # create the envelope definition + env = EnvelopeDefinition( + email_subject="Please sign this document set" + ) + doc1_b64 = base64.b64encode(bytes(cls.create_document1(args), "utf-8")).decode("ascii") + # read files 2 and 3 from a local directory + # The reads could raise an exception if the file is not available! + with open(path.join(demo_docs_path, DS_CONFIG["doc_docx"]), "rb") as file: + doc2_docx_bytes = file.read() + doc2_b64 = base64.b64encode(doc2_docx_bytes).decode("ascii") + with open(path.join(demo_docs_path, DS_CONFIG["doc_pdf"]), "rb") as file: + doc3_pdf_bytes = file.read() + doc3_b64 = base64.b64encode(doc3_pdf_bytes).decode("ascii") + + # Create the document models + document1 = Document( # create the DocuSign document object + document_base64=doc1_b64, + name="Order acknowledgement", # can be different from actual file name + file_extension="html", # many different document types are accepted + document_id="1" # a label used to reference the doc + ) + document2 = Document( # create the DocuSign document object + document_base64=doc2_b64, + name="Battle Plan", # can be different from actual file name + file_extension="docx", # many different document types are accepted + document_id="2" # a label used to reference the doc + ) + document3 = Document( # create the DocuSign document object + document_base64=doc3_b64, + name="Lorem Ipsum", # can be different from actual file name + file_extension="pdf", # many different document types are accepted + document_id="3" # a label used to reference the doc + ) + # The order in the docs array determines the order in the envelope + env.documents = [document1, document2, document3] + + # Create the signer recipient model + signer1 = Signer( + email=args["signer_email"], + name=args["signer_name"], + recipient_id="1", + routing_order="1" + ) + # routingOrder (lower means earlier) determines the order of deliveries + # to the recipients. Parallel routing order is supported by using the + # same integer as the order for two or more recipients. + + # create a cc recipient to receive a copy of the documents + cc1 = CarbonCopy( + email=args["cc_email"], + name=args["cc_name"], + recipient_id="2", + routing_order="2" + ) + + # Create signHere fields (also known as tabs) on the documents, + # We"re using anchor (autoPlace) positioning + # + # The DocuSign platform searches throughout your envelope"s + # documents for matching anchor strings. So the + # signHere2 tab will be used in both document 2 and 3 since they + # use the same anchor string for their "signer 1" tabs. + sign_here1 = SignHere( + anchor_string="**signature_1**", + anchor_units="pixels", + anchor_y_offset="10", + anchor_x_offset="20" + ) + + sign_here2 = SignHere( + anchor_string="/sn1/", + anchor_units="pixels", + anchor_y_offset="10", + anchor_x_offset="20" + ) + + # Add the tabs model (including the sign_here tabs) to the signer + # The Tabs object wants arrays of the different field/tab types + signer1.tabs = Tabs(sign_here_tabs=[sign_here1, sign_here2]) + + # Add the recipients to the envelope object + recipients = Recipients(signers=[signer1], carbon_copies=[cc1]) + env.recipients = recipients + + # Request that the envelope be sent by setting |status| to "sent". + # To request that the envelope be created as a draft, set to "created" + env.status = args["status"] + + return env + + @classmethod + def create_document1(cls, args): + """ Creates document 1 -- an html document""" + + return f""" + + + + + + +

World Wide Corp

+

Order Processing Division

+

Ordered by {args["signer_name"]}

+

Email: {args["signer_email"]}

+

Copy to: {args["cc_name"]}, {args["cc_email"]}

+

+ 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. +

+ +

Agreed: **signature_1**/

+ + + """ diff --git a/app/examples/eg002_signing_via_email/views.py b/app/examples/eg002_signing_via_email/views.py new file mode 100644 index 00000000..bbc727c5 --- /dev/null +++ b/app/examples/eg002_signing_via_email/views.py @@ -0,0 +1,59 @@ +""" Example 002: Remote signer, cc, envelope has three documents """ + +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import render_template, session, Blueprint + +from .controller import Eg002Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg002" # reference (and url) for this example +eg002 = Blueprint("eg002", __name__) + + +@eg002.route("/eg002", methods=["POST"]) +@authenticate(eg=eg) +def sign_by_email(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Render success response with envelopeId + """ + + # 1. Get required arguments + args = Eg002Controller.get_args() + try: + # 1. Call the worker method + results = Eg002Controller.worker(args) + except ApiException as err: + return process_error(err) + + session["envelope_id"] = results["envelope_id"] # Save for use by other examples which need an envelopeId + + # 2. Render success response with envelopeId + return render_template( + "example_done.html", + title="Envelope sent", + h1="Envelope sent", + message=f"The envelope has been created and sent!
Envelope ID {results['envelope_id']}." + ) + + +@eg002.route("/eg002", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """responds with the form for the example""" + + return render_template( + "eg002_signing_via_email.html", + title="Signing via email", + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + signer_name=DS_CONFIG["signer_name"], + signer_email=DS_CONFIG["signer_email"] + ) diff --git a/app/examples/eg003_list_envelopes/__init__.py b/app/examples/eg003_list_envelopes/__init__.py new file mode 100644 index 00000000..c5813bf4 --- /dev/null +++ b/app/examples/eg003_list_envelopes/__init__.py @@ -0,0 +1 @@ +from .views import eg003 diff --git a/app/examples/eg003_list_envelopes/controller.py b/app/examples/eg003_list_envelopes/controller.py new file mode 100644 index 00000000..1431612d --- /dev/null +++ b/app/examples/eg003_list_envelopes/controller.py @@ -0,0 +1,41 @@ +from datetime import datetime, timedelta + +from docusign_esign import EnvelopesApi +from flask import session + +from ...docusign import create_api_client + + +class Eg003Controller: + @staticmethod + def get_args(): + """Get request and session arguments""" + return { + "account_id": session["ds_account_id"], + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], + } + + @staticmethod + def worker(args): + """ + 1. Call the envelope status change method to list the envelopes + that have changed in the last 10 days + """ + # Exceptions will be caught by the calling function + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + envelope_api = EnvelopesApi(api_client) + + # The Envelopes::listStatusChanges method has many options + # See https://developers.docusign.com/esign-rest-api/reference/Envelopes/Envelopes/listStatusChanges + + # The list status changes call requires at least a from_date OR + # a set of envelopeIds. Here we filter using a from_date. + # Here we set the from_date to filter envelopes for the last month + # Use ISO 8601 date format + # 1. Call the envelope status change method to list the envelopes + from_date = (datetime.utcnow() - timedelta(days=30)).isoformat() + results = envelope_api.list_status_changes(account_id=args["account_id"], from_date=from_date) + + return results diff --git a/app/examples/eg003_list_envelopes/views.py b/app/examples/eg003_list_envelopes/views.py new file mode 100644 index 00000000..412fe478 --- /dev/null +++ b/app/examples/eg003_list_envelopes/views.py @@ -0,0 +1,56 @@ +"""Example 003: List envelopes in the user"s account""" + +import json +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import render_template, Blueprint + +from .controller import Eg003Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg003" # reference (and url) for this example +eg003 = Blueprint("eg003", __name__) + + +@eg003.route("/eg003", methods=["POST"]) +@authenticate(eg=eg) +def envelope_list(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Show envelopes + """ + + # 1. Get required arguments + args = Eg003Controller.get_args() + try: + # 1. Call the worker method + results = Eg003Controller.worker(args) + except ApiException as err: + return process_error(err) + # 3. Show envelopes + return render_template( + "example_done.html", + title="List envelopes results", + h1="List envelopes results", + message="Results from the Envelopes::listStatusChanges method:", + json=json.dumps(json.dumps(results.to_dict())) + ) + + +@eg003.route("/eg003", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """responds with the form for the example""" + + return render_template( + "eg003_list_envelopes.html", + title="List changed envelopes", + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + ) diff --git a/app/examples/eg004_envelope_info/__init__.py b/app/examples/eg004_envelope_info/__init__.py new file mode 100644 index 00000000..4af46e78 --- /dev/null +++ b/app/examples/eg004_envelope_info/__init__.py @@ -0,0 +1 @@ +from .views import eg004 diff --git a/app/examples/eg004_envelope_info/controller.py b/app/examples/eg004_envelope_info/controller.py new file mode 100644 index 00000000..7ba82933 --- /dev/null +++ b/app/examples/eg004_envelope_info/controller.py @@ -0,0 +1,33 @@ +from docusign_esign import EnvelopesApi +from flask import session + +from ...docusign import create_api_client + + +class Eg004Controller: + @staticmethod + def get_args(): + """ + Get session arguments + """ + return { + "account_id": session["ds_account_id"], + "envelope_id": session["envelope_id"], + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], + } + + @staticmethod + def worker(args): + """ + 1. Call the envelope get method + """ + + # Exceptions will be caught by the calling function + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + envelope_api = EnvelopesApi(api_client) + # 1. Call the envelope get method + results = envelope_api.get_envelope(account_id=args["account_id"], envelope_id=args["envelope_id"]) + + return results diff --git a/app/examples/eg004_envelope_info/views.py b/app/examples/eg004_envelope_info/views.py new file mode 100644 index 00000000..00b084ac --- /dev/null +++ b/app/examples/eg004_envelope_info/views.py @@ -0,0 +1,68 @@ +"""004: Get an envelope"s basic information and status""" + +import json +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import render_template, session, Blueprint + +from .controller import Eg004Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg004" # reference (and url) for this example +eg004 = Blueprint("eg004", __name__) + + +@eg004.route("/eg004", methods=["POST"]) +@authenticate(eg=eg) +def envelope_info(): + """ + 1. Get required arguments + 1. Call the worker method + 2. Show envelope info + """ + + if "envelope_id" in session: + # 1. Get required arguments + args = Eg004Controller.get_args() + try: + # 1. Call the worker method + results = Eg004Controller.worker(args) + except ApiException as err: + return process_error(err) + # 2.Show envelope info + return render_template( + "example_done.html", + title="Get envelope status results", + h1="Get envelope status results", + message="Results from the Envelopes::get method:", + json=json.dumps(json.dumps(results.to_dict())) + ) + else: + return render_template( + "eg004_envelope_info.html", + title="Envelope information", + envelope_ok=False, + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + ) + + +@eg004.route("/eg004", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """responds with the form for the example""" + + return render_template( + "eg004_envelope_info.html", + title="Envelope information", + envelope_ok="envelope_id" in session, + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + ) diff --git a/app/examples/eg005_envelope_recipients/__init__.py b/app/examples/eg005_envelope_recipients/__init__.py new file mode 100644 index 00000000..45102c7a --- /dev/null +++ b/app/examples/eg005_envelope_recipients/__init__.py @@ -0,0 +1 @@ +from .views import eg005 diff --git a/app/examples/eg005_envelope_recipients/controller.py b/app/examples/eg005_envelope_recipients/controller.py new file mode 100644 index 00000000..814794f9 --- /dev/null +++ b/app/examples/eg005_envelope_recipients/controller.py @@ -0,0 +1,33 @@ +from docusign_esign import EnvelopesApi +from flask import session + +from ...docusign import create_api_client + + +class Eg005Controller: + @staticmethod + def get_args(): + """ + Get session arguments + """ + return { + "account_id": session["ds_account_id"], + "envelope_id": session["envelope_id"], + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], + } + + @staticmethod + def worker(args): + """ + 1. Call the envelope recipients list method + """ + + # Exceptions will be caught by the calling function + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + envelope_api = EnvelopesApi(api_client) + # 1. Call the envelope recipients list method + results = envelope_api.list_recipients(account_id=args["account_id"], envelope_id=args["envelope_id"]) + + return results diff --git a/app/examples/eg005_envelope_recipients/views.py b/app/examples/eg005_envelope_recipients/views.py new file mode 100644 index 00000000..631af36d --- /dev/null +++ b/app/examples/eg005_envelope_recipients/views.py @@ -0,0 +1,68 @@ +"""005: List an envelope"s recipients and status""" + +import json +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import render_template, session, Blueprint + +from .controller import Eg005Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg005" # reference (and url) for this example +eg005 = Blueprint("eg005", __name__) + + +@eg005.route("/eg005", methods=["POST"]) +@authenticate(eg=eg) +def envelope_recipients(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Show recipients + """ + + if "envelope_id" in session: + # 1. Get required arguments + args = Eg005Controller.get_args() + try: + # 2. Call the worker method + results = Eg005Controller.worker(args) + except ApiException as err: + return process_error(err) + # 3. Show recipients + return render_template( + "example_done.html", + title="Envelope recipients results", + h1="List the envelope's recipients and their status", + message="Results from the EnvelopesRecipients::list method:", + json=json.dumps(json.dumps(results.to_dict())) + ) + else: + return render_template( + "eg005_envelope_recipients.html", + title="Envelope recipient information", + envelope_ok=False, + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + ) + + +@eg005.route("/eg005", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """responds with the form for the example""" + + return render_template( + "eg005_envelope_recipients.html", + title="Envelope recipient information", + envelope_ok="envelope_id" in session, + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + ) diff --git a/app/examples/eg006_envelope_docs/__init__.py b/app/examples/eg006_envelope_docs/__init__.py new file mode 100644 index 00000000..b2c4ff9f --- /dev/null +++ b/app/examples/eg006_envelope_docs/__init__.py @@ -0,0 +1 @@ +from .views import eg006 diff --git a/app/examples/eg006_envelope_docs/controller.py b/app/examples/eg006_envelope_docs/controller.py new file mode 100644 index 00000000..af82e26c --- /dev/null +++ b/app/examples/eg006_envelope_docs/controller.py @@ -0,0 +1,56 @@ +from docusign_esign import EnvelopesApi +from flask import session + +from ...docusign import create_api_client + + +class Eg006Controller: + @staticmethod + def get_args(): + """ + Get session arguments + """ + return { + "account_id": session["ds_account_id"], + "envelope_id": session["envelope_id"], + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], + } + + @staticmethod + def worker(args): + """ + 1. Call the EnvelopeDocuments::list method + """ + + # Exceptions will be caught by the calling function + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + envelope_api = EnvelopesApi(api_client) + # 1. Call the EnvelopeDocuments::list method + results = envelope_api.list_documents(account_id=args["account_id"], envelope_id=args["envelope_id"]) + + return results + + @staticmethod + def save_envelope_documents(results): + """ + Save the envelopeId and its list of documents in the session so + they can be used in example 7 (download a document) + """ + standard_doc_items = [ + {"name": "Combined", "type": "content", "document_id": "combined"}, + {"name": "Zip archive", "type": "zip", "document_id": "archive"}] + # The certificate of completion is named "summary". + # We give it a better name below. + envelope_doc_items = list(map(lambda doc: + ({"document_id": doc.document_id, "name": "Certificate of completion", + "type": doc.type}) + if (doc.document_id == "certificate") else + ({"document_id": doc.document_id, "name": doc.name, "type": doc.type}), + results.envelope_documents)) + envelope_documents = { + "envelope_id": session["envelope_id"], + "documents": standard_doc_items + envelope_doc_items # See https://stackoverflow.com/a/6005217/64904 + } + session["envelope_documents"] = envelope_documents diff --git a/app/examples/eg006_envelope_docs/views.py b/app/examples/eg006_envelope_docs/views.py new file mode 100644 index 00000000..061d964e --- /dev/null +++ b/app/examples/eg006_envelope_docs/views.py @@ -0,0 +1,73 @@ +"""006: List an envelope"s documents""" +import json +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import render_template, session, Blueprint + +from .controller import Eg006Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg006" # reference (and url) for this example +eg006 = Blueprint("eg006", __name__) + + +@eg006.route("/eg006", methods=["POST"]) +@authenticate(eg=eg) +def envelope_docs(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Save envelope documents + 4. Show envelope documents + """ + + if "envelope_id" in session: + # 1. Get required arguments + args = Eg006Controller.get_args() + try: + # 2. Call the worker method + results = Eg006Controller.worker(args) + except ApiException as err: + return process_error(err) + + # 3. Save envelope documents + Eg006Controller.save_envelope_documents(results) + + # 4. Show envelope documents + return render_template( + "example_done.html", + title="List an envelope's documents", + h1="List an envelope's documents", + message="Results from the EnvelopeDocuments::list method:", + json=json.dumps(json.dumps(results.to_dict())) + ) + + else: + return render_template( + "eg006_envelope_docs.html", + title="Envelope documents", + envelope_ok=False, + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + ) + + +@eg006.route("/eg006", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """responds with the form for the example""" + + return render_template( + "eg006_envelope_docs.html", + title="Envelope documents", + envelope_ok="envelope_id" in session, + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + ) diff --git a/app/examples/eg007_envelope_get_doc/__init__.py b/app/examples/eg007_envelope_get_doc/__init__.py new file mode 100644 index 00000000..0e6d362e --- /dev/null +++ b/app/examples/eg007_envelope_get_doc/__init__.py @@ -0,0 +1 @@ +from .views import eg007 diff --git a/app/examples/eg007_envelope_get_doc/controller.py b/app/examples/eg007_envelope_get_doc/controller.py new file mode 100644 index 00000000..3a2a5d04 --- /dev/null +++ b/app/examples/eg007_envelope_get_doc/controller.py @@ -0,0 +1,66 @@ +from docusign_esign import EnvelopesApi +from flask import request, session + +from ...consts import pattern +from ...docusign import create_api_client + + +class Eg007Controller: + @staticmethod + def get_args(): + """ + Get session arguments + """ + # More data validation would be a good idea here + # Strip anything other than characters listed + document_id = pattern.sub("", request.form.get("document_id")) + + args = { + "account_id": session["ds_account_id"], + "envelope_id": session["envelope_id"], + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], + "document_id": document_id, + "envelope_documents": session["envelope_documents"] + } + return args + + @staticmethod + def worker(args): + """ + 1. Call the envelope get method + """ + # Exceptions will be caught by the calling function + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + envelope_api = EnvelopesApi(api_client) + document_id = args["document_id"] + + # The SDK always stores the received file as a temp file + # 1. Call the envelope get method + temp_file = envelope_api.get_document( + account_id=args["account_id"], + document_id=document_id, + envelope_id=args["envelope_id"] + ) + doc_item = next(item for item in args["envelope_documents"]["documents"] if item["document_id"] == document_id) + doc_name = doc_item["name"] + has_pdf_suffix = doc_name[-4:].upper() == ".PDF" + pdf_file = has_pdf_suffix + # Add .pdf if it"s a content or summary doc and doesn"t already end in .pdf + if (doc_item["type"] == "content" or doc_item["type"] == "summary") and not has_pdf_suffix: + doc_name += ".pdf" + pdf_file = True + # Add .zip as appropriate + if doc_item["type"] == "zip": + doc_name += ".zip" + + # Return the file information + if pdf_file: + mimetype = "application/pdf" + elif doc_item["type"] == "zip": + mimetype = "application/zip" + else: + mimetype = "application/octet-stream" + + return {"mimetype": mimetype, "doc_name": doc_name, "data": temp_file} diff --git a/app/examples/eg007_envelope_get_doc/views.py b/app/examples/eg007_envelope_get_doc/views.py new file mode 100644 index 00000000..103e9ce2 --- /dev/null +++ b/app/examples/eg007_envelope_get_doc/views.py @@ -0,0 +1,79 @@ +"""007: Get an envelope"s document""" + +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import render_template, session, send_file, Blueprint + +from .controller import Eg007Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg007" # reference (and url) for this example +eg007 = Blueprint("eg007", __name__) + + +@eg007.route("/eg007", methods=["POST"]) +@authenticate(eg=eg) +def get_envelope_doc(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Download envelope document + """ + + if "envelope_id" in session and "envelope_documents" in session: + # 1. Get required arguments + args = Eg007Controller.get_args() + try: + # 2. Call the worker method + results = Eg007Controller.worker(args) + except ApiException as err: + return process_error(err) + + # 3. Download envelope document + return send_file( + results["data"], + mimetype=results["mimetype"], + as_attachment=True, + attachment_filename=results["doc_name"] + ) + else: + return render_template( + "eg007_envelope_get_doc.html", + title="Download an Envelope's Document", + envelope_ok=False, + documents_ok=False, + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + ) + + +@eg007.route("/eg007", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """responds with the form for the example""" + + documents_ok = "envelope_documents" in session + document_options = [] + if documents_ok: + # Prepare the select items + envelope_documents = session["envelope_documents"] + document_options = map(lambda item: + {"text": item["name"], "document_id": item["document_id"]} + , envelope_documents["documents"]) + + return render_template( + "eg007_envelope_get_doc.html", + title="Download an Envelope's Document", + envelope_ok="envelope_id" in session, + documents_ok=documents_ok, + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + document_options=document_options + ) diff --git a/app/examples/eg008_create_template/__init__.py b/app/examples/eg008_create_template/__init__.py new file mode 100644 index 00000000..2111ed18 --- /dev/null +++ b/app/examples/eg008_create_template/__init__.py @@ -0,0 +1 @@ +from .views import eg008 diff --git a/app/examples/eg008_create_template/controller.py b/app/examples/eg008_create_template/controller.py new file mode 100644 index 00000000..1514f527 --- /dev/null +++ b/app/examples/eg008_create_template/controller.py @@ -0,0 +1,195 @@ +import base64 +from os import path + +from docusign_esign import Document, Signer, CarbonCopy, SignHere, Tabs, Recipients, \ + TemplatesApi, Checkbox, List, ListItem, Number, Radio, RadioGroup, Text, EnvelopeTemplate +from flask import session + +from ...consts import demo_docs_path, doc_file, template_name +from ...docusign import create_api_client + + +class Eg008Controller: + @staticmethod + def get_args(): + """ + Get session arguments + """ + return { + "account_id": session["ds_account_id"], + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"] + } + + @classmethod + def worker(cls, args): + """ + 1. Check to see if the template already exists + 2. If not, create the template + """ + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + templates_api = TemplatesApi(api_client) + # 1. call Templates::list API method + # Exceptions will be caught by the calling function + results = templates_api.list_templates(account_id=args["account_id"], search_text=template_name) + created_new_template = False + if int(results.result_set_size) > 0: + template_id = results.envelope_templates[0].template_id + results_template_name = results.envelope_templates[0].name + else: + + # Template not found -- so create it + # 2. create the template + template_req_object = cls.make_template_req() + res = templates_api.create_template(account_id=args["account_id"], envelope_template=template_req_object) + # Pick the first template object within the result + results = res.templates[0] + template_id = results.template_id + results_template_name = results.name + created_new_template = True + + return { + "template_id": template_id, + "template_name": results_template_name, + "created_new_template": created_new_template + } + + @classmethod + def make_template_req(cls): + """Creates template req object""" + + # document 1 (pdf) + # + # The template has two recipient roles. + # recipient 1 - signer + # recipient 2 - cc + with open(path.join(demo_docs_path, doc_file), "rb") as file: + content_bytes = file.read() + base64_file_content = base64.b64encode(content_bytes).decode("ascii") + + # Create the document model + document = Document( # create the DocuSign document object + document_base64=base64_file_content, + name="Lorem Ipsum", # can be different from actual file name + file_extension="pdf", # many different document types are accepted + document_id=1 # a label used to reference the doc + ) + + # Create the signer recipient model + signer = Signer(role_name="signer", recipient_id="1", routing_order="1") + # create a cc recipient to receive a copy of the envelope (transaction) + cc = CarbonCopy(role_name="cc", recipient_id="2", routing_order="2") + # Create fields using absolute positioning + # Create a sign_here tab (field on the document) + sign_here = SignHere(document_id="1", page_number="1", x_position="191", y_position="148") + check1 = Checkbox( + document_id="1", + page_number="1", + x_position="75", + y_position="417", + tab_label="ckAuthorization" + ) + check2 = Checkbox( + document_id="1", + page_number="1", + x_position="75", + y_position="447", + tab_label="ckAuthentication" + ) + check3 = Checkbox( + document_id="1", + page_number="1", + x_position="75", + y_position="478", + tab_label="ckAgreement" + ) + check4 = Checkbox( + document_id="1", + page_number="1", + x_position="75", + y_position="508", + tab_label="ckAcknowledgement" + ) + list1 = List( + document_id="1", + page_number="1", + x_position="142", + y_position="291", + font="helvetica", + font_size="size14", + tab_label="list", + required="false", + list_items=[ + ListItem(text="Red", value="red"), + ListItem(text="Orange", value="orange"), + ListItem(text="Yellow", value="yellow"), + ListItem(text="Green", value="green"), + ListItem(text="Blue", value="blue"), + ListItem(text="Indigo", value="indigo"), + ListItem(text="Violet", value="violet") + ] + ) + number1 = Number( + document_id="1", + page_number="1", + x_position="163", + y_position="260", + font="helvetica", + font_size="size14", + tab_label="numbersOnly", + width="84", + required="false" + ) + radio_group = RadioGroup( + document_id="1", + group_name="radio1", + radios=[ + Radio( + page_number="1", x_position="142", y_position="384", + value="white", required="false" + ), + Radio( + page_number="1", x_position="74", y_position="384", + value="red", required="false" + ), + Radio( + page_number="1", x_position="220", y_position="384", + value="blue", required="false" + ) + ] + ) + text = Text( + document_id="1", + page_number="1", + x_position="153", + y_position="230", + font="helvetica", + font_size="size14", + tab_label="text", + height="23", + width="84", + required="false" + ) + # Add the tabs model to the signer + # The Tabs object wants arrays of the different field/tab types + signer.tabs = Tabs( + sign_here_tabs=[sign_here], + checkbox_tabs=[check1, check2, check3, check4], + list_tabs=[list1], + number_tabs=[number1], + radio_group_tabs=[radio_group], + text_tabs=[text] + ) + + # Top object: + template_request = EnvelopeTemplate( + documents=[document], email_subject="Please sign this document", + recipients=Recipients(signers=[signer], carbon_copies=[cc]), + description="Example template created via the API", + name=template_name, + shared="false", + status="created" + ) + + return template_request diff --git a/app/examples/eg008_create_template/views.py b/app/examples/eg008_create_template/views.py new file mode 100644 index 00000000..d69cf193 --- /dev/null +++ b/app/examples/eg008_create_template/views.py @@ -0,0 +1,61 @@ +""" Example 008: create a template if it doesn"t already exist """ + +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import render_template, session, Blueprint + +from .controller import Eg008Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg008" # reference (and url) for this example +eg008 = Blueprint("eg008", __name__) + + +@eg008.route("/eg008", methods=["POST"]) +@authenticate(eg=eg) +def create_template(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Render template info + """ + + # 1. Get required arguments + args = Eg008Controller.get_args() + try: + # 2. Call the worker method + results = Eg008Controller.worker(args) + except ApiException as err: + return process_error(err) + + # Save the templateId in the session so they can be used in future examples + session["template_id"] = results["template_id"] + session["template_ok"] = True + + msg = "The template has been created!" if results["created_new_template"] else \ + "Done. The template already existed in your account." + + # 3. Render template info + return render_template( + "example_done.html", + title="Template results", + h1="Template results", + message=f"""{msg}
Template name: {results["template_name"]}, ID {results["template_id"]}.""" + ) + + +@eg008.route("/eg008", methods=["GET"]) +def get_view(): + """responds with the form for the example""" + + return render_template( + "eg008_create_template.html", + title="Create a template", + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + ) diff --git a/app/examples/eg009_use_template/__init__.py b/app/examples/eg009_use_template/__init__.py new file mode 100644 index 00000000..4467780d --- /dev/null +++ b/app/examples/eg009_use_template/__init__.py @@ -0,0 +1 @@ +from .views import eg009 diff --git a/app/examples/eg009_use_template/controller.py b/app/examples/eg009_use_template/controller.py new file mode 100644 index 00000000..761f26ae --- /dev/null +++ b/app/examples/eg009_use_template/controller.py @@ -0,0 +1,84 @@ +from docusign_esign import EnvelopesApi, EnvelopeDefinition, TemplateRole +from flask import request, session + +from ...consts import pattern +from ...docusign import create_api_client + + +class Eg009Controller: + + @staticmethod + def get_args(): + """Get required session and request arguments""" + # More data validation would be a good idea here + # Strip anything other than characters listed + signer_email = pattern.sub("", request.form.get("signer_email")) + signer_name = pattern.sub("", request.form.get("signer_name")) + cc_email = pattern.sub("", request.form.get("cc_email")) + cc_name = pattern.sub("", request.form.get("cc_name")) + template_id = session["template_id"] + envelope_args = { + "signer_email": signer_email, + "signer_name": signer_name, + "cc_email": cc_email, + "cc_name": cc_name, + "template_id": template_id + } + args = { + "account_id": session["ds_account_id"], + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], + "envelope_args": envelope_args + } + return args + + @classmethod + def worker(cls, args): + """ + 1. Create the envelope request object + 2. Send the envelope + """ + envelope_args = args["envelope_args"] + # 1. Create the envelope request object + envelope_definition = cls.make_envelope(envelope_args) + + # 2. call Envelopes::create API method + # Exceptions will be caught by the calling function + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + envelope_api = EnvelopesApi(api_client) + results = envelope_api.create_envelope(account_id=args["account_id"], envelope_definition=envelope_definition) + envelope_id = results.envelope_id + return {"envelope_id": envelope_id} + + @classmethod + def make_envelope(cls, args): + """ + Creates envelope + args -- parameters for the envelope: + signer_email, signer_name, signer_client_id + returns an envelope definition + """ + + # create the envelope definition + envelope_definition = EnvelopeDefinition( + status="sent", # requests that the envelope be created and sent. + template_id=args["template_id"] + ) + # Create template role elements to connect the signer and cc recipients + # to the template + signer = TemplateRole( + email=args["signer_email"], + name=args["signer_name"], + role_name="signer" + ) + # Create a cc template role. + cc = TemplateRole( + email=args["cc_email"], + name=args["cc_name"], + role_name="cc" + ) + + # Add the TemplateRole objects to the envelope object + envelope_definition.template_roles = [signer, cc] + return envelope_definition diff --git a/app/examples/eg009_use_template/views.py b/app/examples/eg009_use_template/views.py new file mode 100644 index 00000000..96f198ac --- /dev/null +++ b/app/examples/eg009_use_template/views.py @@ -0,0 +1,68 @@ +"""Example 009: Send envelope using a template""" + +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import render_template, session, Blueprint + +from .controller import Eg009Controller +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg009" # reference (and url) for this example +eg009 = Blueprint("eg009", __name__) + + +@eg009.route("/eg009", methods=["POST"]) +def use_template(): + """ + 1. 1. Get required arguments + 2. Call the worker method + """ + + if "template_id" in session: + # 1. Get required arguments + args = Eg009Controller.get_args() + try: + # 1. Call the worker method + results = Eg009Controller.worker(args) + except ApiException as err: + return process_error(err) + + session["envelope_id"] = results["envelope_id"] # Save for use by other examples + # which need an envelopeId + return render_template( + "example_done.html", + title="Envelope sent", + h1="Envelope sent", + message=f"""The envelope has been created and sent!
+ Envelope ID {results["envelope_id"]}.""", + envelope_ok="envelope_id" in results + ) + else: + return render_template( + "eg009_use_template.html", + title="Use a template to send an envelope", + template_ok=False, + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + ) + + +@eg009.route("/eg009", methods=["GET"]) +def get_view(): + """responds with the form for the example""" + + return render_template( + "eg009_use_template.html", + title="Use a template to send an envelope", + template_ok="template_id" in session, + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + signer_name=DS_CONFIG["signer_name"], + signer_email=DS_CONFIG["signer_email"] + ) diff --git a/app/examples/eg010_send_binary_docs/__init__.py b/app/examples/eg010_send_binary_docs/__init__.py new file mode 100644 index 00000000..babbba1b --- /dev/null +++ b/app/examples/eg010_send_binary_docs/__init__.py @@ -0,0 +1 @@ +from .views import eg010 diff --git a/app/examples/eg010_send_binary_docs/controller.py b/app/examples/eg010_send_binary_docs/controller.py new file mode 100644 index 00000000..d204c329 --- /dev/null +++ b/app/examples/eg010_send_binary_docs/controller.py @@ -0,0 +1,227 @@ +import json +from os import path + +import requests +from flask import request, session + +from ...consts import demo_docs_path, pattern +from ...ds_config import DS_CONFIG + + +class Eg010Controller: + @staticmethod + def get_args(): + """Get required session and request arguments""" + # More data validation would be a good idea here + # Strip anything other than characters listed + signer_email = pattern.sub("", request.form.get("signer_email")) + signer_name = pattern.sub("", request.form.get("signer_name")) + cc_email = pattern.sub("", request.form.get("cc_email")) + cc_name = pattern.sub("", request.form.get("cc_name")) + envelope_args = { + "signer_email": signer_email, + "signer_name": signer_name, + "cc_email": cc_email, + "cc_name": cc_name, + "status": "sent", + } + args = { + "account_id": session["ds_account_id"], + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], + "envelope_args": envelope_args + } + return args + + @classmethod + def worker(cls, args): + """ + This function does the work of creating the envelope by using + the API directly with multipart mime + @param {object} args An object with the following args:
+ account_id: Current account Id
+ base_path: base path for making API call
+ access_token: a valid access token
+ demo_docs_path: relative path for the demo docs
+ envelope_args: envelopeArgs, an object with elements
+ signer_email, signer_name, cc_email, cc_name + """ + + # Step 1. Make the envelope JSON request body + envelope_json = cls.make_envelope_json(args["envelope_args"]) + + # Step 2. Gather documents and their headers + # Read files 2 and 3 from a local directory + # The reads could raise an exception if the file is not available! + # Note: the fles are not binary encoded! + with open(path.join(demo_docs_path, DS_CONFIG["doc_docx"]), "rb") as file: + doc2_docx_bytes = file.read() + with open(path.join(demo_docs_path, DS_CONFIG["doc_pdf"]), "rb") as file: + doc3_pdf_bytes = file.read() + + documents = [ + {"mime": "text/html", "filename": envelope_json["documents"][0]["name"], + "document_id": envelope_json["documents"][0]["documentId"], + "bytes": cls.create_document1(args["envelope_args"]).encode("utf-8")}, + {"mime": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "filename": envelope_json["documents"][1]["name"], + "document_id": envelope_json["documents"][1]["documentId"], + "bytes": doc2_docx_bytes}, + {"mime": "application/pdf", "filename": envelope_json["documents"][2]["name"], + "document_id": envelope_json["documents"][2]["documentId"], + "bytes": doc3_pdf_bytes} + ] + + # Step 3. Create the multipart body + CRLF = b"\r\n" + boundary = b"multipartboundary_multipartboundary" + hyphens = b"--" + + req_body = b"".join([ + hyphens, boundary, + CRLF, b"Content-Type: application/json", + CRLF, b"Content-Disposition: form-data", + CRLF, + CRLF, json.dumps(envelope_json, indent=4).encode("utf-8")]) + + # Loop to add the documents. + # See section Multipart Form Requests on page + # https://developers.docusign.com/esign-rest-api/guides/requests-and-responses + for d in documents: + content_disposition = (f"Content-Disposition: file; filename={d['filename']};" + + f"documentid={d['document_id']}").encode("utf-8") + req_body = b"".join([req_body, + CRLF, hyphens, boundary, + CRLF, f"Content-Type: {d['mime']}".encode("utf-8"), + CRLF, content_disposition, + CRLF, + CRLF, d["bytes"]]) + + # Add closing boundary + req_body = b"".join([req_body, CRLF, hyphens, boundary, hyphens, CRLF]) + + # Step 2. call Envelopes::create API method + # Exceptions will be caught by the calling function + results = requests.post( + url=f"{args['base_path']}/v2.1/accounts/{args['account_id']}/envelopes", + headers={ + "Authorization": "bearer " + args['access_token'], + "Accept": "application/json", + "Content-Type": f"multipart/form-data; boundary={boundary.decode('utf-8')}" + }, + data=req_body + ) + return {"status_code": results.status_code, "results": results.json()} + + @classmethod + def make_envelope_json(cls, args): + """ + Create envelope JSON +
Document 1: An HTML document. +
Document 2: A Word .docx document. +
Document 3: A PDF document. +
DocuSign will convert all of the documents to the PDF format. +
The recipients" field tags are placed using anchor strings. + @param {Object} args parameters for the envelope: + signerEmail, signerName, cc_email, cc_name + @returns {Envelope} An envelope definition + """ + + # document 1 (html) has tag **signature_1** + # document 2 (docx) has tag /sn1/ + # document 3 (pdf) has tag /sn1/ + # + # The envelope has two recipients. + # recipient 1 - signer + # recipient 2 - cc + # The envelope will be sent first to the signer. + # After it is signed, a copy is sent to the cc person. + + # create the envelope definition + env_json = { + "emailSubject": "Please sign this document set" + } + # add the documents + doc1 = { + "name": "Order acknowledgement", # can be different from actual file name + "fileExtension": "html", # Source data format. Signed docs are always pdf. + "documentId": "1"} # a label used to reference the doc + doc2 = { + "name": "Battle Plan", "fileExtension": "docx", "documentId": "2"} + doc3 = { + "name": "Lorem Ipsum", "fileExtension": "pdf", "documentId": "3"} + # The order in the docs array determines the order in the envelope + env_json["documents"] = [doc1, doc2, doc3] + + # create a signer recipient to sign the document, identified by name and email + signer1 = { + "email": args["signer_email"], "name": args["signer_name"], + "recipientId": "1", "routingOrder": "1"} + # routingOrder (lower means earlier) determines the order of deliveries + # to the recipients. Parallel routing order is supported by using the + # same integer as the order for two or more recipients. + + # create a cc recipient to receive a copy of the documents, identified by name and email + cc1 = { + "email": args["cc_email"], "name": args["cc_name"], + "routingOrder": "2", "recipientId": "2"} + + # Create signHere fields (also known as tabs) on the documents, + # We"re using anchor (autoPlace) positioning + # + # The DocuSign platform searches throughout your envelope"s + # documents for matching anchor strings. So the + # signHere2 tab will be used in both document 2 and 3 since they + # use the same anchor string for their "signer 1" tabs. + sign_here1 = { + "anchorString": "**signature_1**", "anchorYOffset": "10", "anchorUnits": "pixels", + "anchorXOffset": "20"} + sign_here2 = { + "anchorString": "/sn1/", "anchorYOffset": "10", "anchorUnits": "pixels", + "anchorXOffset": "20"} + + # Tabs are set per recipient / signer + signer1_tabs = {"signHereTabs": [sign_here1, sign_here2]} + signer1["tabs"] = signer1_tabs + + # Add the recipients to the envelope object + recipients = {"signers": [signer1], "carbonCopies": [cc1]} + env_json["recipients"] = recipients + + # Request that the envelope be sent by setting |status| to "sent". + # To request that the envelope be created as a draft, set to "created" + env_json["status"] = "sent" + + return env_json + + @classmethod + def create_document1(cls, args): + """ Creates document 1 -- an html document""" + + return f""" + + + + + + +

World Wide Corp

+

Order Processing Division

+

Ordered by {args["signer_name"]}

+

Email: {args["signer_email"]}

+

Copy to: {args["cc_name"]}, {args["cc_email"]}

+

+ 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. +

+ +

Agreed: **signature_1**/

+ + + """ diff --git a/app/examples/eg010_send_binary_docs/views.py b/app/examples/eg010_send_binary_docs/views.py new file mode 100644 index 00000000..90033d9c --- /dev/null +++ b/app/examples/eg010_send_binary_docs/views.py @@ -0,0 +1,67 @@ +""" Example 010: Send binary docs with multipart mime: Remote signer, cc; the envelope has three documents""" + +from os import path + +from flask import render_template, Blueprint + +from .controller import Eg010Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG + +eg = "eg010" # reference (and url) for this example +eg010 = Blueprint("eg010", __name__) + + +@eg010.route("/eg010", methods=["POST"]) +@authenticate(eg=eg) +def send_bynary_docs(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Render success response + """ + + # 1. Get required arguments + args = Eg010Controller.get_args() + # 2. Call the worker method + results = Eg010Controller.worker(args) + + if results["status_code"] < 299: + # 3. Render Success response + return render_template( + "example_done.html", + title="Envelope sent", + h1="Envelope sent", + message=f"""The envelope has been created and sent!
Envelope ID {results["results"]["envelopeId"]}.""" + ) + else: + # Problem! + error_body = results["results"] + # we can pull the DocuSign error code and message from the response body + error_code = error_body and "errorCode" in error_body and error_body["errorCode"] + error_message = error_body and "message" in error_body and error_body["message"] + # In production, may want to provide customized error messages and + # remediation advice to the user. + return render_template( + "error.html", + err=None, + error_code=error_code, + error_message=error_message + ) + + +@eg010.route("/eg010", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """Responds with the form for the example""" + + return render_template( + "eg010_send_binary_docs.html", + title="Send binary documents", + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + signer_name=DS_CONFIG["signer_name"], + signer_email=DS_CONFIG["signer_email"] + ) diff --git a/app/examples/eg011_embedded_sending/__init__.py b/app/examples/eg011_embedded_sending/__init__.py new file mode 100644 index 00000000..88a815c0 --- /dev/null +++ b/app/examples/eg011_embedded_sending/__init__.py @@ -0,0 +1 @@ +from .views import eg011 diff --git a/app/examples/eg011_embedded_sending/controller.py b/app/examples/eg011_embedded_sending/controller.py new file mode 100644 index 00000000..efd19d0c --- /dev/null +++ b/app/examples/eg011_embedded_sending/controller.py @@ -0,0 +1,69 @@ +from docusign_esign import EnvelopesApi, ReturnUrlRequest +from flask import url_for, session, request + +from ..eg002_signing_via_email.controller import Eg002Controller +from ...consts import pattern +from ...docusign import create_api_client + + +class Eg011Controller: + + @staticmethod + def get_args(): + """Get required session and request arguments""" + # More data validation would be a good idea here + # Strip anything other than characters listed + signer_email = pattern.sub("", request.form.get("signer_email")) + signer_name = pattern.sub("", request.form.get("signer_name")) + cc_email = pattern.sub("", request.form.get("cc_email")) + cc_name = pattern.sub("", request.form.get("cc_name")) + starting_view = pattern.sub("", request.form.get("starting_view")) + + envelope_args = { + "signer_email": signer_email, + "signer_name": signer_name, + "cc_email": cc_email, + "cc_name": cc_name, + "status": "sent", + } + args = { + "starting_view": starting_view, + "account_id": session["ds_account_id"], + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], + "envelope_args": envelope_args, + "ds_return_url": url_for("ds.ds_return", _external=True), + } + return args + + @staticmethod + def worker(args): + """ + This function does the work of creating the envelope in + draft mode and returning a URL for the sender"s view + """ + + # Step 1. Create the envelope with "created" (draft) status + args["envelope_args"]["status"] = "created" + # Using worker from example 002 + results = Eg002Controller.worker(args) + envelope_id = results["envelope_id"] + + # Step 2. Create the sender view + view_request = ReturnUrlRequest(return_url=args["ds_return_url"]) + # Exceptions will be caught by the calling function + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + envelope_api = EnvelopesApi(api_client) + results = envelope_api.create_sender_view( + account_id=args["account_id"], + envelope_id=envelope_id, + return_url_request=view_request + ) + + # Switch to Recipient and Documents view if requested by the user + url = results.url + if args["starting_view"] == "recipient": + url = url.replace("send=1", "send=0") + + return {"envelope_id": envelope_id, "redirect_url": url} diff --git a/app/examples/eg011_embedded_sending/views.py b/app/examples/eg011_embedded_sending/views.py new file mode 100644 index 00000000..1dc151e4 --- /dev/null +++ b/app/examples/eg011_embedded_sending/views.py @@ -0,0 +1,55 @@ +"""011: Embedded sending: Remote signer, cc, envelope has three documents""" + +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import render_template, redirect, Blueprint + +from .controller import Eg011Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg011" # reference (and url) for this example +eg011 = Blueprint("eg011", __name__) + + +@eg011.route("/eg011", methods=["POST"]) +@authenticate(eg=eg) +def embedded_sending(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Redirect user to NDSE view + """ + + # 1. Get required arguments + args = Eg011Controller.get_args() + try: + # 2. Call the worker method + results = Eg011Controller.worker(args) + except ApiException as err: + return process_error(err) + + # Redirect the user to the NDSE view + # Don"t use an iFrame! + # State can be stored/recovered using the framework"s session or a + # query parameter on the returnUrl (see the makeRecipientViewRequest method) + return redirect(results["redirect_url"]) + + +@eg011.route("/eg011", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """responds with the form for the example""" + + return render_template( + "eg011_embedded_sending.html", + title="Embedded Sending", + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + signer_name=DS_CONFIG["signer_name"], + signer_email=DS_CONFIG["signer_email"] + ) diff --git a/app/examples/eg012_embedded_console/__init__.py b/app/examples/eg012_embedded_console/__init__.py new file mode 100644 index 00000000..37211f9c --- /dev/null +++ b/app/examples/eg012_embedded_console/__init__.py @@ -0,0 +1 @@ +from .views import eg012 diff --git a/app/examples/eg012_embedded_console/controller.py b/app/examples/eg012_embedded_console/controller.py new file mode 100644 index 00000000..d373aefc --- /dev/null +++ b/app/examples/eg012_embedded_console/controller.py @@ -0,0 +1,48 @@ +from docusign_esign import EnvelopesApi, ConsoleViewRequest +from flask import session, url_for, request + +from ...consts import pattern +from ...docusign import create_api_client + + +class Eg012Controller: + @staticmethod + def get_args(): + """Get required session and request arguments""" + # Strip anything other than characters listed + starting_view = pattern.sub("", request.form.get("starting_view")) + envelope_id = "envelope_id" in session and session["envelope_id"] + args = { + "envelope_id": envelope_id, + "starting_view": starting_view, + "account_id": session["ds_account_id"], + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], + "ds_return_url": url_for("ds.ds_return", _external=True), + } + + return args + + @staticmethod + def worker(args): + """ + This function does the work of returning a URL for the NDSE view + """ + + # Step 1. Create the NDSE view request object + # Set the url where you want the recipient to go once they are done + # with the NDSE. It is usually the case that the + # user will never "finish" with the NDSE. + # Assume that control will not be passed back to your app. + view_request = ConsoleViewRequest(return_url=args["ds_return_url"]) + if args["starting_view"] == "envelope" and args["envelope_id"]: + view_request.envelope_id = args["envelope_id"] + + # Step 2. Get the console view url + # Exceptions will be caught by the calling function + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + envelope_api = EnvelopesApi(api_client) + results = envelope_api.create_console_view(account_id=args["account_id"], console_view_request=view_request) + url = results.url + return {"redirect_url": url} diff --git a/app/examples/eg012_embedded_console/views.py b/app/examples/eg012_embedded_console/views.py new file mode 100644 index 00000000..83a82226 --- /dev/null +++ b/app/examples/eg012_embedded_console/views.py @@ -0,0 +1,54 @@ +"""012: Embedded console""" + +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import render_template, redirect, session, Blueprint + +from .controller import Eg012Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg012" # reference (and url) for this example +eg012 = Blueprint("eg012", __name__) + + +@eg012.route("/eg012", methods=["POST"]) +@authenticate(eg=eg) +def embedded_console(): + """ + 1. Get required args + 2. Call the worker method + 3. Redirect user to NDSE view + """ + + # 1. Get required args + args = Eg012Controller.get_args() + try: + # 2. Call the worker method + results = Eg012Controller.worker(args) + except ApiException as err: + return process_error(err) + + # 3. Redirect the user to the NDSE view + # Don"t use an iFrame! + # State can be stored/recovered using the framework"s session + return redirect(results["redirect_url"]) + + +@eg012.route("/eg012", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """responds with the form for the example""" + + envelope_id = "envelope_id" in session and session["envelope_id"] + return render_template( + "eg012_embedded_console.html", + title="Embedded Console", + envelope_ok=envelope_id, + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + ) diff --git a/app/examples/eg013_add_doc_to_template/__init__.py b/app/examples/eg013_add_doc_to_template/__init__.py new file mode 100644 index 00000000..525e5b86 --- /dev/null +++ b/app/examples/eg013_add_doc_to_template/__init__.py @@ -0,0 +1 @@ +from .views import eg013 diff --git a/app/examples/eg013_add_doc_to_template/controller.py b/app/examples/eg013_add_doc_to_template/controller.py new file mode 100644 index 00000000..eaa1d154 --- /dev/null +++ b/app/examples/eg013_add_doc_to_template/controller.py @@ -0,0 +1,218 @@ +import base64 + +from docusign_esign import EnvelopesApi, EnvelopeDefinition, Document, Signer, CarbonCopy, SignHere, Tabs, Recipients, \ + CompositeTemplate, InlineTemplate, ServerTemplate, RecipientViewRequest +from flask import url_for, session, request + +from ...consts import signer_client_id, pattern +from ...docusign import create_api_client + + +class Eg013Controller: + @staticmethod + def get_args(): + """Get required session and request arguments""" + # More data validation would be a good idea here + # Strip anything other than characters listed + signer_email = pattern.sub("", request.form.get("signer_email")) + signer_name = pattern.sub("", request.form.get("signer_name")) + cc_email = pattern.sub("", request.form.get("cc_email")) + cc_name = pattern.sub("", request.form.get("cc_name")) + item = pattern.sub("", request.form.get("item")) + quantity = pattern.sub("", request.form.get("quantity")) + quantity = int(quantity) + template_id = session["template_id"] + envelope_args = { + "signer_email": signer_email, + "signer_name": signer_name, + "cc_email": cc_email, + "cc_name": cc_name, + "template_id": template_id, + "signer_client_id": signer_client_id, + "item": item, + "quantity": quantity, + "ds_return_url": url_for("ds.ds_return", _external=True) + } + args = { + "account_id": session["ds_account_id"], + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], + "envelope_args": envelope_args + } + return args + + @classmethod + def worker(cls, args): + """ + Create the envelope and the embedded Signing Ceremony + 1. Create the envelope request object using composite template to + add the new document + 2. Send the envelope + 3. Make the recipient view request object + 4. Get the recipient view (Signing Ceremony) url + """ + envelope_args = args["envelope_args"] + # 1. Create the envelope request object + envelope_definition = cls.make_envelope(envelope_args) + + # 2. call Envelopes::create API method + # Exceptions will be caught by the calling function + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + envelope_api = EnvelopesApi(api_client) + results = envelope_api.create_envelope(account_id=args["account_id"], envelope_definition=envelope_definition) + envelope_id = results.envelope_id + + # 3. Create the Recipient View request object + authentication_method = "None" # How is this application authenticating + # the signer? See the "authenticationMethod" definition + # https://goo.gl/qUhGTm + recipient_view_request = RecipientViewRequest( + authentication_method=authentication_method, + client_user_id=envelope_args["signer_client_id"], + recipient_id="1", + return_url=envelope_args["ds_return_url"], + user_name=envelope_args["signer_name"], + email=envelope_args["signer_email"] + ) + # 4. Obtain the recipient_view_url for the signing ceremony + # Exceptions will be caught by the calling function + results = envelope_api.create_recipient_view( + account_id=args["account_id"], + envelope_id=envelope_id, + recipient_view_request=recipient_view_request + ) + + return {"envelope_id": envelope_id, "redirect_url": results.url} + + @classmethod + def make_envelope(cls, args): + """ + Creates envelope + Uses compositing templates to add a new document to the existing template + returns an envelope definition + + The envelope request object uses Composite Template to + include in the envelope: + 1. A template stored on the DocuSign service + 2. An additional document which is a custom HTML source document + """ + + # 1. Create Recipients for server template. Note that Recipients object + # is used, not TemplateRole + # + # Create a signer recipient for the signer role of the server template + signer1 = Signer( + email=args["signer_email"], + name=args["signer_name"], + role_name="signer", + recipient_id="1", + # Adding clientUserId transforms the template recipient + # into an embedded recipient: + client_user_id=args["signer_client_id"] + ) + # Create the cc recipient + cc1 = CarbonCopy( + email=args["cc_email"], + name=args["cc_name"], + role_name="cc", + recipient_id="2" + ) + # Recipients object: + recipients_server_template = Recipients( + carbon_copies=[cc1], + signers=[signer1] + ) + + # 2. create a composite template for the Server template + roles + comp_template1 = CompositeTemplate( + composite_template_id="1", + server_templates=[ + ServerTemplate(sequence="1", template_id=args["template_id"]) + ], + # Add the roles via an inlineTemplate + inline_templates=[ + InlineTemplate(sequence="1", + recipients=recipients_server_template) + ] + ) + + # Next, create the second composite template that will + # include the new document. + # + # 3. Create the signer recipient for the added document + # starting with the tab definition: + sign_here1 = SignHere( + anchor_string="**signature_1**", + anchor_y_offset="10", + anchor_units="pixels", + anchor_x_offset="20" + ) + signer1_tabs = Tabs(sign_here_tabs=[sign_here1]) + + # 4. Create Signer definition for the added document + signer1_added_doc = Signer( + email=args["signer_email"], + name=args["signer_name"], + role_name="signer", + recipient_id="1", + client_user_id=args["signer_client_id"], + tabs=signer1_tabs + ) + # 5. The Recipients object for the added document. + # Using cc1 definition from above. + recipients_added_doc = Recipients( + carbon_copies=[cc1], signers=[signer1_added_doc]) + # 6. Create the HTML document that will be added to the envelope + doc1_b64 = base64.b64encode(bytes(cls.create_document1(args), "utf-8")) \ + .decode("ascii") + doc1 = Document( + document_base64=doc1_b64, + name="Appendix 1--Sales order", # can be different from + # actual file name + file_extension="html", + document_id="1" + ) + # 6. create a composite template for the added document + comp_template2 = CompositeTemplate( + composite_template_id="2", + # Add the recipients via an inlineTemplate + inline_templates=[ + InlineTemplate(sequence="2", recipients=recipients_added_doc) + ], + document=doc1 + ) + # 7. create the envelope definition with the composited templates + envelope_definition = EnvelopeDefinition( + status="sent", + composite_templates=[comp_template1, comp_template2] + ) + + return envelope_definition + + @classmethod + def create_document1(cls, args): + return f""" + + + + + + +

World Wide Corp

+

Order Processing Division

+

Ordered by {args["signer_name"]}

+

Email: {args["signer_email"]}

+

Copy to: {args["cc_name"]}, {args["cc_email"]}

+

Item: {args["item"]}, quantity: {args["quantity"]} at market price.

+

+ 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. +

+ +

Agreed: **signature_1**/

+ + + """ diff --git a/app/examples/eg013_add_doc_to_template/views.py b/app/examples/eg013_add_doc_to_template/views.py new file mode 100644 index 00000000..e1b016b0 --- /dev/null +++ b/app/examples/eg013_add_doc_to_template/views.py @@ -0,0 +1,68 @@ +"""Example 013: Embedded Signing Ceremony from template with added document""" + +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import render_template, redirect, session, Blueprint + +from .controller import Eg013Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg013" # reference (and url) for this example +eg013 = Blueprint("eg013", __name__) + + +@eg013.route("/eg013", methods=["POST"]) +@authenticate(eg=eg) +def add_doc_template(): + """ + 1. Check the presence of a saved template_id + 2. Get required arguments + 3. Call the worker method + 4. Redirect user to Signing ceremory + """ + # 1. Check the presence of a saved template_id + if "template_id" in session: + # 2. Get required arguments + args = Eg013Controller.get_args() + try: + # 3. Call the worker method + results = Eg013Controller.worker(args) + except ApiException as err: + return process_error(err) + + # 4. Redirect the user to the Signing Ceremony + # Don"t use an iFrame! + # State can be stored/recovered using the framework"s session + return redirect(results["redirect_url"]) + + else: + return render_template( + "eg013_add_doc_to_template.html", + title="Embedded Signing Ceremony from template and extra doc", + template_ok=False, + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + ) + + +@eg013.route("/eg013", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """responds with the form for the example""" + + return render_template( + "eg013_add_doc_to_template.html", + title="Embedded Signing Ceremony from template and extra doc", + template_ok="template_id" in session, + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + signer_name=DS_CONFIG["signer_name"], + signer_email=DS_CONFIG["signer_email"] + ) diff --git a/app/examples/eg014_collect_payment/__init__.py b/app/examples/eg014_collect_payment/__init__.py new file mode 100644 index 00000000..82422776 --- /dev/null +++ b/app/examples/eg014_collect_payment/__init__.py @@ -0,0 +1 @@ +from .views import eg014 diff --git a/app/examples/eg014_collect_payment/controller.py b/app/examples/eg014_collect_payment/controller.py new file mode 100644 index 00000000..3f13ba75 --- /dev/null +++ b/app/examples/eg014_collect_payment/controller.py @@ -0,0 +1,274 @@ +import base64 +from os import path + +from docusign_esign import EnvelopesApi, EnvelopeDefinition, Document, Signer, CarbonCopy, SignHere, Tabs, Recipients, \ + ListItem, List, FormulaTab, PaymentLineItem, PaymentDetails +from flask import request, session + +from ...consts import demo_docs_path, pattern +from ...docusign import create_api_client +from ...ds_config import DS_CONFIG + + +class Eg014Controller: + @staticmethod + def get_args(): + """Get required session and request arguments""" + # More data validation would be a good idea here + # Strip anything other than characters listed + signer_email = pattern.sub("", request.form.get("signer_email")) + signer_name = pattern.sub("", request.form.get("signer_name")) + cc_email = pattern.sub("", request.form.get("cc_email")) + cc_name = pattern.sub("", request.form.get("cc_name")) + envelope_args = { + "signer_email": signer_email, + "signer_name": signer_name, + "cc_email": cc_email, + "cc_name": cc_name, + "status": "sent", + "gateway_account_id": DS_CONFIG["gateway_account_id"], + "gateway_name": DS_CONFIG["gateway_name"], + "gateway_display_name": DS_CONFIG["gateway_display_name"] + } + args = { + "account_id": session["ds_account_id"], + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], + "envelope_args": envelope_args + } + return args + + @classmethod + def worker(cls, args): + """ + 1. Create the envelope request object + 2. Send the envelope + """ + envelope_args = args["envelope_args"] + # 1. Create the envelope request object + envelope_definition = cls.make_envelope(envelope_args) + + # 2. call Envelopes::create API method + # Exceptions will be caught by the calling function + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + envelope_api = EnvelopesApi(api_client) + results = envelope_api.create_envelope(account_id=args["account_id"], envelope_definition=envelope_definition) + + envelope_id = results.envelope_id + # app.logger.info(f"Envelope was created. EnvelopeId {envelope_id}") + + return {"envelope_id": envelope_id} + + @classmethod + def make_envelope(cls, args): + """ + This function creates the envelope definition for the + order form. + document 1 (html) has multiple tags: + /l1q/ and /l2q/ -- quantities: drop down + /l1e/ and /l2e/ -- extended: payment lines + /l3t/ -- total -- formula + + The envelope has two recipients. + recipient 1 - signer + recipient 2 - cc + The envelope will be sent first to the signer. + After it is signed, a copy is sent to the cc person. + + ################################################################# + # # + # NOTA BENA: This method programmatically constructs the # + # order form. For many use cases, it would be # + # better to create the order form as a template # + # using the DocuSign web tool as WYSIWYG # + # form designer. # + # # + ################################################################# + + """ + + # Order form constants + l1_name = "Harmonica" + l1_price = 5 + l1_description = f"${l1_price} each" + l2_name = "Xylophone" + l2_price = 150 + l2_description = f"${l2_price} each" + currency_multiplier = 100 + + # read the html file from a local directory + # The read could raise an exception if the file is not available! + doc1_file = "order_form.html" + with open(path.join(demo_docs_path, doc1_file), "r") as file: + doc1_html_v1 = file.read() + + # Substitute values into the HTML + # Substitute for: {signerName}, {signerEmail}, {cc_name}, {cc_email} + doc1_html_v2 = doc1_html_v1.replace("{signer_name}", args["signer_name"]) \ + .replace("{signer_email}", args["signer_email"]) \ + .replace("{cc_name}", args["cc_name"]) \ + .replace("{cc_email}", args["cc_email"]) + + # create the envelope definition + envelope_definition = EnvelopeDefinition( + email_subject="Please complete your order" + ) + # add the document + doc1_b64 = base64.b64encode(bytes(doc1_html_v2, "utf-8")).decode("ascii") + doc1 = Document( + document_base64=doc1_b64, + name="Order form", # can be different from actual file name + file_extension="html", # Source data format. + document_id="1" # a label used to reference the doc + ) + envelope_definition.documents = [doc1] + # create a signer recipient to sign the document + signer1 = Signer( + email=args["signer_email"], + name=args["signer_name"], + recipient_id="1", + routing_order="1" + ) + # create a cc recipient to receive a copy of the documents + cc1 = CarbonCopy( + email=args["cc_email"], + name=args["cc_name"], + recipient_id="2", + routing_order="2" + ) + # Create signHere fields (also known as tabs) on the documents, + # We"re using anchor (autoPlace) positioning + sign_here1 = SignHere( + anchor_string="/sn1/", + anchor_y_offset="10", + anchor_units="pixels", + anchor_x_offset="20" + ) + list_item0 = ListItem(text="none", value="0") + list_item1 = ListItem(text="1", value="1") + list_item2 = ListItem(text="2", value="2") + list_item3 = ListItem(text="3", value="3") + list_item4 = ListItem(text="4", value="4") + list_item5 = ListItem(text="5", value="5") + list_item6 = ListItem(text="6", value="6") + list_item7 = ListItem(text="7", value="7") + list_item8 = ListItem(text="8", value="8") + list_item9 = ListItem(text="9", value="9") + list_item10 = ListItem(text="10", value="10") + + listl1q = List( + font="helvetica", + font_size="size11", + anchor_string="/l1q/", + anchor_y_offset="-10", anchor_units="pixels", + anchor_x_offset="0", + list_items=[list_item0, list_item1, list_item2, + list_item3, list_item4, list_item5, list_item6, + list_item7, list_item8, list_item9, list_item10], + required="true", + tab_label="l1q" + ) + listl2q = List( + font="helvetica", + font_size="size11", + anchor_string="/l2q/", + anchor_y_offset="-10", anchor_units="pixels", + anchor_x_offset="0", + list_items=[list_item0, list_item1, list_item2, + list_item3, list_item4, list_item5, list_item6, + list_item7, list_item8, list_item9, list_item10], + required="true", + tab_label="l2q" + ) + # create two formula tabs for the extended price on the line items + formulal1e = FormulaTab( + font="helvetica", + font_size="size11", + anchor_string="/l1e/", + anchor_y_offset="-8", anchor_units="pixels", + anchor_x_offset="105", + tab_label="l1e", + formula=f"[l1q] * {l1_price}", + round_decimal_places="0", + required="true", + locked="true", + disable_auto_size="false", + ) + formulal2e = FormulaTab( + font="helvetica", + font_size="size11", + anchor_string="/l2e/", + anchor_y_offset="-8", anchor_units="pixels", + anchor_x_offset="105", + tab_label="l2e", + formula=f"[l2q] * {l2_price}", + round_decimal_places="0", + required="true", + locked="true", + disable_auto_size="false", + ) + # Formula for the total + formulal3t = FormulaTab( + font="helvetica", + bold="true", + font_size="size12", + anchor_string="/l3t/", + anchor_y_offset="-8", anchor_units="pixels", + anchor_x_offset="50", + tab_label="l3t", + formula="[l1e] + [l2e]", + round_decimal_places="0", + required="true", + locked="true", + disable_auto_size="false", + ) + # Payment line items + payment_line_iteml1 = PaymentLineItem( + name=l1_name, + description=l1_description, + amount_reference="l1e" + ) + payment_line_iteml2 = PaymentLineItem( + name=l2_name, + description=l2_description, + amount_reference="l2e" + ) + payment_details = PaymentDetails( + gateway_account_id=args["gateway_account_id"], + currency_code="USD", + gateway_name=args["gateway_name"], + line_items=[payment_line_iteml1, payment_line_iteml2] + ) + # Hidden formula for the payment itself + formula_payment = FormulaTab( + tab_label="payment", + formula=f"([l1e] + [l2e]) * {currency_multiplier}", + round_decimal_places="0", + payment_details=payment_details, + hidden="true", + required="true", + locked="true", + document_id="1", + page_number="1", + x_position="0", + y_position="0" + ) + + # Tabs are set per recipient / signer + signer1_tabs = Tabs( + sign_here_tabs=[sign_here1], + list_tabs=[listl1q, listl2q], + formula_tabs=[formulal1e, formulal2e, formulal3t, formula_payment] + ) + signer1.tabs = signer1_tabs + + # Add the recipients to the envelope object + recipients = Recipients(signers=[signer1], carbon_copies=[cc1]) + envelope_definition.recipients = recipients + + # Request that the envelope be sent by setting |status| to "sent". + # To request that the envelope be created as a draft, set to "created" + envelope_definition.status = args["status"] + + return envelope_definition diff --git a/app/examples/eg014_collect_payment/views.py b/app/examples/eg014_collect_payment/views.py new file mode 100644 index 00000000..e09931c8 --- /dev/null +++ b/app/examples/eg014_collect_payment/views.py @@ -0,0 +1,61 @@ +""" Example 014: Remote signer, cc; envelope has an order form """ + +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import render_template, Blueprint + +from .controller import Eg014Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg014" # reference (and url) for this example +eg014 = Blueprint("eg014", __name__) + + +@eg014.route("/eg014", methods=["POST"]) +@authenticate(eg=eg) +def collect_payment(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Render success response + """ + + # 1. Get required arguments + args = Eg014Controller.get_args() + try: + # 2. Call the worker method + results = Eg014Controller.worker(args) + except ApiException as err: + return process_error(err) + + # 3. Render success response + return render_template( + "example_done.html", + title="Envelope sent", + h1="Envelope sent", + message=f"""The envelope has been created and sent!
Envelope ID {results["envelope_id"]}.""" + ) + + +@eg014.route("/eg014", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """responds with the form for the example""" + + gateway = DS_CONFIG["gateway_account_id"] + gateway_ok = gateway and len(gateway) > 25 + + return render_template( + "eg014_collect_payment.html", + title="Order form with payment", + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + signer_name=DS_CONFIG["signer_name"], + signer_email=DS_CONFIG["signer_email"], + gateway_ok=gateway_ok + ) diff --git a/app/examples/eg015_envelope_tab_data/__init__.py b/app/examples/eg015_envelope_tab_data/__init__.py new file mode 100644 index 00000000..c0675101 --- /dev/null +++ b/app/examples/eg015_envelope_tab_data/__init__.py @@ -0,0 +1 @@ +from .views import eg015 diff --git a/app/examples/eg015_envelope_tab_data/controller.py b/app/examples/eg015_envelope_tab_data/controller.py new file mode 100644 index 00000000..3642ff92 --- /dev/null +++ b/app/examples/eg015_envelope_tab_data/controller.py @@ -0,0 +1,29 @@ +from docusign_esign import EnvelopesApi +from flask import session + +from ...docusign import create_api_client + + +class Eg015Controller: + @staticmethod + def get_args(): + """Get required session arguments""" + return { + "account_id": session["ds_account_id"], + "envelope_id": session["envelope_id"], + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], + } + + @staticmethod + def worker(args): + """ + 1. Call the envelope get method + """ + + # Exceptions will be caught by the calling function + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + envelopes_api = EnvelopesApi(api_client) + results = envelopes_api.get_form_data(account_id=args["account_id"], envelope_id=args["envelope_id"]) + + return results diff --git a/app/examples/eg015_envelope_tab_data/views.py b/app/examples/eg015_envelope_tab_data/views.py new file mode 100644 index 00000000..6c010511 --- /dev/null +++ b/app/examples/eg015_envelope_tab_data/views.py @@ -0,0 +1,74 @@ +"""015: Get an envelope"s tab information data""" + +import json +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import render_template, session, Blueprint + +from .controller import Eg015Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg015" # Reference (and URL) for this example +eg015 = Blueprint("eg015", __name__) + + +@eg015.route("/eg015", methods=["POST"]) +@authenticate(eg=eg) +def envelope_tab_data(): + """ + 1. Check presence of envelope_id in session + 2. Get required arguments + 3. Call the worker method + 4. Show Envelope tab data results + """ + + # 1. Check presence of envelope_id in session + if "envelope_id" in session: + + # 2. Get required arguments + args = Eg015Controller.get_args() + + try: + # 3. Call the worker method + results = Eg015Controller.worker(args) + except ApiException as err: + return process_error(err) + + # 4.Show Envelope tab data results + return render_template( + "example_done.html", + title="Get envelope tab data results", + h1="Get envelope tab data results", + message="Results from the Envelopes::formData GET method:", + json=json.dumps(json.dumps(results.to_dict())) + ) + + else: + return render_template( + "eg015_envelope_tab_data.html", + title="Envelope Tab Data", + envelope_ok=False, + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + ) + + +@eg015.route("/eg015", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """responds with the form for the example""" + + return render_template( + "eg015_envelope_tab_data.html", + title="Envelope information", + envelope_ok="envelope_id" in session, + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + ) diff --git a/app/examples/eg016_set_tab_values/__init__.py b/app/examples/eg016_set_tab_values/__init__.py new file mode 100644 index 00000000..7f301c03 --- /dev/null +++ b/app/examples/eg016_set_tab_values/__init__.py @@ -0,0 +1 @@ +from .views import eg016 diff --git a/app/examples/eg016_set_tab_values/controller.py b/app/examples/eg016_set_tab_values/controller.py new file mode 100644 index 00000000..f1a6183b --- /dev/null +++ b/app/examples/eg016_set_tab_values/controller.py @@ -0,0 +1,169 @@ +import base64 +from os import path + +from docusign_esign import EnvelopesApi, EnvelopeDefinition, Document, Signer, SignHere, Tabs, Recipients, \ + CustomFields, TextCustomField, Text, RecipientViewRequest +from flask import current_app as app, session, url_for, request + +from ...consts import demo_docs_path, authentication_method, signer_client_id, pattern +from ...docusign import create_api_client +from ...ds_config import DS_CONFIG + + +class Eg016Controller: + @staticmethod + def get_args(): + """Get required session and request arguments""" + # More data validation would be a good idea here + # Strip anything other than characters listed + signer_email = pattern.sub("", request.form.get("signer_email")) + signer_name = pattern.sub("", request.form.get("signer_name")) + envelope_args = { + "signer_email": signer_email, + "signer_name": signer_name, + "signer_client_id": signer_client_id, + "ds_return_url": url_for("ds.ds_return", _external=True), + } + args = { + "account_id": session["ds_account_id"], + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], + "envelope_args": envelope_args + } + return args + + @classmethod + def worker(cls, args): + """ + 1. Create the envelope request object + 2. Send the envelope + 3. Create the Recipient View request object + 4. Obtain the recipient_view_url for the signing ceremony + """ + envelope_args = args["envelope_args"] + # 1. Create the envelope request object + envelope_definition = cls.make_envelope(envelope_args) + + # 2. call Envelopes::create API method + # Exceptions will be caught by the calling function + api_client = create_api_client( + base_path=args["base_path"], + access_token=args["access_token"] + ) + + envelopes_api = EnvelopesApi(api_client) + results = envelopes_api.create_envelope(account_id=args["account_id"], envelope_definition=envelope_definition) + + envelope_id = results.envelope_id + app.logger.info(f"Envelope was created. EnvelopeId {envelope_id}") + + # 3. Create the RecipientViewRequest object + recipient_view_request = RecipientViewRequest( + authentication_method=authentication_method, + client_user_id=envelope_args["signer_client_id"], + recipient_id="1", + return_url=envelope_args["ds_return_url"], + user_name=envelope_args["signer_name"], email=envelope_args["signer_email"] + ) + # 4. Obtain the recipient view URL for the signing ceremony + # Exceptions will be caught by the calling function + results = envelopes_api.create_recipient_view( + account_id=args["account_id"], + envelope_id=envelope_id, + recipient_view_request=recipient_view_request + ) + + return {"envelope_id": envelope_id, "redirect_url": results.url} + + @classmethod + def make_envelope(cls, args): + """ + Creates envelope + args -- parameters for the envelope: + signer_email, signer_name, signer_client_id + returns an envelope definition + """ + + # Document 1 (PDF) has tag /sn1/ + # + # The envelope has one recipient: + # recipient 1 - signer + with open(path.join(demo_docs_path, DS_CONFIG["doc_salary_docx"]), "rb") as file: + content_bytes = file.read() + base64_file_content = base64.b64encode(content_bytes).decode("ascii") + + # Create the document model + document = Document( # Create the DocuSign document object + document_base64=base64_file_content, + name="Lorem Ipsum", # Can be different from the actual filename + file_extension="docx", # Many different document types are accepted + document_id=1 # A label used to reference the doc + ) + + # Create the signer recipient model + signer = Signer( # The signer + email=args["signer_email"], name=args["signer_name"], + recipient_id="1", routing_order="1", + # Setting the client_user_id marks the signer as embedded + client_user_id=args["signer_client_id"] + ) + + # Create a SignHere tab (field on the document) + sign_here = SignHere( # DocuSign SignHere field/tab + anchor_string="/sn1/", anchor_units="pixels", + anchor_y_offset="10", anchor_x_offset="20" + ) + + text_legal = Text( + anchor_string="/legal/", anchor_units="pixels", + anchor_y_offset="-9", anchor_x_offset="5", + font="helvetica", font_size="size11", + bold="true", value=args["signer_name"], + locked="false", tab_id="legal_name", + tab_label="Legal name") + + text_familar = Text( + anchor_string="/familiar/", anchor_units="pixels", + anchor_y_offset="-9", anchor_x_offset="5", + font="helvetica", font_size="size11", + bold="true", value=args["signer_name"], + locked="false", tab_id="familar_name", + tab_label="Familiar name") + + salary = 123000 + + text_salary = Text( + anchor_string="/salary/", + anchor_units="pixels", + anchor_y_offset="-9", + anchor_x_offset="5", + font="helvetica", + font_size="size11", + bold="true", + value="${:.2f}".format(salary), + locked="true", + tab_id="salary", + tab_label="Salary") + + salary_custom_field = TextCustomField( + name="salary", + required="false", + show="true", # Yes, include in the CoC + value=str(salary) + ) + cf = CustomFields(text_custom_fields=[salary_custom_field]) + # Add the tabs model (including the SignHere tab) to the signer + # The Tabs object wants arrays of the different field/tab types + signer.tabs = Tabs(sign_here_tabs=[sign_here], text_tabs=[text_legal, text_familar, text_salary]) + + # Create the top level envelope definition and populate it + envelope_definition = EnvelopeDefinition( + email_subject="Please sign this document sent from the Python SDK", + documents=[document], + # The Recipients object wants arrays for each recipient type + recipients=Recipients(signers=[signer]), + custom_fields=cf, + status="sent" # Requests that the envelope be created and sent + ) + + return envelope_definition diff --git a/app/examples/eg016_set_tab_values/views.py b/app/examples/eg016_set_tab_values/views.py new file mode 100644 index 00000000..8ad37d08 --- /dev/null +++ b/app/examples/eg016_set_tab_values/views.py @@ -0,0 +1,62 @@ +"""Example 016: Set Tab Values""" + +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import render_template, redirect, session, Blueprint + +from .controller import Eg016Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg016" # Reference (and URL) for this example +eg016 = Blueprint("eg016", __name__) + + +# the signer? See the "authenticationMethod" definition +# https://developers.docusign.com/esign-rest-api/reference/Envelopes/EnvelopeViews/createRecipient + + +@eg016.route("/eg016", methods=["POST"]) +@authenticate(eg=eg) +def set_tab_values(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Redirect the user to the signing ceremony + """ + + # 1. Get required arguments + args = Eg016Controller.get_args() + + try: + # 2. Call the worker method for setting tab values + results = Eg016Controller.worker(args) + except ApiException as err: + return process_error(err) + + session["envelope_id"] = results["envelope_id"] # Save for use by other examples + # that need an envelope ID + # 3. Redirect the user to the signing ceremony + # Don"t use an iframe! + # State can be stored/recovered using the framework"s session or a + # query parameter on the return URL (see the makeRecipientViewRequest method) + return redirect(results["redirect_url"]) + + +@eg016.route("/eg016", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """Responds with the form for the example""" + + return render_template( + "eg016_set_tab_values.html", + title="SetTabValues", + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + signer_name=DS_CONFIG["signer_name"], + signer_email=DS_CONFIG["signer_email"] + ) diff --git a/app/examples/eg017_set_template_tab_values/__init__.py b/app/examples/eg017_set_template_tab_values/__init__.py new file mode 100644 index 00000000..5c5c41b3 --- /dev/null +++ b/app/examples/eg017_set_template_tab_values/__init__.py @@ -0,0 +1 @@ +from .views import eg017 diff --git a/app/examples/eg017_set_template_tab_values/controller.py b/app/examples/eg017_set_template_tab_values/controller.py new file mode 100644 index 00000000..13ca7c48 --- /dev/null +++ b/app/examples/eg017_set_template_tab_values/controller.py @@ -0,0 +1,163 @@ +from docusign_esign import EnvelopesApi, EnvelopeDefinition, RecipientViewRequest, Tabs, TemplateRole, RadioGroup, \ + TextCustomField, Text, CustomFields, Checkbox, Radio, List +from flask import current_app as app, url_for, request +from flask import session + +from ...consts import authentication_method, signer_client_id, pattern +from ...docusign import create_api_client + + +class Eg017Controller: + @staticmethod + def get_args(): + """Get required session and request arguments""" + # More data validation would be a good idea here + # Strip anything other than characters listed + signer_email = pattern.sub("", request.form.get("signer_email")) + signer_name = pattern.sub("", request.form.get("signer_name")) + cc_email = pattern.sub("", request.form.get("cc_email")) + cc_name = pattern.sub("", request.form.get("cc_name")) + envelope_args = { + "signer_email": signer_email, + "signer_name": signer_name, + "signer_client_id": signer_client_id, + "ds_return_url": url_for("ds.ds_return", _external=True), + "cc_email": cc_email, + "cc_name": cc_name + } + args = { + "account_id": session["ds_account_id"], + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], + "envelope_args": envelope_args + } + return args + + @classmethod + def worker(cls, args): + """ + 1. Create the envelope request object + 2. Send the envelope + 3. Create the Recipient View request object + 4. Obtain the recipient_view_url for the signing ceremony + """ + envelope_args = args["envelope_args"] + # 1. Create the envelope request object + envelope_definition = cls.make_envelope(envelope_args) + + # 2. call Envelopes::create API method + # Exceptions will be caught by the calling function + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + envelopes_api = EnvelopesApi(api_client) + results = envelopes_api.create_envelope(account_id=args["account_id"], envelope_definition=envelope_definition) + + envelope_id = results.envelope_id + app.logger.info(f"Envelope was created. EnvelopeId {envelope_id}") + + # 3. Create the Recipient View request object + recipient_view_request = RecipientViewRequest( + authentication_method=authentication_method, + client_user_id=envelope_args["signer_client_id"], + recipient_id="1", + return_url=envelope_args["ds_return_url"], + user_name=envelope_args["signer_name"], email=envelope_args["signer_email"] + ) + # 4. Obtain the recipient_view_url for the signing ceremony + # Exceptions will be caught by the calling function + results = envelopes_api.create_recipient_view( + account_id=args["account_id"], + envelope_id=envelope_id, + recipient_view_request=recipient_view_request + ) + return {"envelope_id": envelope_id, "redirect_url": results.url} + + @classmethod + def make_envelope(cls, args): + """ + Creates envelope + args -- parameters for the envelope: + signer_email, signer_name, signer_client_id + returns an envelope definition + """ + + # Set the values for the fields in the template + # List item + list1 = List( + value="green", document_id="1", + page_number="1", tab_label="list") + + # Checkboxes + check1 = Checkbox( + tab_label="ckAuthorization", selected="true") + + check3 = Checkbox( + tab_label="ckAgreement", selected="true") + + radio_group = RadioGroup( + group_name="radio1", + radios=[Radio(value="white", selected="true")] + ) + + text = Text( + tab_label="text", value="Jabberywocky!" + ) + + # We can also add a new tab (field) to the ones already in the template: + text_extra = Text( + document_id="1", page_number="1", + x_position="280", y_position="172", + font="helvetica", font_size="size14", + tab_label="added text field", height="23", + width="84", required="false", + bold="true", value=args["signer_name"], + locked="false", tab_id="name" + ) + + # Add the tabs model (including the SignHere tab) to the signer. + # The Tabs object wants arrays of the different field/tab types + # Tabs are set per recipient / signer + tabs = Tabs( + checkbox_tabs=[check1, check3], radio_group_tabs=[radio_group], + text_tabs=[text, text_extra], list_tabs=[list1] + ) + + # create a signer recipient to sign the document, identified by name and email + # We"re setting the parameters via the object creation + signer = TemplateRole( # The signer + email=args["signer_email"], name=args["signer_name"], + # Setting the client_user_id marks the signer as embedded + client_user_id=args["signer_client_id"], + role_name="signer", + tabs=tabs + ) + + cc = TemplateRole( + email=args["cc_email"], + name=args["cc_name"], + role_name="cc" + ) + + # create an envelope custom field to save our application"s + # data about the envelope + + custom_field = TextCustomField( + name="app metadata item", + required="false", + show="true", # Yes, include in the CoC + value="1234567" + ) + + cf = CustomFields(text_custom_fields=[custom_field]) + + # Next, create the top level envelope definition and populate it. + envelope_definition = EnvelopeDefinition( + email_subject="Please sign this document sent from the Python SDK", + # The Recipients object wants arrays for each recipient type + template_id=session["template_id"], + template_roles=[signer, cc], + custom_fields=cf, + status="sent" # requests that the envelope be created and sent. + ) + + return envelope_definition diff --git a/app/examples/eg017_set_template_tab_values/views.py b/app/examples/eg017_set_template_tab_values/views.py new file mode 100644 index 00000000..25f9b0a5 --- /dev/null +++ b/app/examples/eg017_set_template_tab_values/views.py @@ -0,0 +1,63 @@ +"""Example 017: Set Template Tab Values""" + +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import render_template, redirect, session, Blueprint + +from .controller import Eg017Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg017" # reference (and url) for this example +eg017 = Blueprint("eg017", __name__) + + +# the signer? See the "authenticationMethod" definition +# https://developers.docusign.com/esign-rest-api/reference/Envelopes/EnvelopeViews/createRecipient + + +@eg017.route("/eg017", methods=["POST"]) +@authenticate(eg=eg) +def set_template_tab_values(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Redirect the user to the signing ceremony + """ + + # 1. Get required arguments + args = Eg017Controller.get_args() + + try: + # 2. Call the worker method for setting template tab values + results = Eg017Controller.worker(args) + except ApiException as err: + return process_error(err) + + session["envelope_id"] = results["envelope_id"] # Save for use by other examples + # which need an envelopeId + # 3. Redirect the user to the Signing Ceremony + # Don"t use an iFrame! + # State can be stored/recovered using the framework"s session or a + # query parameter on the returnUrl (see the makeRecipientViewRequest method) + return redirect(results["redirect_url"]) + + +@eg017.route("/eg017", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """Responds with the form for the example""" + + return render_template( + "eg017_set_template_tab_values.html", + title="SetTemplateTabValues", + template_ok="template_id" in session, + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + signer_name=DS_CONFIG["signer_name"], + signer_email=DS_CONFIG["signer_email"] + ) diff --git a/app/examples/eg018_envelope_custom_field_data/__init__.py b/app/examples/eg018_envelope_custom_field_data/__init__.py new file mode 100644 index 00000000..96299bf3 --- /dev/null +++ b/app/examples/eg018_envelope_custom_field_data/__init__.py @@ -0,0 +1 @@ +from .views import eg018 diff --git a/app/examples/eg018_envelope_custom_field_data/controller.py b/app/examples/eg018_envelope_custom_field_data/controller.py new file mode 100644 index 00000000..8647b377 --- /dev/null +++ b/app/examples/eg018_envelope_custom_field_data/controller.py @@ -0,0 +1,29 @@ +from docusign_esign import EnvelopesApi +from flask import session + +from ...docusign import create_api_client + + +class Eg018Controller: + @staticmethod + def get_args(): + """Get required session arguments""" + return { + "account_id": session["ds_account_id"], + "envelope_id": session["envelope_id"], + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], + } + + @staticmethod + def worker(args): + """ + 1. Call the envelope get method + """ + + # Exceptions will be caught by the calling function + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + envelopes_api = EnvelopesApi(api_client) + results = envelopes_api.list_custom_fields(account_id=args["account_id"], envelope_id=args["envelope_id"]) + + return results diff --git a/app/examples/eg018_envelope_custom_field_data/views.py b/app/examples/eg018_envelope_custom_field_data/views.py new file mode 100644 index 00000000..4b09642e --- /dev/null +++ b/app/examples/eg018_envelope_custom_field_data/views.py @@ -0,0 +1,69 @@ +"""018: Get an envelope"s custom field data""" + +import json +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import render_template, session, Blueprint + +from .controller import Eg018Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg018" # reference (and URL) for this example +eg018 = Blueprint("eg018", __name__) + + +@eg018.route("/eg018", methods=["POST"]) +@authenticate(eg=eg) +def envelope_custom_field_data(): + """ + 1. Get required args + 2. Call the worker method + 3. Show custom field data + """ + + if "envelope_id" in session: + # 1. Get required args + args = Eg018Controller.get_args() + + try: + # 2. Call the worker method for creating envelope with custom field + results = Eg018Controller.worker(args) + except ApiException as err: + return process_error(err) + # 3. Step render the results + return render_template( + "example_done.html", + title="Get custom field data", + h1="Envelope custom field data", + message="Results from the EnvelopeCustomFields::list method:", + json=json.dumps(json.dumps(results.to_dict())) + ) + else: + return render_template( + "eg018_envelope_custom_field_data.html", + title="Envelope Custom Field Data", + envelope_ok=False, + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + ) + + +@eg018.route("/eg018", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """Responds with the form for the example""" + + return render_template( + "eg015_envelope_tab_data.html", + title="Envelope information", + envelope_ok="envelope_id" in session, + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + ) diff --git a/app/examples/eg019_access_code_authentication/__init__.py b/app/examples/eg019_access_code_authentication/__init__.py new file mode 100644 index 00000000..7b48ff8c --- /dev/null +++ b/app/examples/eg019_access_code_authentication/__init__.py @@ -0,0 +1 @@ +from .views import eg019 diff --git a/app/examples/eg019_access_code_authentication/controller.py b/app/examples/eg019_access_code_authentication/controller.py new file mode 100644 index 00000000..2a938afc --- /dev/null +++ b/app/examples/eg019_access_code_authentication/controller.py @@ -0,0 +1,98 @@ +import base64 +from os import path + +from docusign_esign import EnvelopesApi, EnvelopeDefinition, Document, Signer, SignHere, Tabs, Recipients +from flask import session, request + +from ...consts import demo_docs_path, pattern +from ...docusign import create_api_client +from ...ds_config import DS_CONFIG + + +class Eg019Controller: + @staticmethod + def get_args(): + """Get required session and request arguments""" + # More data validation would be a good idea here + # Strip anything other than characters listed + signer_email = pattern.sub("", request.form.get("signer_email")) + signer_name = pattern.sub("", request.form.get("signer_name")) + recip_access_code = request.form.get("accessCode") + envelope_args = { + "signer_email": signer_email, + "signer_name": signer_name, + "status": "sent", + "recip_access_code": recip_access_code + } + args = { + "account_id": session["ds_account_id"], # represents your {ACCOUNT_ID} + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], # represnts your {ACCESS_TOKEN} + "envelope_args": envelope_args + } + return args + + @staticmethod + def worker(args): + """ + 1. Create an api client + 2. Create an envelope definition object + 3. Call the eSignature REST API using the SDK + """ + # Step 1: Construct your API headers + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + # Step 2: Construct your envelope + envelope_definition = EnvelopeDefinition( + email_subject="Please sign this document set" + ) + + # Open the example file + with open(path.join(demo_docs_path, DS_CONFIG["doc_pdf"]), "rb") as file: + content_bytes = file.read() + base64_file_content = base64.b64encode(content_bytes).decode("ascii") + + # Add a Document + document1 = Document( # create the DocuSign document object + document_base64=base64_file_content, + document_id="1", # a label used to reference the doc + file_extension="pdf", # many different document types are accepted + name="Lorem" # can be different from actual file name + ) + + envelope_definition.documents = [document1] + envelope_definition.status = args["envelope_args"]["status"] + + signer1 = Signer( + email=args["envelope_args"]["signer_email"], # represents your {signer_name} + name=args["envelope_args"]["signer_name"], # represents your {signer_email} + access_code=args["envelope_args"]["recip_access_code"], + # represents your {ACCESS_CODE} for your recipient to access the envelope + recipient_id="1", + routing_order="1" + ) + + # Create your signature tab + sign_here1 = SignHere( + name="SignHereTab", + x_position="75", + y_position="572", + tab_label="SignHereTab", + page_number="1", + document_id="1", + # A 1- to 8-digit integer or 32-character GUID to match recipient IDs on your own systems. + # This value is referenced in the Tabs element below to assign tabs on a per-recipient basis. + recipient_id="1" # represents your {RECIPIENT_ID} + ) + + # Add the tabs model (including the sign_here tabs) to the signer + # The Tabs object wants arrays of the different field/tab types + signer1.tabs = Tabs(sign_here_tabs=[sign_here1]) + + # Tabs are set per recipient + envelope_definition.recipients = Recipients(signers=[signer1]) + # Step 3: Call the eSignature REST API + envelope_api = EnvelopesApi(api_client) + results = envelope_api.create_envelope(account_id=args["account_id"], envelope_definition=envelope_definition) + + return results diff --git a/app/examples/eg019_access_code_authentication/views.py b/app/examples/eg019_access_code_authentication/views.py new file mode 100644 index 00000000..8b899850 --- /dev/null +++ b/app/examples/eg019_access_code_authentication/views.py @@ -0,0 +1,58 @@ +""" Example 019: Access Code Recipient Authentication""" + +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import current_app as app +from flask import render_template, Blueprint + +from .controller import Eg019Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg019" # reference (and url) for this example +eg019 = Blueprint("eg019", __name__) + +@eg019.route("/eg019", methods=["POST"]) +@authenticate(eg=eg) +def access_code_authentication(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Render success response + """ + args = Eg019Controller.get_args() + try: + # Step 1: Call the worker method for authenticating with access code + results = Eg019Controller.worker(args) + envelope_id = results.envelope_id + + app.logger.info(f"Envelope was created. EnvelopeId {envelope_id} ") + + return render_template( + "example_done.html", + title="Envelope sent", + h1="Envelope sent", + message=f"""The envelope has been created and sent!
Envelope ID {envelope_id}.""" + ) + + except ApiException as err: + return process_error(err) + + +@eg019.route("/eg019", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """Responds with the form for the example""" + + return render_template( + "eg019_access_code_authentication.html", + title="Access-code recipient authentication", + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + signer_name=DS_CONFIG["signer_name"], + signer_email=DS_CONFIG["signer_email"] + ) diff --git a/app/examples/eg020_sms_authentication/__init__.py b/app/examples/eg020_sms_authentication/__init__.py new file mode 100644 index 00000000..f06e8874 --- /dev/null +++ b/app/examples/eg020_sms_authentication/__init__.py @@ -0,0 +1 @@ +from .views import eg020 diff --git a/app/examples/eg020_sms_authentication/controller.py b/app/examples/eg020_sms_authentication/controller.py new file mode 100644 index 00000000..dc222105 --- /dev/null +++ b/app/examples/eg020_sms_authentication/controller.py @@ -0,0 +1,100 @@ +import base64 +from os import path + +from docusign_esign import EnvelopesApi, EnvelopeDefinition, Document, Signer, SignHere, Tabs, Recipients +from flask import request, session + +from ...consts import demo_docs_path, pattern +from ...docusign import create_api_client +from ...ds_config import DS_CONFIG + + +class Eg020Controller: + @staticmethod + def get_args(): + """Get required session and request arguments""" + # More data validation would be a good idea here + # Strip anything other than characters listed + phone_number = request.form.get("phone_number") + signer_email = pattern.sub("", request.form.get("signer_email")) + signer_name = pattern.sub("", request.form.get("signer_name")) + envelope_args = { + "signer_email": signer_email, + "signer_name": signer_name, + "status": "sent", + "phone_number": phone_number + } + + args = { + "account_id": session["ds_account_id"], # represents your {ACCOUNT_ID} + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], # represnts your {ACCESS_TOKEN} + "envelope_args": envelope_args + } + return args + + @staticmethod + def worker(args): + """ + 1. Create an api client + 2. Create an envelope definition object + 3. Call the eSignature REST API using the SDK + """ + # Step 1: Construct your API headers + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + # Step 2: Construct your envelope + envelope_definition = EnvelopeDefinition( + email_subject="Please sign this document set" + ) + + # Open the example file + with open(path.join(demo_docs_path, DS_CONFIG["doc_pdf"]), "rb") as file: + content_bytes = file.read() + base64_file_content = base64.b64encode(content_bytes).decode("ascii") + + # Add a Document + document1 = Document( # create the DocuSign document object + document_base64=base64_file_content, + document_id="1", # a label used to reference the doc + file_extension="pdf", # many different document types are accepted + name="Lorem" # can be different from actual file name + ) + + envelope_definition.documents = [document1] + envelope_definition.status = args["envelope_args"]["status"] + + signer1 = Signer( + email=args["envelope_args"]["signer_email"], # represents your {signer_email} + name=args["envelope_args"]["signer_name"], # represents your {signer_name} + sms_authentication={"senderProvidedNumbers": [args["envelope_args"]["phone_number"]]}, + id_check_configuration_name="SMS Auth $", + require_id_lookup="true", + recipient_id="1", + routing_order="1" + ) + + # Create your signature tab + sign_here1 = SignHere( + name="SignHereTab", + x_position="75", + y_position="572", + tab_label="SignHereTab", + page_number="1", + document_id="1", + # A 1- to 8-digit integer or 32-character GUID to match recipient IDs on your own systems. + # This value is referenced in the Tabs element below to assign tabs on a per-recipient basis. + recipient_id="1" # represents your {RECIPIENT_ID} + ) + + # Add the tabs model (including the sign_here tabs) to the signer + # The Tabs object wants arrays of the different field/tab types + signer1.tabs = Tabs(sign_here_tabs=[sign_here1]) + + # Tabs are set per recipient + envelope_definition.recipients = Recipients(signers=[signer1]) + # Step 3: Call the eSignature REST API + envelope_api = EnvelopesApi(api_client) + results = envelope_api.create_envelope(account_id=args["account_id"], envelope_definition=envelope_definition) + + return results diff --git a/app/examples/eg020_sms_authentication/views.py b/app/examples/eg020_sms_authentication/views.py new file mode 100644 index 00000000..d7122f17 --- /dev/null +++ b/app/examples/eg020_sms_authentication/views.py @@ -0,0 +1,61 @@ +""" Example 020: Sms Recipient Authentication""" + +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import current_app as app +from flask import render_template, Blueprint + +from .controller import Eg020Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg020" # reference (and url) for this example +eg020 = Blueprint("eg020", __name__) + + +@eg020.route("/eg020", methods=["POST"]) +@authenticate(eg=eg) +def sms_authentication(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Render success response + """ + + # 1. Get required arguments + args = Eg020Controller.get_args() + try: + # Step 2: Call the worker method for sms authenticating + results = Eg020Controller.worker(args) + envelope_id = results.envelope_id + app.logger.info(f"Envelope was created. EnvelopeId {envelope_id} ") + + # 3. Render success response + return render_template( + "example_done.html", + title="Envelope sent", + h1="Envelope sent", + message=f"""The envelope has been created and sent!
Envelope ID {envelope_id}.""" + ) + + except ApiException as err: + return process_error(err) + + +@eg020.route("/eg020", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """Responds with the form for the example""" + + return render_template( + "eg020_sms_authentication.html", + title="SMS recipient authentication", + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + signer_name=DS_CONFIG["signer_name"], + signer_email=DS_CONFIG["signer_email"] + ) diff --git a/app/examples/eg021_phone_authentication/__init__.py b/app/examples/eg021_phone_authentication/__init__.py new file mode 100644 index 00000000..88a2b913 --- /dev/null +++ b/app/examples/eg021_phone_authentication/__init__.py @@ -0,0 +1 @@ +from .views import eg021 diff --git a/app/examples/eg021_phone_authentication/controller.py b/app/examples/eg021_phone_authentication/controller.py new file mode 100644 index 00000000..9532e82b --- /dev/null +++ b/app/examples/eg021_phone_authentication/controller.py @@ -0,0 +1,99 @@ +import base64 +from os import path + +from docusign_esign import EnvelopesApi, EnvelopeDefinition, Document, Signer, SignHere, Tabs, Recipients +from flask import session, request + +from ...consts import demo_docs_path, pattern +from ...docusign import create_api_client +from ...ds_config import DS_CONFIG + + +class Eg021Controller: + @staticmethod + def get_args(): + """Get required session and request arguments""" + # More data validation would be a good idea here + # Strip anything other than characters listed + phone_number = request.form.get("phoneNumber") + signer_email = pattern.sub("", request.form.get("signer_email")) + signer_name = pattern.sub("", request.form.get("signer_name")) + envelope_args = { + "signer_email": signer_email, + "signer_name": signer_name, + "status": "sent", + "phone_number": phone_number + } + args = { + "account_id": session["ds_account_id"], # represents your {ACCOUNT_ID} + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], # represnts your {ACCESS_TOKEN} + "envelope_args": envelope_args + } + return args + + @staticmethod + def worker(args): + """ + 1. Create an api client + 2. Create an envelope definition object + 3. Call the eSignature REST API using the SDK + """ + # Step 1: Construct your API headers + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + # Step 2: Construct your envelope + envelope_definition = EnvelopeDefinition( + email_subject="Please sign this document set" + ) + + # Open the example file + with open(path.join(demo_docs_path, DS_CONFIG["doc_pdf"]), "rb") as file: + content_bytes = file.read() + base64_file_content = base64.b64encode(content_bytes).decode("ascii") + + # Add a Document + document1 = Document( # create the DocuSign document object + document_base64=base64_file_content, + document_id="1", # a label used to reference the doc + file_extension="pdf", # many different document types are accepted + name="Lorem" # can be different from actual file name + ) + + envelope_definition.documents = [document1] + envelope_definition.status = args["envelope_args"]["status"] + + signer1 = Signer( + email=args["envelope_args"]["signer_email"], # represents your {signer_email} + name=args["envelope_args"]["signer_name"], # represents your {signer_name} + phone_authentication={"senderProvidedNumbers": [args["envelope_args"]["phone_number"]]}, + id_check_configuration_name="Phone Auth $", + require_id_lookup="true", + recipient_id="1", + routing_order="1" + ) + + # Create your signature tab + sign_here1 = SignHere( + name="SignHereTab", + x_position="75", + y_position="572", + tab_label="SignHereTab", + page_number="1", + document_id="1", + # A 1- to 8-digit integer or 32-character GUID to match recipient IDs on your own systems. + # This value is referenced in the Tabs element below to assign tabs on a per-recipient basis. + recipient_id="1" # represents your {RECIPIENT_ID} + ) + + # Add the tabs model (including the sign_here tabs) to the signer + # The Tabs object wants arrays of the different field/tab types + signer1.tabs = Tabs(sign_here_tabs=[sign_here1]) + + # Tabs are set per recipient + envelope_definition.recipients = Recipients(signers=[signer1]) + # Step 3: Call the eSignature REST API + envelope_api = EnvelopesApi(api_client) + results = envelope_api.create_envelope(account_id=args["account_id"], envelope_definition=envelope_definition) + + return results diff --git a/app/examples/eg021_phone_authentication/views.py b/app/examples/eg021_phone_authentication/views.py new file mode 100644 index 00000000..b97ac28f --- /dev/null +++ b/app/examples/eg021_phone_authentication/views.py @@ -0,0 +1,61 @@ +""" Example 021: Recipient Phone Authentication""" + +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import current_app as app +from flask import render_template, Blueprint + +from .controller import Eg021Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg021" # reference (and url) for this example +eg021 = Blueprint("eg021", __name__) + + +@eg021.route("/eg021", methods=["POST"]) +@authenticate(eg=eg) +def phone_authentication(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Render success response + """ + + # 1. Get required arguments + args = Eg021Controller.get_args() + try: + # Step 2: Call the worker method for authenticating with phone + results = Eg021Controller.worker(args) + envelope_id = results.envelope_id + app.logger.info(f"Envelope was created. EnvelopeId {envelope_id} ") + + # 3. Render success response + return render_template( + "example_done.html", + title="Envelope sent", + h1="Envelope sent", + message=f"""The envelope has been created and sent!
Envelope ID {envelope_id}.""" + ) + + except ApiException as err: + return process_error(err) + + +@eg021.route("/eg021", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """Responds with the form for the example""" + + return render_template( + "eg021_phone_authentication.html", + title="Phone recipient authentication", + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + signer_name=DS_CONFIG["signer_name"], + signer_email=DS_CONFIG["signer_email"] + ) diff --git a/app/examples/eg022_kba_authentication/__init__.py b/app/examples/eg022_kba_authentication/__init__.py new file mode 100644 index 00000000..3c07b6dc --- /dev/null +++ b/app/examples/eg022_kba_authentication/__init__.py @@ -0,0 +1 @@ +from .views import eg022 diff --git a/app/examples/eg022_kba_authentication/controller.py b/app/examples/eg022_kba_authentication/controller.py new file mode 100644 index 00000000..1a1382ce --- /dev/null +++ b/app/examples/eg022_kba_authentication/controller.py @@ -0,0 +1,96 @@ +import base64 +from os import path + +from docusign_esign import EnvelopesApi, EnvelopeDefinition, Document, Signer, SignHere, Tabs, Recipients +from flask import session, request + +from ...consts import demo_docs_path, pattern +from ...docusign import create_api_client +from ...ds_config import DS_CONFIG + + +class Eg022Controller: + @staticmethod + def get_args(): + """Get required session and request arguments""" + # More data validation would be a good idea here + # Strip anything other than characters listed + signer_email = pattern.sub("", request.form.get("signer_email")) + signer_name = pattern.sub("", request.form.get("signer_name")) + envelope_args = { + "signer_email": signer_email, + "signer_name": signer_name, + "status": "sent", + } + args = { + "account_id": session["ds_account_id"], # represents your {ACCOUNT_ID} + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], # represnts your {ACCESS_TOKEN} + "envelope_args": envelope_args + } + return args + + @staticmethod + def worker(args): + """ + 1. Create an api client + 2. Create an envelope definition object + 3. Call the eSignature REST API using the SDK + """ + # Step 1: Construct your API headers + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + # Step 2: Construct your envelope + envelope_definition = EnvelopeDefinition( + email_subject="Please sign this document set" + ) + + # Open the example file + with open(path.join(demo_docs_path, DS_CONFIG["doc_pdf"]), "rb") as file: + content_bytes = file.read() + base64_file_content = base64.b64encode(content_bytes).decode("ascii") + + # Add a Document + document1 = Document( # create the DocuSign document object + document_base64=base64_file_content, + document_id="1", # a label used to reference the doc + file_extension="pdf", # many different document types are accepted + name="Lorem" # can be different from actual file name + ) + + envelope_definition.documents = [document1] + envelope_definition.status = args["envelope_args"]["status"] + + signer1 = Signer( + email=args["envelope_args"]["signer_email"], # represents your {signer_email} + name=args["envelope_args"]["signer_name"], # represents your {signer_name} + id_check_configuration_name="ID Check", # configuration name for KBA based Verification + require_id_lookup="true", + recipient_id="1", + routing_order="1" + ) + + # Create your signature tab + sign_here1 = SignHere( + name="SignHereTab", + x_position="75", + y_position="572", + tab_label="SignHereTab", + page_number="1", + document_id="1", + # A 1- to 8-digit integer or 32-character GUID to match recipient IDs on your own systems. + # This value is referenced in the Tabs element below to assign tabs on a per-recipient basis. + recipient_id="1" # represents your {RECIPIENT_ID} + ) + + # Add the tabs model (including the sign_here tabs) to the signer + # The Tabs object wants arrays of the different field/tab types + signer1.tabs = Tabs(sign_here_tabs=[sign_here1]) + + # Tabs are set per recipient + envelope_definition.recipients = Recipients(signers=[signer1]) + # Step 3: Call the eSignature REST API + envelope_api = EnvelopesApi(api_client) + results = envelope_api.create_envelope(account_id=args["account_id"], envelope_definition=envelope_definition) + + return results diff --git a/app/examples/eg022_kba_authentication/views.py b/app/examples/eg022_kba_authentication/views.py new file mode 100644 index 00000000..a901b10a --- /dev/null +++ b/app/examples/eg022_kba_authentication/views.py @@ -0,0 +1,60 @@ +""" Example 022: Knowledge Based authentication""" + +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import current_app as app +from flask import render_template, Blueprint + +from .controller import Eg022Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg022" # reference (and url) for this example +eg022 = Blueprint("eg022", __name__) + + +@eg022.route("/eg022", methods=["POST"]) +@authenticate(eg=eg) +def kba_authentication(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Render success response + """ + + # 1. Get required arguments + args = Eg022Controller.get_args() + try: + # Step 2: Call the worker method for kba + results = Eg022Controller.worker(args) + envelope_id = results.envelope_id + app.logger.info(f"Envelope was created. EnvelopeId {envelope_id} ") + + # 3. Render success response + return render_template( + "example_done.html", + title="Envelope sent", + h1="Envelope sent", + message=f"""The envelope has been created and sent!
Envelope ID {envelope_id}.""" + ) + + except ApiException as err: + return process_error(err) + + +@eg022.route("/eg022", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """Responds with the form for the example""" + return render_template( + "eg022_kba_authentication.html", + title="Kba recipient authentication", + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + signer_name=DS_CONFIG["signer_name"], + signer_email=DS_CONFIG["signer_email"] + ) diff --git a/app/examples/eg023_idv_authentication/__init__.py b/app/examples/eg023_idv_authentication/__init__.py new file mode 100644 index 00000000..b66f3e80 --- /dev/null +++ b/app/examples/eg023_idv_authentication/__init__.py @@ -0,0 +1 @@ +from .views import eg023 diff --git a/app/examples/eg023_idv_authentication/controller.py b/app/examples/eg023_idv_authentication/controller.py new file mode 100644 index 00000000..3dd342d4 --- /dev/null +++ b/app/examples/eg023_idv_authentication/controller.py @@ -0,0 +1,122 @@ +import base64 +from os import path + +from docusign_esign import AccountsApi, EnvelopesApi, EnvelopeDefinition, Document, Signer, SignHere, Tabs, Recipients +from docusign_esign.client.api_exception import ApiException +from flask import current_app as app, session, request + +from ...consts import demo_docs_path, pattern +from ...docusign import create_api_client +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + + +class Eg023Controller: + @staticmethod + def get_args(): + """Get required session and request arguments""" + # More data validation would be a good idea here + # Strip anything other than the characters listed + signer_email = pattern.sub("", request.form.get("signer_email")) + signer_name = pattern.sub("", request.form.get("signer_name")) + envelope_args = { + "signer_email": signer_email, + "signer_name": signer_name, + "status": "sent", + "workflow_id": session['workflow_id'] + } + args = { + "account_id": session["ds_account_id"], # represents your {ACCOUNT_ID} + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], # represnts your {ACCESS_TOKEN} + "envelope_args": envelope_args + } + return args + + @staticmethod + def worker(args): + """ + 1. Create an api client + 2. Create an envelope definition object + """ + # Step 1: Construct your API headers + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + # Step 3: Construct your envelope + envelope_definition = EnvelopeDefinition( + email_subject="Please sign this document set" + ) + + # Open the example file + with open(path.join(demo_docs_path, DS_CONFIG["doc_pdf"]), "rb") as file: + content_bytes = file.read() + base64_file_content = base64.b64encode(content_bytes).decode("ascii") + + # Add a document + document1 = Document( # create the DocuSign document object + document_base64=base64_file_content, + document_id="1", # a label used to reference the doc + file_extension="pdf", # many different document types are accepted + name="Lorem" # can be different from actual file name + ) + + envelope_definition.documents = [document1] + envelope_definition.status = args["envelope_args"]["status"] + + # Create your signature tab + sign_here1 = SignHere( + name="SignHereTab", + x_position="75", + y_position="572", + tab_label="SignHereTab", + page_number="1", + document_id="1", + # A 1- to 8-digit integer or 32-character GUID to match recipient IDs on your own systems. + # This value is referenced in the Tabs element below to assign tabs on a per-recipient basis. + recipient_id="1" # represents your {RECIPIENT_ID} + ) + + signer1 = Signer( + email=args["envelope_args"]["signer_email"], # Represents your {signer_email} + name=args["envelope_args"]["signer_name"], # Represents your {signer_name} + role_name="", + note="", + status="created", + delivery_method="email", + recipient_id="1", # Represents your {RECIPIENT_ID} + routing_order="1", + identity_verification={"workflowId": args["envelope_args"]["workflow_id"], "steps": "null"}, + tabs=Tabs(sign_here_tabs=[sign_here1]) + ) + + # Tabs are set per recipient + envelope_definition.recipients = Recipients(signers=[signer1]) + + # Step 4: Call the eSignature REST API + envelopes_api = EnvelopesApi(api_client) + results = envelopes_api.create_envelope(account_id=args["account_id"], envelope_definition=envelope_definition) + + return results + + @staticmethod + def get_workflow(args): + """Retrieve the workflow id""" + try: + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + workflow_details = AccountsApi(api_client) + workflow_response = workflow_details.get_account_identity_verification(account_id=args["account_id"]) + + # Check that idv authentication is enabled + if workflow_response.identity_verification: + workflow_id = workflow_response.identity_verification[0].workflow_id + app.logger.info("We found the following workflowID: " + workflow_id) + session['workflow_id'] = workflow_id + + return workflow_id + + else: + return None + + except ApiException as err: + return process_error(err) diff --git a/app/examples/eg023_idv_authentication/views.py b/app/examples/eg023_idv_authentication/views.py new file mode 100644 index 00000000..6effb6c7 --- /dev/null +++ b/app/examples/eg023_idv_authentication/views.py @@ -0,0 +1,69 @@ +""" Example 023: ID Verificiation Based authentication""" + +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import current_app as app +from flask import render_template, Blueprint, session + +from .controller import Eg023Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg023" # Reference (and URL) for this example +eg023 = Blueprint("eg023", __name__) + + +@eg023.route("/eg023", methods=["POST"]) +@authenticate(eg=eg) +def idv_authentication(): + """ + 1. Get required data + 2. Call the worker method + 3. Render success response + """ + # 1. Get required data + args = Eg023Controller.get_args() + try: + # 2: Call the worker method for idv authentication + results = Eg023Controller.worker(args) + envelope_id = results.envelope_id + app.logger.info(f"Envelope was created. EnvelopeId {envelope_id} ") + + # 3. Render success response + return render_template( + "example_done.html", + title="Envelope sent", + h1="Envelope sent", + message=f"The envelope has been created and sent!
Envelope ID {envelope_id}." + ) + + except ApiException as err: + return process_error(err) + + +@eg023.route("/eg023", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """Responds with the form for the example""" + args = { + "account_id": session["ds_account_id"], # represent your {ACCOUNT_ID} + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], # represent your {ACCESS_TOKEN} + + } + + workflow_id = Eg023Controller.get_workflow(args) + + return render_template( + "eg023_idv_authentication.html", + title="IDV authentication", + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + signer_name=DS_CONFIG["signer_name"], + signer_email=DS_CONFIG["signer_email"], + workflow_id=workflow_id + ) diff --git a/app/examples/eg024_permissions_creating/__init__.py b/app/examples/eg024_permissions_creating/__init__.py new file mode 100644 index 00000000..4511f0d1 --- /dev/null +++ b/app/examples/eg024_permissions_creating/__init__.py @@ -0,0 +1 @@ +from .views import eg024 \ No newline at end of file diff --git a/app/examples/eg024_permissions_creating/controller.py b/app/examples/eg024_permissions_creating/controller.py new file mode 100644 index 00000000..9915576c --- /dev/null +++ b/app/examples/eg024_permissions_creating/controller.py @@ -0,0 +1,45 @@ +from docusign_esign import AccountsApi, PermissionProfile +from flask import session, request + +from ...consts import settings +from ...docusign import create_api_client + + +class Eg024Controller: + @staticmethod + def get_args(): + """Get required session and request arguments""" + args = { + "account_id": session["ds_account_id"], # represent your {ACCOUNT_ID} + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], # represent your {ACCESS_TOKEN} + "permission_profile_name": request.form.get("permission_profile_name"), + "settings": settings + } + return args + + @staticmethod + def worker(args): + """ + 1. Create an api client + 2. Create a permission profile object + 3. Create the permission profile using the SDK + """ + + # Step 2. Construct your API request headers + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + # Step 3. Construct your request body + permission_profile = PermissionProfile( + permission_profile_name=args["permission_profile_name"], + settings=args["settings"] + ) + + # Step 4. Call the eSignature REST API + account_api = AccountsApi(api_client) + response = account_api.create_permission_profile( + account_id=args["account_id"], + permission_profile=permission_profile + ) + + return response diff --git a/app/examples/eg024_permissions_creating/views.py b/app/examples/eg024_permissions_creating/views.py new file mode 100644 index 00000000..5f79f7cb --- /dev/null +++ b/app/examples/eg024_permissions_creating/views.py @@ -0,0 +1,59 @@ +"""Example 024: Creating a permission profile""" + +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import current_app as app +from flask import render_template, Blueprint + +from .controller import Eg024Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg024" +eg024 = Blueprint("eg024", __name__) + + +@eg024.route("/eg024", methods=["POST"]) +@authenticate(eg=eg) +def permissions_creating(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Render a response + """ + + # 1. Get required args + args = Eg024Controller.get_args() + try: + # 2. Call the worker method to create a permission profile + response = Eg024Controller.worker(args) + permission_profile_id = response.permission_profile_id + app.logger.info(f"The permission profile has been created. Permission profile ID: {permission_profile_id}") + + # 3. Render the response + return render_template( + "example_done.html", + title="Creating a permission profile", + h1="Creating a permission profile", + message=f"""The permission profile has been created!
Permission profile ID: {permission_profile_id}.""" + ) + + except ApiException as err: + return process_error(err) + +@eg024.route("/eg024", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """Responds with the form for the example""" + + return render_template( + "eg024_permissions_creating.html", + title="Creating a permission profile", + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + permission_profile_name="Sample Profile 134972-Alpha" + ) \ No newline at end of file diff --git a/app/examples/eg025_permissions_set_user_group/__init__.py b/app/examples/eg025_permissions_set_user_group/__init__.py new file mode 100644 index 00000000..eab4fbef --- /dev/null +++ b/app/examples/eg025_permissions_set_user_group/__init__.py @@ -0,0 +1 @@ +from .views import eg025 \ No newline at end of file diff --git a/app/examples/eg025_permissions_set_user_group/controller.py b/app/examples/eg025_permissions_set_user_group/controller.py new file mode 100644 index 00000000..06f63ed9 --- /dev/null +++ b/app/examples/eg025_permissions_set_user_group/controller.py @@ -0,0 +1,53 @@ +from docusign_esign import AccountsApi, Group, GroupInformation, GroupsApi +from docusign_esign.client.api_exception import ApiException +from flask import session, request +from ...docusign import create_api_client +from ...error_handlers import process_error + +class Eg025Controller: + @staticmethod + def get_args(): + return { + "account_id": session["ds_account_id"], # Represents your {ACCOUNT_ID} + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], # Represents your {ACCESS_TOKEN} + "permission_profile_id": request.form.get("permission_profile"), + "group_id": request.form.get("group") + } + + @staticmethod + def worker(args): + """ + Step 1: Create an API client + Step 2: Create a group object + Step 3: Create a group information object + Step 4: Update the group + """ + + # Step 2. Construct your API headers + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + group_api = GroupsApi(api_client) + + # Step 3. Construct your request body + group = Group(group_id=args["group_id"], permission_profile_id=args["permission_profile_id"]) + group_information = GroupInformation(groups=[group]) + + # Step 4. Call the eSignature REST API + response = group_api.update_groups(account_id=args["account_id"], group_information=group_information) + + return response + + @staticmethod + def get_data(args): + """Retrieve groups and permission profiles""" + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + try: + account_api = AccountsApi(api_client) + group_api = GroupsApi(api_client) + permission_profiles = account_api.list_permissions(account_id=args["account_id"]).permission_profiles + groups = group_api.list_groups(account_id=args["account_id"]).groups + + return permission_profiles, groups + + except ApiException as err: + return process_error(err) \ No newline at end of file diff --git a/app/examples/eg025_permissions_set_user_group/views.py b/app/examples/eg025_permissions_set_user_group/views.py new file mode 100644 index 00000000..a1286c42 --- /dev/null +++ b/app/examples/eg025_permissions_set_user_group/views.py @@ -0,0 +1,66 @@ +"""Example 025: Set a permission profile for a group of users""" + +from os import path +from docusign_esign.client.api_exception import ApiException +from flask import current_app as app +from flask import render_template, session, request, Blueprint +from .controller import Eg025Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg025" +eg025 = Blueprint("eg025", __name__) + +@eg025.route("/eg025", methods=["POST"]) +@authenticate(eg=eg) +def permissions_set_user_group(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Render a response + """ + + # 1. Get required arguments + args = Eg025Controller.get_args() + try: + # 2. Call the worker method to set the permission profile + response = Eg025Controller.worker(args) + app.logger.info(f"The permission profile has been set.") + permission_profile_id = response.groups[0].permission_profile_id + group_id = response.groups[0].group_id + + # 3. Render the response + return render_template( + "example_done.html", + title="Set a permission profile for a group of users", + h1="Setting a permission profile for a group of users", + message=f"""The permission profile has been set!
""" + f"""Permission profile ID: {permission_profile_id}
""" + f"""Group id: {group_id}""" + ) + + except ApiException as err: + return process_error(err) + +@eg025.route("/eg025", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """Responds with the form for the example""" + + args = { + "account_id": session["ds_account_id"], # Represents your {ACCOUNT_ID} + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], # Represents your {ACCESS_TOKEN} + } + permission_profiles, groups = Eg025Controller.get_data(args) + return render_template( + "eg025_permissions_set_user_group.html", + title="Setting a permission profile", + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + permission_profiles=permission_profiles, + groups=groups + ) \ No newline at end of file diff --git a/app/examples/eg026_permissions_change_single_setting/__init__.py b/app/examples/eg026_permissions_change_single_setting/__init__.py new file mode 100644 index 00000000..7b085b79 --- /dev/null +++ b/app/examples/eg026_permissions_change_single_setting/__init__.py @@ -0,0 +1 @@ +from .views import eg026 \ No newline at end of file diff --git a/app/examples/eg026_permissions_change_single_setting/controller.py b/app/examples/eg026_permissions_change_single_setting/controller.py new file mode 100644 index 00000000..6059a7b0 --- /dev/null +++ b/app/examples/eg026_permissions_change_single_setting/controller.py @@ -0,0 +1,75 @@ +from docusign_esign import AccountsApi, PermissionProfile +from docusign_esign.client.api_exception import ApiException +from flask import request, session +from ...consts import settings +from ...docusign import create_api_client +from ...error_handlers import process_error + +class Eg026Controller: + @staticmethod + def get_args(): + """Get required session and request arguments""" + permission_profile_id = request.form.get("permission_profile") + args = { + "account_id": session["ds_account_id"], # Represents your {ACCOUNT_ID} + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], # Represents your {ACCESS_TOKEN} + "permission_profile_id": permission_profile_id, + "settings": settings + } + return args + + @staticmethod + def worker(args): + """ + 1. Create an API client + 2. Create a permission profile object + 3. Get existing profile settings + 4. Change the permission profile setting using the SDK + """ + + # Step 2. Construct your API headers + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + # Step 3. Construct your request body + permission_profile = PermissionProfile( + settings=args["settings"] + ) + account_api = AccountsApi(api_client) + previous_settings = account_api.get_permission_profile( + account_id=args["account_id"], + permission_profile_id=args["permission_profile_id"] + ).settings.to_dict() + # Step 4. Call the eSignature REST API + response = account_api.update_permission_profile( + account_id=args["account_id"], + permission_profile_id=args["permission_profile_id"], + permission_profile=permission_profile + ) + new_settings = response.settings.to_dict() + changed_settings = {} + + # Save only changed settings + for k, v in new_settings.items(): + if v != previous_settings[k]: + key = " ".join(k.split("_")) + changed_settings[key] = v + + return response, changed_settings + + @staticmethod + def get_permissions_profiles(args): + """Retrieve all permissions profiles""" + api_client = create_api_client( + base_path=args["base_path"], + access_token=args["access_token"] + ) + + try: + account_api = AccountsApi(api_client) + response = account_api.list_permissions(account_id=args["account_id"]) + + return response.permission_profiles + + except ApiException as err: + return process_error(err) \ No newline at end of file diff --git a/app/examples/eg026_permissions_change_single_setting/views.py b/app/examples/eg026_permissions_change_single_setting/views.py new file mode 100644 index 00000000..851a3274 --- /dev/null +++ b/app/examples/eg026_permissions_change_single_setting/views.py @@ -0,0 +1,68 @@ +"""Example 026: Changing a single setting in a permission profile""" + +from os import path + +from docusign_esign.client.api_exception import ApiException +from flask import current_app as app +from flask import render_template, session, Blueprint + +from .controller import Eg026Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg026" +eg026 = Blueprint("eg026", __name__) + +@eg026.route("/eg026", methods=["POST"]) +@authenticate(eg=eg) +def permissions_change_single_setting(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Render a response + """ + + # 1. Get required arguments + args = Eg026Controller.get_args() + try: + # 2. Call the worker method to change a setting in an existing permission profile + response, changed_settings = Eg026Controller.worker(args) + + permission_profile_id = response.permission_profile_id + + app.logger.info(f"Permission profile setting has been changed. Permission profile ID: {permission_profile_id}") + + # 3. Render the response + return render_template( + "example_done.html", + title="Changing setting in a permission profile", + h1="Changing setting in a permission profile", + message=f"""Setting of permission profile has been changed!
""" + f"""Permission profile ID: {permission_profile_id}.
Changed settings:""", + changed_settings=changed_settings + ) + + except ApiException as err: + return process_error(err) + +@eg026.route("/eg026", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """Responds with the form for the example""" + + args = { + "account_id": session["ds_account_id"], # Represents your {ACCOUNT_ID} + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], # Represents your {ACCESS_TOKEN} + } + permission_profiles = Eg026Controller.get_permissions_profiles(args) + return render_template( + "eg026_permissions_change_single_setting.html", + title="Changing a setting in an existing permission profile", + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + permission_profiles=permission_profiles + ) \ No newline at end of file diff --git a/app/examples/eg027_permissions_delete/__init__.py b/app/examples/eg027_permissions_delete/__init__.py new file mode 100644 index 00000000..0f01ca1c --- /dev/null +++ b/app/examples/eg027_permissions_delete/__init__.py @@ -0,0 +1 @@ +from .views import eg027 \ No newline at end of file diff --git a/app/examples/eg027_permissions_delete/controller.py b/app/examples/eg027_permissions_delete/controller.py new file mode 100644 index 00000000..a83e420c --- /dev/null +++ b/app/examples/eg027_permissions_delete/controller.py @@ -0,0 +1,46 @@ +from docusign_esign import AccountsApi +from docusign_esign.client.api_exception import ApiException +from flask import session, request +from ...docusign import create_api_client +from ...error_handlers import process_error + +class Eg027Controller: + @staticmethod + def get_args(): + """Get required session and request arguments""" + return { + "account_id": session["ds_account_id"], # Represents your {ACCOUNT_ID} + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], # Represents your {ACCESS_TOKEN} + "permission_profile_id": request.form.get("permission_profile"), + } + + @staticmethod + def worker(args): + """ + Step 1: Create an API client + Step 2: Delete the permission profile using SDK + """ + + # Step 2. Construct your API headers + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + account_api = AccountsApi(api_client) + + # Step 3. Call the eSignature REST API + account_api.delete_permission_profile( + account_id=args["account_id"], + permission_profile_id=args["permission_profile_id"]) + + @staticmethod + def get_permissions_profiles(args): + """Retrieve all permissions profiles""" + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + try: + account_api = AccountsApi(api_client) + response = account_api.list_permissions(account_id=args["account_id"]) + + return response.permission_profiles + + except ApiException as err: + return process_error(err) \ No newline at end of file diff --git a/app/examples/eg027_permissions_delete/views.py b/app/examples/eg027_permissions_delete/views.py new file mode 100644 index 00000000..f8700d4b --- /dev/null +++ b/app/examples/eg027_permissions_delete/views.py @@ -0,0 +1,60 @@ +"""Example 027: Deleting a permission profile""" + +from os import path +from docusign_esign.client.api_exception import ApiException +from flask import current_app as app +from flask import render_template, session, request, Blueprint +from .controller import Eg027Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg027" +eg027 = Blueprint("eg027", __name__) + +@eg027.route("/eg027", methods=["POST"]) +@authenticate(eg=eg) +def permissions_delete(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Render success response + """ + + args = Eg027Controller.get_args() + try: + # 2. Call the worker method to delete a permission profile + Eg027Controller.worker(args) + app.logger.info(f"The permission profile has been deleted.") + + # 3. Render success response + return render_template( + "example_done.html", + title="Deleting a permission profile", + h1="Deleting a permission profile", + message=f"The permission profile has been deleted!
" + ) + + except ApiException as err: + return process_error(err) + +@eg027.route("/eg027", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """Responds with the form for the example""" + + args = { + "account_id": session["ds_account_id"], # Represents your {ACCOUNT_ID} + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], # Represents your {ACCESS_TOKEN} + } + permission_profiles = Eg027Controller.get_permissions_profiles(args) + return render_template( + "eg027_permissions_delete.html", + title="Deleting a permission profile", + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + permission_profiles=permission_profiles + ) \ No newline at end of file diff --git a/app/examples/eg028_brand_creating/__init__.py b/app/examples/eg028_brand_creating/__init__.py new file mode 100644 index 00000000..41007bea --- /dev/null +++ b/app/examples/eg028_brand_creating/__init__.py @@ -0,0 +1 @@ +from .views import eg028 \ No newline at end of file diff --git a/app/examples/eg028_brand_creating/controller.py b/app/examples/eg028_brand_creating/controller.py new file mode 100644 index 00000000..fa249a3d --- /dev/null +++ b/app/examples/eg028_brand_creating/controller.py @@ -0,0 +1,37 @@ +from docusign_esign import AccountsApi, Brand +from flask import session, request +from ...docusign import create_api_client + +class Eg028Controller: + @staticmethod + def get_args(): + """Get required session and request arguments""" + return { + "account_id": session["ds_account_id"], # Represents your {ACCOUNT_ID} + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], # Represents your {ACCESS_TOKEN} + "brand_name": request.form.get("brand_name"), + "default_language": request.form.get("default_language") + } + + @staticmethod + def worker(args): + """ + 1. Create an API client with headers + 2. Create a brand object + 3. Post the brand using SDK + """ + + # Step 2. Construct your API headers + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + # Step 3. Construct your request body + brand = Brand( + brand_name=args["brand_name"], + default_brand_language=args["default_language"], + ) + + # Step 4. Call the eSignature REST API + account_api = AccountsApi(api_client) + response = account_api.create_brand(account_id=args["account_id"], brand=brand) + return response \ No newline at end of file diff --git a/app/examples/eg028_brand_creating/views.py b/app/examples/eg028_brand_creating/views.py new file mode 100644 index 00000000..d9e03f7c --- /dev/null +++ b/app/examples/eg028_brand_creating/views.py @@ -0,0 +1,57 @@ +"""Example 028: Creating a brand""" + +from os import path +from docusign_esign.client.api_exception import ApiException +from flask import current_app as app +from flask import render_template, session, request, Blueprint +from .controller import Eg028Controller +from ...consts import languages +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg028" # reference and url for this example +eg028 = Blueprint("eg028", __name__) + +@eg028.route("/eg028", methods=["POST"]) +@authenticate(eg=eg) +def brand_creating(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Render the response + """ + + # 1. Get required arguments + args = Eg028Controller.get_args() + try: + # 2. Call the worker method to create a new brand + response = Eg028Controller.worker(args) + brand_id = response.brands[0].brand_id + app.logger.info(f"Brand has been created. Brand ID: {brand_id}") + + # 3. Render the response + return render_template( + "example_done.html", + title="Brand creating", + h1="Brand creating", + message=f"""The brand has been created and sent!
Brand ID: {brand_id}.""" + ) + + except ApiException as err: + return process_error(err) + +@eg028.route("/eg028", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """Responds with the form for the example""" + + return render_template( + "eg028_brand_creating.html", + title="Brand creating", + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + languages=languages + ) \ No newline at end of file diff --git a/app/examples/eg029_brands_apply_to_envelope/__init__.py b/app/examples/eg029_brands_apply_to_envelope/__init__.py new file mode 100644 index 00000000..0cc93356 --- /dev/null +++ b/app/examples/eg029_brands_apply_to_envelope/__init__.py @@ -0,0 +1 @@ +from .views import eg029 \ No newline at end of file diff --git a/app/examples/eg029_brands_apply_to_envelope/controller.py b/app/examples/eg029_brands_apply_to_envelope/controller.py new file mode 100644 index 00000000..798916d5 --- /dev/null +++ b/app/examples/eg029_brands_apply_to_envelope/controller.py @@ -0,0 +1,108 @@ +import base64 +from os import path +from docusign_esign import AccountsApi, EnvelopesApi, EnvelopeDefinition, Document, Signer, SignHere, Tabs, Recipients +from docusign_esign.client.api_exception import ApiException +from flask import request, session +from ...consts import demo_docs_path, pattern +from ...docusign import create_api_client +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +class Eg029Controller: + @staticmethod + def get_args(): + """Get required session and request arguments""" + # More data validation would be a good idea here + # Strip anything other than the characters listed + signer_email = pattern.sub("", request.form.get("signer_email")) + signer_name = pattern.sub("", request.form.get("signer_name")) + brand_id = request.form.get("brand") + + args = { + "account_id": session["ds_account_id"], # Represents your {ACCOUNT_ID} + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], # Represents your {ACCESS_TOKEN} + "envelope_args": { + "signer_name": signer_name, + "signer_email": signer_email, + "brand_id": brand_id + } + + } + return args + + @classmethod + def worker(cls, args): + """ + 1. Create an API client + 2. Create an envelope definition object + 3. Apply the brand to the envelope using SDK + """ + + # Step 2. Construct your API headers + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + # Step 3. Construct your request body + envelope_api = EnvelopesApi(api_client) + envelope_definition = cls.make_envelope(args["envelope_args"]) + + # Step 4. Call the eSignature REST API + response = envelope_api.create_envelope(account_id=args["account_id"], envelope_definition=envelope_definition) + + return response + + @classmethod + def make_envelope(cls, args): + """ + Creates envelope + """ + + # Open the example file + with open(path.join(demo_docs_path, DS_CONFIG["doc_pdf"]), "rb") as file: + content_bytes = file.read() + base64_file_content = base64.b64encode(content_bytes).decode("ascii") + + document = Document( + document_base64=base64_file_content, + name="lorem", + file_extension="pdf", + document_id=1 + ) + + signer = Signer( + email=args["signer_email"], + name=args["signer_name"], + recipient_id="1", + routing_order="1" + ) + + sign_here = SignHere( + anchor_string="/sn1/", + anchor_units="pixels", + anchor_y_offset="572", + anchor_x_offset="75" + ) + + signer.tabs = Tabs(sign_here_tabs=[sign_here]) + + envelope_definition = EnvelopeDefinition( + email_subject="Please Sign", + documents=[document], + recipients=Recipients(signers=[signer]), + status="sent", + brand_id=args["brand_id"], + ) + + return envelope_definition + + @staticmethod + def get_brands(args): + """Retrieve all brands using the AccountBrands::List""" + + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + try: + account_api = AccountsApi(api_client) + response = account_api.list_brands(account_id=args["account_id"]) + return response.brands + except ApiException as err: + return process_error(err) \ No newline at end of file diff --git a/app/examples/eg029_brands_apply_to_envelope/views.py b/app/examples/eg029_brands_apply_to_envelope/views.py new file mode 100644 index 00000000..7f742c52 --- /dev/null +++ b/app/examples/eg029_brands_apply_to_envelope/views.py @@ -0,0 +1,69 @@ +"""Example 029: Applying a brand to an envelope""" + +from os import path +from docusign_esign.client.api_exception import ApiException +from flask import current_app as app +from flask import render_template, session, Blueprint +from .controller import Eg029Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg029" # Reference and URL for this example +eg029 = Blueprint("eg029", __name__) + +@eg029.route("/eg029", methods=["POST"]) +@authenticate(eg=eg) +def brands_apply_to_envelope(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Render the response + """ + + # 1. Get required arguments + args = Eg029Controller.get_args() + try: + # 2. Call the worker method to apply brand to the envelope + response = Eg029Controller.worker(args) + envelope_id = response.envelope_id + app.logger.info(f"Brand has been applied to envelope. Envelope ID: {envelope_id}") + + # 3. Render the response + return render_template( + "example_done.html", + title="The brand applying to the envelope", + h1="The brand applying to the envelope", + message=f"The brand has been applied to the envelope!
Envelope ID: {envelope_id}." + ) + + except ApiException as err: + return process_error(err) + + +@eg029.route("/eg029", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """Responds with the form for the example""" + + args = { + "account_id": session["ds_account_id"], # Represents your {ACCOUNT_ID} + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], # Represents your {ACCESS_TOKEN} + + } + + # Get all brands to render in the template + brands = Eg029Controller.get_brands(args) + + return render_template( + "eg029_brands_apply_to_envelope.html", + title="Applying a brand to an envelope", + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + brands=brands, + signer_name=DS_CONFIG["signer_name"], + signer_email=DS_CONFIG["signer_email"] + ) \ No newline at end of file diff --git a/app/examples/eg030_brands_apply_to_template/__init__.py b/app/examples/eg030_brands_apply_to_template/__init__.py new file mode 100644 index 00000000..9dd04620 --- /dev/null +++ b/app/examples/eg030_brands_apply_to_template/__init__.py @@ -0,0 +1 @@ +from .views import eg030 \ No newline at end of file diff --git a/app/examples/eg030_brands_apply_to_template/controller.py b/app/examples/eg030_brands_apply_to_template/controller.py new file mode 100644 index 00000000..ec30e63c --- /dev/null +++ b/app/examples/eg030_brands_apply_to_template/controller.py @@ -0,0 +1,113 @@ +from docusign_esign import EnvelopesApi, EnvelopeDefinition, TemplateRole, AccountsApi, TemplatesApi +from docusign_esign.client.api_exception import ApiException +from flask import session, request + +from ...consts import pattern +from ...docusign import create_api_client +from ...error_handlers import process_error + + +class Eg030Controller: + @staticmethod + def get_args(): + """Get required session and request arguments""" + # More data validation would be a good idea here + # Strip anything other than the characters listed + signer_email = pattern.sub("", request.form.get("signer_email")) + signer_name = pattern.sub("", request.form.get("signer_name")) + cc_email = request.form.get("cc_email") + cc_name = request.form.get("cc_name") + brand_id = request.form.get("brand") + template_id = request.form.get("envelope_template") + + if cc_email and cc_name: + cc_email = pattern.sub("", cc_email) + cc_name = pattern.sub("", cc_name) + + args = { + "account_id": session["ds_account_id"], # represent your {ACCOUNT_ID} + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], # represent your {ACCESS_TOKEN} + "envelope_args": { + "signer_name": signer_name, + "signer_email": signer_email, + "cc_name": cc_name, + "cc_email": cc_email, + "brand_id": brand_id, + "template_id": template_id + } + } + + return args + + @classmethod + def worker(cls, args): + """ + 1. Create an api client + 2. Create an envelope definition object + 3. Apply the brand to the envelope using the SDK + """ + + # Step 2. Construct your API headers + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + # Step 3. Construct your request body + envelope_api = EnvelopesApi(api_client) + envelope_definition = cls.make_envelope(args["envelope_args"]) + + # Step 4. Call the eSignature REST API + response = envelope_api.create_envelope(account_id=args["account_id"], envelope_definition=envelope_definition) + + return response + + @classmethod + def make_envelope(cls, args): + """ + Creates the envelope definition object + """ + # Create the envelope definition + envelope_definition = EnvelopeDefinition( + status="sent", + template_id=args["template_id"], + brand_id=args["brand_id"] + ) + + signer = TemplateRole( + email=args["signer_email"], + name=args["signer_name"], + role_name="signer" + ) + + # In case, we have cc we add him to envelope definition + if args["cc_email"] and args["cc_name"]: + cc = TemplateRole( + email=args["cc_email"], + name=args["cc_name"], + role_name="cc" + ) + + envelope_definition.template_roles = [signer, cc] + + else: + envelope_definition.template_roles = [signer] + + return envelope_definition + + @staticmethod + def get_data(args): + """Retrieve brands and envelope templates""" + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + try: + """Retrieve all brands using the AccountBrands::List""" + account_api = AccountsApi(api_client) + brands = account_api.list_brands(account_id=args["account_id"]).brands + + """Retrieve all templates using the Templates::List""" + template_api = TemplatesApi(api_client) + envelope_templates = template_api.list_templates(account_id=args["account_id"]).envelope_templates + + return brands, envelope_templates + + except ApiException as err: + return process_error(err) diff --git a/app/examples/eg030_brands_apply_to_template/views.py b/app/examples/eg030_brands_apply_to_template/views.py new file mode 100644 index 00000000..b73f1adf --- /dev/null +++ b/app/examples/eg030_brands_apply_to_template/views.py @@ -0,0 +1,67 @@ +"""Example 030: Applying a brand and template to an envelope""" + +from os import path +from docusign_esign.client.api_exception import ApiException +from flask import current_app as app +from flask import render_template, session, Blueprint +from .controller import Eg030Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg030" # Reference and URL for this example +eg030 = Blueprint("eg030", __name__) + + +@eg030.route("/eg030", methods=["POST"]) +@authenticate(eg=eg) +def brands_apply_to_template(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Render a response + """ + + # 1. Get required arguments + args = Eg030Controller.get_args() + try: + # 2: Call the worker method to apply the brand to the template + response = Eg030Controller.worker(args) + envelope_id = response.envelope_id + app.logger.info(f"The brand and template have been applied to the envelope. Envelope ID: {envelope_id}") + + # 3: Render the response + return render_template( + "example_done.html", + title="Applying a brand and template to an envelope", + h1="Applying a brand and template to an envelope", + message=f"The brand and template have been applied to the envelope!
Envelope ID: {envelope_id}." + ) + + except ApiException as err: + return process_error(err) + + +@eg030.route("/eg030", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """Responds with the form for the example""" + + args = { + "account_id": session["ds_account_id"], # Represents your {ACCOUNT_ID} + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], # Represents your {ACCESS_TOKEN} + } + brands, templates = Eg030Controller.get_data(args) + return render_template( + "eg030_brands_apply_to_template.html", + title="Applying a brand and template to an envelope", + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + brands=brands, + templates=templates, + signer_name=DS_CONFIG["signer_name"], + signer_email=DS_CONFIG["signer_email"], + ) \ No newline at end of file diff --git a/app/examples/eg031_bulk_send/__init__.py b/app/examples/eg031_bulk_send/__init__.py new file mode 100644 index 00000000..8fb2250a --- /dev/null +++ b/app/examples/eg031_bulk_send/__init__.py @@ -0,0 +1 @@ +from .views import eg031 \ No newline at end of file diff --git a/app/examples/eg031_bulk_send/controller.py b/app/examples/eg031_bulk_send/controller.py new file mode 100644 index 00000000..4026aba9 --- /dev/null +++ b/app/examples/eg031_bulk_send/controller.py @@ -0,0 +1,197 @@ +import base64 +from os import path + +from docusign_esign import EnvelopesApi, Document, Signer, EnvelopeDefinition, Recipients, \ + BulkEnvelopesApi, TextCustomField, CustomFields +from docusign_esign.models import BulkSendingCopy, BulkSendingList, BulkSendingCopyRecipient, BulkSendRequest +from flask import request, session + +from ...consts import demo_docs_path, pattern +from ...docusign import create_api_client +from ...ds_config import DS_CONFIG + +class Eg031Controller: + @staticmethod + def get_args(): + # More data validation would be a good idea here + # Strip anything other than the characters listed + signer_email_1 = pattern.sub("", request.form.get("signer_email_1")) + signer_name_1 = pattern.sub("", request.form.get("signer_name_1")) + cc_email_1 = pattern.sub("", request.form.get("cc_email_1")) + cc_name_1 = pattern.sub("", request.form.get("cc_name_1")) + signer_email_2 = pattern.sub("", request.form.get("signer_email_2")) + signer_name_2 = pattern.sub("", request.form.get("signer_name_2")) + cc_email_2 = pattern.sub("", request.form.get("cc_email_2")) + cc_name_2 = pattern.sub("", request.form.get("cc_name_2")) + + args = { + "account_id": session["ds_account_id"], # Represents your {ACCOUNT_ID} + "base_path": session["ds_base_path"], + "access_token": session["ds_access_token"], # Represents your {ACCESS_TOKEN} + "signers": [{ + "signer_name": signer_name_1, + "signer_email": signer_email_1, + "cc_email": cc_email_1, + "cc_name": cc_name_1 + }, + { + "signer_name": signer_name_2, + "signer_email": signer_email_2, + "cc_email": cc_email_2, + "cc_name": cc_name_2 + } + ] + + } + return args + + @classmethod + def worker(cls, args): + """ + 1. Create an api client and construct API clients + 2. Create and submit a bulk sending list + 3. Create a draft envelope + 4. Add custom fields to the envelope + 5. Add recipients to the envelope + 6. Initiate bulk envelope sending + 7. Confirm sending success + """ + + # Step 2. Construct your API headers + api_client = create_api_client(base_path=args["base_path"], access_token=args["access_token"]) + + # Step 3. Submit a bulk list + bulk_envelopes_api = BulkEnvelopesApi(api_client) + bulk_sending_list = cls.create_bulk_sending_list(args["signers"]) + bulk_list = bulk_envelopes_api.create_bulk_send_list( + account_id=args["account_id"], + bulk_sending_list=bulk_sending_list + ) + bulk_list_id = bulk_list.list_id + + # Step 4. Create an envelope + envelope_api = EnvelopesApi(api_client) + envelope_definition = cls.make_draft_envelope() + envelope = envelope_api.create_envelope(account_id=args["account_id"], envelope_definition=envelope_definition) + envelope_id = envelope.envelope_id + + # Step 5. Attach your bulk list id to the envelope + text_custom_fields = TextCustomField(name="mailingListId", required="false", show="false", value=bulk_list_id) + custom_fields = CustomFields(list_custom_fields=[], text_custom_fields=[text_custom_fields]) + envelope_api.create_custom_fields( + account_id=args["account_id"], + envelope_id=envelope_id, + custom_fields=custom_fields + ) + + # Step 6. Add placeholder recipients + cc = Signer( + name="Multi Bulk Recipient::cc", + email="multiBulkRecipients-cc@docusign.com", + role_name="cc", + note="", + routing_order="1", + status="created", + delivery_method="email", + recipient_id="12", + recipient_type="signer" + ) + + signer = Signer( + name="Multi Bulk Recipient::signer", + email="multiBulkRecipients-signer@docusign.com", + role_name="signer", + note="", + routing_order="1", + status="created", + delivery_method="email", + recipient_id="13", + recipient_type="signer" + ) + + envelope_api.create_recipient( + account_id=args["account_id"], + envelope_id=envelope_id, + recipients=Recipients(signers=[signer, cc]) + ) + + # Step 7. Initiate bulk send + bulk_send_request = BulkSendRequest(envelope_or_template_id=envelope_id) + batch = bulk_envelopes_api.create_bulk_send_request( + account_id=args["account_id"], + bulk_send_list_id=bulk_list_id, + bulk_send_request=bulk_send_request + ) + batch_id = batch.batch_id + + # Step 8. Confirm successful batch send + response = bulk_envelopes_api.get(account_id=args["account_id"], batch_id=batch_id) + + return response + + @classmethod + def create_bulk_sending_list(cls, args): + """ + 1. Create recipient objects with signers + 2. Create recipient objects with ccs + 3. Create bulk copies objects + 4. Create the bulk sending list object + """ + + bulk_copies = [] + for signer in args: + recipient_1 = BulkSendingCopyRecipient( + role_name="signer", + tabs=[], + name=signer["signer_name"], + email=signer["signer_email"] + ) + + recipient_2 = BulkSendingCopyRecipient( + role_name="cc", + tabs=[], + name=signer["cc_name"], + email=signer["cc_email"] + ) + + bulk_copy = BulkSendingCopy( + recipients=[recipient_1, recipient_2], + custom_fields=[] + ) + + bulk_copies.append(bulk_copy) + + bulk_sending_list = BulkSendingList( + name="sample", + bulk_copies=bulk_copies + ) + + return bulk_sending_list + + @classmethod + def make_draft_envelope(cls): + """ + Creates the envelope + """ + + # Open the example file + with open(path.join(demo_docs_path, DS_CONFIG["doc_pdf"]), "rb") as file: + content_bytes = file.read() + base64_file_content = base64.b64encode(content_bytes).decode("ascii") + + document = Document( + document_base64=base64_file_content, + name="lorem", + file_extension="pdf", + document_id=2 + ) + + envelope_definition = EnvelopeDefinition( + email_subject="Please Sign", + documents=[document], + status="created", + envelope_id_stamping="true", + recipients={}, + ) + + return envelope_definition \ No newline at end of file diff --git a/app/examples/eg031_bulk_send/views.py b/app/examples/eg031_bulk_send/views.py new file mode 100644 index 00000000..57f5e2c5 --- /dev/null +++ b/app/examples/eg031_bulk_send/views.py @@ -0,0 +1,58 @@ +"""Example 031: Send an envelope to multiple recipients""" + +from os import path +from docusign_esign.client.api_exception import ApiException +from flask import current_app as app +from flask import render_template, Blueprint +from .controller import Eg031Controller +from ...docusign import authenticate +from ...ds_config import DS_CONFIG +from ...error_handlers import process_error + +eg = "eg031" # reference and url for this example +eg031 = Blueprint("eg031", __name__) + +@eg031.route("/eg031", methods=["POST"]) +@authenticate(eg=eg) +def bulk_send(): + """ + 1. Get required arguments + 2. Call the worker method + 3. Render response + """ + + # 1. Get required arguments + args = Eg031Controller.get_args() + try: + # 2. Call the worker method for bulk sending + response = Eg031Controller.worker(args) + batch_id = response.batch_id + app.logger.info(f"The envelope has been sent to multiple recipients.") + + # 3. Render the response + return render_template("example_done.html", + title="Bulk sending envelopes to multiple recipients", + h1="Bulk sending envelopes to multiple recipients", + message=f"""The envelope has been sent to recipients!
+ Batch id: {batch_id}.""" + ) + + except ApiException as err: + return process_error(err) + + +@eg031.route("/eg031", methods=["GET"]) +@authenticate(eg=eg) +def get_view(): + """Responds with the form for the example""" + + return render_template( + "eg031_bulk_send.html", + title="Bulk sending envelopes to multiple recipients", + source_file=path.basename(path.dirname(__file__)) + "/controller.py", + source_url=DS_CONFIG["github_example_url"] + path.basename(path.dirname(__file__)) + "/controller.py", + documentation=DS_CONFIG["documentation"] + eg, + show_doc=DS_CONFIG["documentation"], + signer_name=DS_CONFIG["signer_name"], + signer_email=DS_CONFIG["signer_email"] + ) \ No newline at end of file diff --git a/app/templates/404.html b/app/templates/404.html index eea3993e..307da311 100644 --- a/app/templates/404.html +++ b/app/templates/404.html @@ -3,5 +3,5 @@ {% block content %}

File Not Found

-

Continue

+

Continue

{% endblock %} diff --git a/app/templates/500.html b/app/templates/500.html index 720d6fbe..7caf7e85 100644 --- a/app/templates/500.html +++ b/app/templates/500.html @@ -3,5 +3,5 @@ {% block content %}

{{ 'An unexpected error has occurred' }}

-

{{ 'Back' }}

+

{{ 'Back' }}

{% endblock %} diff --git a/app/templates/base.html b/app/templates/base.html index 5cd38f73..f67f7ba9 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -28,12 +28,12 @@ {% if session['ds_user_name'] %}
  • - Logout (current)
  • {% else %}
  • - Login (current)
  • {% endif %} diff --git a/app/templates/eg023_idv_authentication.html b/app/templates/eg023_idv_authentication.html index 6a2fb8b5..d7617108 100644 --- a/app/templates/eg023_idv_authentication.html +++ b/app/templates/eg023_idv_authentication.html @@ -23,21 +23,27 @@

    23. Requiring Id Verification for a Recipient

    View source file {{ source_file }} on GitHub.

    -
    -
    - - - We'll never share your email with anyone else. -
    -
    - - -
    - - -
    +{% if workflow_id %} +
    +
    + + + We'll never share your email with anyone else. +
    +
    + + +
    + + +
    +{% else %} + ID verification is available to develop API integration in sandbox (demo) accounts, + but is not enabled by default. To enable IDV in your sandbox and/or production accounts, + contact your DocuSign Account Manager or Partner Account Manager +{% endif %} {% endblock %} diff --git a/app/templates/eg024_permissions_creating.html b/app/templates/eg024_permissions_creating.html new file mode 100644 index 00000000..47b8f6f4 --- /dev/null +++ b/app/templates/eg024_permissions_creating.html @@ -0,0 +1,34 @@ + {% extends "base.html" %} {% block content %} + +

    24. {{ title }}

    +

    + This code example demonstrates how to create a new permission profile. +

    + + {% if show_doc %} +

    Documentation about this example.

    + {% endif %} + +

    + API method used: + Accounts::createPermissionProfile +

    + +

    + View source file {{ source_file }} on GitHub. +

    + +
    +
    + + +
    + +
    + + +
    +
    +{% endblock %} + diff --git a/app/templates/eg025_permissions_set_user_group.html b/app/templates/eg025_permissions_set_user_group.html new file mode 100644 index 00000000..1299cd4e --- /dev/null +++ b/app/templates/eg025_permissions_set_user_group.html @@ -0,0 +1,47 @@ + {% extends "base.html" %} {% block content %} + +

    25. {{ title }}

    +

    + Updates the group name and modifies, or sets, the permission profile for the group +

    + + {% if show_doc %} +

    Documentation about this example.

    + {% endif %} + +

    + API method used: + UserGroups::updateGroups +

    + +

    + View source file {{ source_file }} on GitHub. +

    + +
    +
    + +
    + +
    + +
    + +
    + +
    + +
    + + +
    +
    +{% endblock %} \ No newline at end of file diff --git a/app/templates/eg026_permissions_change_single_setting.html b/app/templates/eg026_permissions_change_single_setting.html new file mode 100644 index 00000000..c49ea7e0 --- /dev/null +++ b/app/templates/eg026_permissions_change_single_setting.html @@ -0,0 +1,38 @@ + {% extends "base.html" %} {% block content %} + +

    26. {{ title }}

    +

    + This code example demonstrates how to change settings in the existing permission profile. +

    + + {% if show_doc %} +

    Documentation about this example.

    + {% endif %} + +

    + API method used: + AccountPermissionProfiles: update +

    + +

    + View source file {{ source_file }} on GitHub. +

    + +
    +
    + +
    + +
    + +
    + + +
    +
    + +{% endblock %} \ No newline at end of file diff --git a/app/templates/eg027_permissions_delete.html b/app/templates/eg027_permissions_delete.html new file mode 100644 index 00000000..445bfd5b --- /dev/null +++ b/app/templates/eg027_permissions_delete.html @@ -0,0 +1,37 @@ + {% extends "base.html" %} {% block content %} + +

    27. {{ title }}

    +

    + This code example demonstrates how to delete a permission profile. +

    + + {% if show_doc %} +

    Documentation about this example.

    + {% endif %} + +

    + API methods used: + Accounts::deletePermissionProfile. +

    + +

    + View source file {{ source_file }} on GitHub. +

    + +
    +
    + +
    + +
    + +
    + + +
    +
    +{% endblock %} \ No newline at end of file diff --git a/app/templates/eg028_brand_creating.html b/app/templates/eg028_brand_creating.html new file mode 100644 index 00000000..4512c3ec --- /dev/null +++ b/app/templates/eg028_brand_creating.html @@ -0,0 +1,46 @@ + {% extends "base.html" %} {% block content %} + +

    28. Create a new brand

    +

    + The brand includes a Brand Name Configure Brands +

    +

    + This code example demonstrates how to create a brand. +

    + +{% if show_doc %} +

    Documentation about this example.

    +{% endif %} + +

    + API method used: + AccountBrands::create. +

    + +

    + View source file {{ source_file }} on GitHub. +

    + +
    +
    + + +
    + +
    + + +
    + +
    + + +
    +
    + +{% endblock %} diff --git a/app/templates/eg029_brands_apply_to_envelope.html b/app/templates/eg029_brands_apply_to_envelope.html new file mode 100644 index 00000000..1ac5baed --- /dev/null +++ b/app/templates/eg029_brands_apply_to_envelope.html @@ -0,0 +1,66 @@ + {% extends "base.html" %} {% block content %} + +

    29. {{ title }}

    +

    + The envelope includes a pdf document. +

    + +

    + This code example demonstrates how to apply a brand to an envelope. +

    + + {% if show_doc %} +

    Documentation about this example.

    + {% endif %} + +

    + Api method used: + Envelopes::Create. +

    + +

    + View source file {{ source_file }} on GitHub. +

    + + {% if brands %} +
    +
    + + + We'll never share your email with anyone else. +
    + +
    + + +
    + +
    + +
    + +
    + +
    + + +
    +
    + {% elif not brands %} +

    Problem: please first create the brand using example 24. +
    + Thank you. +

    + +
    + +
    + {% endif %} +{% endblock %} \ No newline at end of file diff --git a/app/templates/eg030_brands_apply_to_template.html b/app/templates/eg030_brands_apply_to_template.html new file mode 100644 index 00000000..c5900bb0 --- /dev/null +++ b/app/templates/eg030_brands_apply_to_template.html @@ -0,0 +1,98 @@ + {% extends "base.html" %} {% block content %} + +

    30. {{ title }}

    + +

    + This code example demonstrates how to apply a brand to a template. +

    + + {% if show_doc %} +

    Documentation about this example.

    + {% endif %} + +

    + Api method used: + Envelopes::Create +

    + +

    + View source file {{ source_file }} on GitHub. +

    + + {% if brands and templates %} +
    +
    + + + We'll never share your email with anyone else. +
    + + +
    + + +
    + + +
    + + + The email and/or name for the cc recipient must be different from yhe signer. +
    + +
    + + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + + +
    +
    + + {% elif not brands %} +

    Problem: please first create the brand using example 24. +
    + Thank you. +

    + +
    + +
    + + {% elif not templates %} +

    Problem: please first create the template using example 8. +
    + Thank you. +

    + +
    + +
    + {% endif %} +{% endblock %} \ No newline at end of file diff --git a/app/templates/eg031_bulk_send.html b/app/templates/eg031_bulk_send.html new file mode 100644 index 00000000..9daf128c --- /dev/null +++ b/app/templates/eg031_bulk_send.html @@ -0,0 +1,88 @@ + {% extends "base.html" %} {% block content %} + +

    31. {{ title }}

    + +

    + Method BulkEnvelopes::createBulkSendList creates a bulk send list that you can use to send an envelope to up to + 1,000 recipients at once +

    + + {% if show_doc %} +

    Documentation about this example.

    + {% endif %} + +

    + API method used: + Envelopes::createRecipient, + Envelopes::createEnvelope, + BulkEnvelopes::get, + Envelopes::createCustomFields, + BulkEnvelopes::createBulkSendList +

    + +

    + View source file {{ source_file }} on GitHub. +

    + +
    +
    +
    +
    Bulk copy #1
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    +
    + +
    +
    Bulk copy #2
    +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    +
    +
    +
    + + +
    +
    +{% endblock %} \ No newline at end of file diff --git a/app/templates/error.html b/app/templates/error.html index c5462836..7d8a9913 100644 --- a/app/templates/error.html +++ b/app/templates/error.html @@ -1,5 +1,7 @@ + +{% extends "base.html" %} {% set title = "Error" %} - {% extends "base.html" %} {% block content %} +{% block content %}

    Problem: an error occurred

    Error information:

    diff --git a/app/templates/example_done.html b/app/templates/example_done.html index 5a42c94c..cf39235c 100644 --- a/app/templates/example_done.html +++ b/app/templates/example_done.html @@ -9,6 +9,14 @@

    {{ h1 }}

    {% endif %} +{% if changed_settings %} + +{% endif %} +

    Continue

    {% endblock %} diff --git a/app/templates/home.html b/app/templates/home.html index 6db4eabf..edb9d421 100644 --- a/app/templates/home.html +++ b/app/templates/home.html @@ -231,6 +231,87 @@

    23. Send an envelope with ID Verification Au Envelopes::create.

    + +

    Permissions

    +

    24. Creating a permission profile

    +

    + Creates a new permission profile +

    +

    + API method used: + Accounts::createPermissionProfile +

    + +

    25. Setting a permission profile

    +

    + Sets a permission profile. +

    +

    + API method used: + UserGroups::updateGroups. +

    + +

    26. Updating individual permission profile settings

    +

    + Updates individual permissions. +

    +

    + API method used: + AccountPermissionProfiles: update +

    + +

    27. Deleting a permission profile

    +

    + Deletes a permission profile. +

    +

    + API method used: + Accounts::deletePermissionProfile. +

    + + +

    Brands

    +

    28. Create a new Brand

    +

    + Creating a brand +

    +

    + API method used: + AccountBrands::create. +

    + +

    29. Applying a brand to an envelope

    +

    + Applies brand to an envelop +

    +

    + API method used: + Envelopes::Create. +

    + +

    30. Applying a brand to a template

    +

    + Applies brand to a template +

    +

    + API method used: + Envelopes::Create. +

    + + +

    Bulk operations

    +

    31. Bulk sending envelopes to multiple recipients

    +

    + Bulk sending envelopes to multiple recipients. +

    +

    + API method used: + Envelopes::createRecipient, + Envelopes::createEnvelope, + BulkEnvelopes::get, + Envelopes::createCustomFields, + BulkEnvelopes::createBulkSendList +

    diff --git a/app/templates/must_authenticate.html b/app/templates/must_authenticate.html index e38ba853..a6f971b3 100644 --- a/app/templates/must_authenticate.html +++ b/app/templates/must_authenticate.html @@ -4,10 +4,20 @@ {% block content %}

    Please Authenticate with DocuSign

    -

    - Authenticate with DocuSign -

    + +
    +
    + + +

    + + +

    +
    +

    You need to authenticate with DocuSign to continue your request.

    diff --git a/app/views.py b/app/views.py index 70912997..f3f0c499 100644 --- a/app/views.py +++ b/app/views.py @@ -1,290 +1,25 @@ -"""Defines the app's routes. Includes OAuth2 support for DocuSign""" +"""Defines the home page route""" -from flask import render_template, url_for, redirect, session, flash, request -from flask_oauthlib.client import OAuth -from datetime import datetime, timedelta -import requests -import uuid -from app import app, ds_config, eg001_embedded_signing, \ - eg002_signing_via_email, eg003_list_envelopes, \ - eg004_envelope_info, eg005_envelope_recipients, \ - eg006_envelope_docs, eg007_envelope_get_doc, \ - eg008_create_template, eg009_use_template, \ - eg010_send_binary_docs, eg011_embedded_sending, \ - eg012_embedded_console, eg013_add_doc_to_template, \ - eg014_collect_payment, eg015_envelope_tab_data, \ - eg016_set_tab_values, eg017_set_template_tab_values, \ - eg018_envelope_custom_field_data, eg019_access_code_authentication, \ - eg020_sms_authentication, eg021_phone_authentication, \ - eg022_kba_authentication, eg023_idv_authentication +from flask import render_template, url_for, redirect, Blueprint +core = Blueprint("core", __name__) -@app.route("/") + +@core.route("/") def index(): return render_template("home.html", title="Home - Python Code Examples") -@app.route("/index") +@core.route("/index") def r_index(): - return redirect(url_for("index")) - - -@app.route("/ds/must_authenticate") -def ds_must_authenticate(): - return render_template("must_authenticate.html", title="Must authenticate") - - -@app.route("/eg001", methods=["GET", "POST"]) -def eg001(): - return eg001_embedded_signing.controller() - - -@app.route("/eg002", methods=["GET", "POST"]) -def eg002(): - return eg002_signing_via_email.controller() - - -@app.route("/eg003", methods=["GET", "POST"]) -def eg003(): - return eg003_list_envelopes.controller() - - -@app.route("/eg004", methods=["GET", "POST"]) -def eg004(): - return eg004_envelope_info.controller() - - -@app.route("/eg005", methods=["GET", "POST"]) -def eg005(): - return eg005_envelope_recipients.controller() - - -@app.route("/eg006", methods=["GET", "POST"]) -def eg006(): - return eg006_envelope_docs.controller() - - -@app.route("/eg007", methods=["GET", "POST"]) -def eg007(): - return eg007_envelope_get_doc.controller() - - -@app.route("/eg008", methods=["GET", "POST"]) -def eg008(): - return eg008_create_template.controller() - - -@app.route("/eg009", methods=["GET", "POST"]) -def eg009(): - return eg009_use_template.controller() - - -@app.route("/eg010", methods=["GET", "POST"]) -def eg010(): - return eg010_send_binary_docs.controller() - - -@app.route("/eg011", methods=["GET", "POST"]) -def eg011(): - return eg011_embedded_sending.controller() - - -@app.route("/eg012", methods=["GET", "POST"]) -def eg012(): - return eg012_embedded_console.controller() - - -@app.route("/eg013", methods=["GET", "POST"]) -def eg013(): - return eg013_add_doc_to_template.controller() - - -@app.route("/eg014", methods=["GET", "POST"]) -def eg014(): - return eg014_collect_payment.controller() - - -@app.route("/eg015", methods=["GET", "POST"]) -def eg015(): - return eg015_envelope_tab_data.controller() - - -@app.route("/eg016", methods=["GET", "POST"]) -def eg016(): - return eg016_set_tab_values.controller() - + return redirect(url_for("core.index")) -@app.route("/eg017", methods=["GET", "POST"]) -def eg017(): - return eg017_set_template_tab_values.controller() - -@app.route("/eg018", methods=["GET", "POST"]) -def eg018(): - return eg018_envelope_custom_field_data.controller() - - -@app.route("/eg019", methods=["GET", "POST"]) -def eg019(): - return eg019_access_code_authentication.controller() - - -@app.route("/eg020", methods=["GET", "POST"]) -def eg020(): - return eg020_sms_authentication.controller() - - -@app.route("/eg021", methods=["GET", "POST"]) -def eg021(): - return eg021_phone_authentication.controller() - - -@app.route("/eg022", methods=["GET", "POST"]) -def eg022(): - return eg022_kba_authentication.controller() - - -@app.route("/eg023", methods=["GET", "POST"]) -def eg023(): - return eg023_idv_authentication.controller() - - -@app.route("/ds_return") -def ds_return(): - event = request.args.get("event") - state = request.args.get("state") - envelope_id = request.args.get("envelopeId") - return render_template("ds_return.html", - title = "Return from DocuSign", - event = event, - envelope_id = envelope_id, - state = state - ) - - -################################################################################ -# -# OAuth support for DocuSign -# - - -def ds_token_ok(buffer_min=60): - """ - :param buffer_min: buffer time needed in minutes - :return: true iff the user has an access token that will be good for another buffer min - """ - - ok = "ds_access_token" in session and "ds_expiration" in session - ok = ok and (session["ds_expiration"] - timedelta(minutes=buffer_min)) > datetime.utcnow() - return ok - - -base_uri_suffix = "/restapi" -oauth = OAuth(app) -request_token_params = {"scope": "signature", - "state": lambda: uuid.uuid4().hex.upper()} -if not ds_config.DS_CONFIG["allow_silent_authentication"]: - request_token_params["prompt"] = "login" -docusign = oauth.remote_app( - "docusign", - consumer_key=ds_config.DS_CONFIG["ds_client_id"], - consumer_secret=ds_config.DS_CONFIG["ds_client_secret"], - access_token_url=ds_config.DS_CONFIG["authorization_server"] + "/oauth/token", - authorize_url=ds_config.DS_CONFIG["authorization_server"] + "/oauth/auth", - request_token_params=request_token_params, - base_url=None, - request_token_url=None, - access_token_method="POST" -) - - -@app.route("/ds/login") -def ds_login(): - return docusign.authorize(callback=url_for("ds_callback", _external=True)) - - -@app.route("/ds/logout") -def ds_logout(): - ds_logout_internal() - flash("You have logged out from DocuSign.") - return redirect(url_for("index")) - - -def ds_logout_internal(): - # remove the keys and their values from the session - session.pop("ds_access_token", None) - session.pop("ds_refresh_token", None) - session.pop("ds_user_email", None) - session.pop("ds_user_name", None) - session.pop("ds_expiration", None) - session.pop("ds_account_id", None) - session.pop("ds_account_name", None) - session.pop("ds_base_path", None) - session.pop("envelope_id", None) - session.pop("eg", None) - session.pop("envelope_documents", None) - session.pop("template_id", None) - - -@app.route("/ds/callback") -def ds_callback(): - """Called via a redirect from DocuSign authentication service """ - # Save the redirect eg if present - redirect_url = session.pop("eg", None) - # reset the session - ds_logout_internal() - - resp = docusign.authorized_response() - if resp is None or resp.get("access_token") is None: - return "Access denied: reason=%s error=%s resp=%s" % ( - request.args["error"], - request.args["error_description"], - resp - ) - # app.logger.info("Authenticated with DocuSign.") - flash("You have authenticated with DocuSign.") - session["ds_access_token"] = resp["access_token"] - session["ds_refresh_token"] = resp["refresh_token"] - session["ds_expiration"] = datetime.utcnow() + timedelta(seconds=resp["expires_in"]) - - # Determine user, account_id, base_url by calling OAuth::getUserInfo - # See https://developers.docusign.com/esign-rest-api/guides/authentication/user-info-endpoints - url = ds_config.DS_CONFIG["authorization_server"] + "/oauth/userinfo" - auth = {"Authorization": "Bearer " + session["ds_access_token"]} - response = requests.get(url, headers=auth).json() - session["ds_user_name"] = response["name"] - session["ds_user_email"] = response["email"] - accounts = response["accounts"] - account = None # the account we want to use - # Find the account... - target_account_id = ds_config.DS_CONFIG["target_account_id"] - if target_account_id: - account = next( (a for a in accounts if a["account_id"] == target_account_id), None) - if not account: - # Panic! The user does not have the targeted account. They should not log in! - raise Exception("No access to target account") - else: # get the default account - account = next((a for a in accounts if a["is_default"]), None) - if not account: - # Panic! Every user should always have a default account - raise Exception("No default account") - - # Save the account information - session["ds_account_id"] = account["account_id"] - session["ds_account_name"] = account["account_name"] - session["ds_base_path"] = account["base_uri"] + base_uri_suffix - - if not redirect_url: - redirect_url = url_for("index") - return redirect(redirect_url) - -################################################################################ - -@app.errorhandler(404) +@core.app_errorhandler(404) def not_found_error(error): return render_template("404.html"), 404 -@app.errorhandler(500) + +@core.app_errorhandler(500) def internal_error(error): return render_template("500.html"), 500 - diff --git a/requirements.txt b/requirements.txt index 34023612..72aca996 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,35 +1,32 @@ -asn1crypto==0.24.0 -astroid==2.0.4 -certifi==2018.4.16 -cffi==1.11.5 +astroid==2.3.3 +certifi==2019.9.11 +cffi==1.13.2 chardet==3.0.4 Click==7.0 -cryptography==2.7 -docusign-esign==3.0.0 -Flask==1.0.2 +cryptography==2.8 +docusign-esign==3.1.0rc2 +Flask==1.1.1 Flask-OAuthlib==0.9.5 -flask-wtf==0.14.2 -idna==2.7 -isort==4.3.4 -itsdangerous==0.24 -Jinja2>=2.10.1 -jwcrypto==0.5.0 -lazy-object-proxy==1.3.1 -MarkupSafe==1.0 +flask-wtf==0.14.3 +idna==2.8 +isort==4.3.21 +itsdangerous==1.1.0 +Jinja2>=2.10.3 +lazy-object-proxy==1.4.3 +MarkupSafe==1.1.1 mccabe==0.6.1 oauthlib==2.1.0 -pendulum==2.0.3 -pipenv==2018.7.1 +pipenv==2018.11.26 py-oauth2==0.0.10 -pycparser==2.18 -pylint==2.1.1 -python-dateutil==2.7.3 -pytzdata==2018.5 -requests>=2.20.1 -requests-oauthlib==1.0.0 -six==1.11.0 -urllib3>=1.24.2 -virtualenv==16.0.0 -virtualenv-clone==0.3.0 -Werkzeug==0.14.1 -wrapt==1.10.11 +pycparser==2.19 +pylint==2.4.4 +python-dateutil==2.8.1 +requests>=2.22.0 +requests-oauthlib==1.3.0 +six==1.13.0 +urllib3>=1.25.8 +virtualenv==16.7.5 +virtualenv-clone==0.5.3 +Werkzeug==0.16.0 +wrapt==1.11.2 + diff --git a/run.py b/run.py index 8570f966..0dafc05e 100755 --- a/run.py +++ b/run.py @@ -1,15 +1,11 @@ #!flask/bin/python from app import app -import os, sys +import os -if (os.environ.get("DEBUG", False) == "True"): +if os.environ.get("DEBUG", False) == "True": app.config["DEBUG"] = True - port = int(os.environ.get("PORT", 5000)) - print >> sys.stderr, "DEBUG Mode! Port: " + str(port) app.run(host="0.0.0.0", port=port, debug=True) - - #app.run(debug=True) else: app.run()