Example #1
0
    def get_signing_urls(self):
        """ Associate the signing URLs for this agreement with its
        :class:`recipients <pyEchosign.classes.users.User>` """
        endpoint = '{}agreements/{}/signingUrls'.format(
            self.account.api_access_point, self.echosign_id)
        headers = get_headers(self.account.access_token)
        r = requests.get(endpoint, headers=headers)

        if response_success(r):
            data = r.json()
            url_sets = data['signingUrlSetInfos']

            # Each signing set will have its own URLs
            for set in url_sets:
                urls = set['signingUrls']
                for url in urls:
                    try:
                        email = url['email']
                        # Find the user in this Agreement's list of users that has a matching email
                        matching_user = find_user_in_list(
                            self.users, 'email', email)
                    except KeyError:
                        continue
                    # Set the signing URL for that recipient
                    matching_user._signing_url = url['esignUrl']
Example #2
0
    def __init__(self, account, file_name, file, mime_type=None):
        # type: (EchosignAccount, str, Union[IOBase, FileIO, BytesIO], str) -> None
        self.file_name = file_name
        self.file = file
        self.mime_type = mime_type

        self.document_id = None
        self.expiration_date = None

        # With file data provided, make request to Echosign API for transient document
        url = account.api_access_point + 'transientDocuments'

        # Create post_data
        file_tuple = (file_name, file)
        # Only add the mime type if provided
        if mime_type is not None:
            file_tuple = file_tuple + (mime_type, )

        files = dict(File=file_tuple)
        r = requests.post(url,
                          headers=get_headers(account.access_token,
                                              content_type=None),
                          files=files)

        if response_success(r):
            log.debug('Request to create document {} successful.'.format(
                self.file_name))
            response_data = r.json()
            self.document_id = response_data.get('transientDocumentId', None)
            # If there was no document ID, something went wrong
            if self.document_id is None:
                log.error(
                    'Did not receive a transientDocumentId from Echosign. Received: {}'
                    .format(r.content))
                raise ApiError(
                    'Did not receive a Transient Document ID from Echosign')
            else:
                today = arrow.now()
                # Document will expire in 7 days from creation
                self.expiration_date = today.replace(days=+7).datetime
        else:
            try:
                log.error(
                    'Error encountered creating document {}. Received message: {}'
                    .format(self.file_name, r.content))
            finally:
                check_error(r)
Example #3
0
    def cancel(self):
        """ Cancels the agreement on Echosign. Agreement will still be visible in the Manage page. """
        url = '{}agreements/{}/status'.format(self.account.api_access_point,
                                              self.echosign_id)
        body = dict(value='CANCEL')
        r = requests.put(url,
                         headers=get_headers(self.account.access_token),
                         data=json.dumps(body))

        if response_success(r):
            log.debug('Request to cancel agreement {} successful.'.format(
                self.echosign_id))

        else:
            try:
                log.error(
                    'Error encountered cancelling agreement {}. Received message: {}'
                    .format(self.echosign_id, r.content))
            finally:
                check_error(r)
Example #4
0
    def delete(self):
        """ Deletes the agreement on Echosign. Agreement will not be visible in the Manage page. 
        
        Note:
            This action requires the 'agreement_retention' scope, which doesn't appear
            to be actually available via OAuth
        """
        url = self.account.api_access_point + 'agreements/' + self.echosign_id

        r = requests.delete(url,
                            headers=get_headers(self.account.access_token))

        if response_success(r):
            log.debug('Request to delete agreement {} successful.'.format(
                self.echosign_id))
        else:
            try:
                log.error(
                    'Error encountered deleting agreement {}. Received message:{}'
                    .format(self.echosign_id, r.content))
            finally:
                check_error(r)
Example #5
0
    def send(self,
             recipients,
             agreement_name=None,
             ccs=None,
             days_until_signing_deadline=0,
             external_id='',
             signature_flow=SignatureFlow.SEQUENTIAL,
             message='',
             merge_fields=None):
        # type: (List[User], str, list, int, str, Agreement.SignatureFlow, str, List[Dict[str, str]]) -> None
        """ Sends this agreement to Echosign for signature

        Args:
            agreement_name: A string for the document name which will appear in the Echosign Manage page, the email
                to recipients, etc. Defaults to the name for the Agreement.
            recipients: A list of :class:`Users <pyEchosign.classes.users.User>`.
                The order which they are provided in the list determines the order in which they sign.
            ccs: (optional) A list of email addresses to be CC'd on the Echosign agreement emails
                (document sent, document fully signed, etc)
            days_until_signing_deadline: (optional) "The number of days that remain before the document expires.
                You cannot sign the document after it expires" Defaults to 0, for no expiration.
            external_id: (optional) "A unique identifier for your transaction...
                You can use the ExternalID to search for your transaction through [the] API"
            signature_flow: (optional) (SignatureFlow): The routing style of this agreement, defaults to Sequential.
            merge_fields: (optional) A list of dictionaries, with each one providing the 'field_name' and
                'default_value' keys. The field name maps to the field on the document, and the default value is
                what will be placed inside.
            message: (optional) A message which will be displayed to recipients of the agreement

        Returns:
            A namedtuple representing the information received back from the API. Contains the following attributes

            `agreement_id`
                *"The unique identifier that can be used to query status and download signed documents"*

            `embedded_code`
                *"Javascript snippet suitable for an embedded page taking a user to a URL"*

            `expiration`
                *"Expiration date for autologin. This is based on the user setting, API_AUTO_LOGIN_LIFETIME"*

            `url`
             *"Standalone URL to direct end users to"*

        Raises:
            ApiError: If the API returns an error, such as a 403. The exact response from the API is provided.

        """
        if agreement_name is None:
            agreement_name = self.name

        if ccs is None:
            ccs = []

        security_options = dict(passwordProtection="NONE",
                                kbaProtection="NONE",
                                webIdentityProtection="NONE",
                                protectOpen=False,
                                internalPassword="",
                                externalPassword="",
                                openPassword="")

        files_data = [{
            'transientDocumentId': file.document_id
        } for file in self.files]

        if merge_fields is None:
            merge_fields = []

        converted_merge_fields = [
            dict(fieldName=field['field_name'],
                 defaultValue=field['default_value']) for field in merge_fields
        ]

        recipients_data = self.__construct_recipient_agreement_request(
            recipients)

        document_creation_info = dict(
            signatureType="ESIGN",
            name=agreement_name,
            callbackInfo="",
            securityOptions=security_options,
            locale="",
            ccs=ccs,
            externalId=external_id,
            signatureFlow=signature_flow,
            fileInfos=files_data,
            mergeFieldInfo=converted_merge_fields,
            recipientSetInfos=recipients_data,
            message=message,
            daysUntilSigningDeadline=days_until_signing_deadline,
        )

        request_data = dict(documentCreationInfo=document_creation_info)
        url = self.account.api_access_point + 'agreements'
        api_response = requests.post(url,
                                     headers=self.account.headers(),
                                     data=json.dumps(request_data))

        if response_success(api_response):
            response = namedtuple(
                'Response',
                ('agreement_id', 'embedded_code', 'expiration', 'url'))

            response_data = api_response.json()
            embedded_code = response_data.get('embeddedCode', None)
            expiration = response_data.get('expiration', None)
            url = response_data.get('url', None)

            response = response(response_data['agreementId'], embedded_code,
                                expiration, url)

            return response

        else:
            check_error(api_response)