Commit 3a90cc19 authored by Volkov Gherman's avatar Volkov Gherman

Finish dousign_app sending vie email

parent 2e162c01
...@@ -3,4 +3,9 @@ DATABASE_NAME='your database name' ...@@ -3,4 +3,9 @@ DATABASE_NAME='your database name'
DATABASE_USER='your database owner' DATABASE_USER='your database owner'
DATABASE_PASSWORD='your database password' DATABASE_PASSWORD='your database password'
DATABASE_HOST='your database host' DATABASE_HOST='your database host'
DATABASE_PORT='your database port' DATABASE_PORT='your database port'
\ No newline at end of file
DOCUSIGN_INTEGRATION_KEY='your integation key'
EMAIL_HOST_USER='host user mail'
EMAIL_HOST_PASSWORD='host user password'
\ No newline at end of file
import base64 import base64
from os import path from os import path
from docusign_esign import EnvelopesApi, EnvelopeDefinition, Document, Signer, CarbonCopy, SignHere, Tabs, Recipients from docusign_esign import EnvelopesApi, EnvelopeDefinition, Document, Signer, CarbonCopy, SignHere, \
Tabs, Recipients
from ...consts import demo_docs_path, pattern from ...consts import demo_docs_path, pattern
from ...jwt_helpers import create_api_client from ...jwt_helpers import create_api_client
......
from core.settings import DOCUSIGN_PRIVATE_KEY_PATH, DOCUSIGN_DOCUMENT_PATH_DOCX, DOCUSIGN_DOCUMENT_PATH_PDF
DS_JWT = { DS_JWT = {
"ds_client_id": "a2ecda0a-ebda-48f6-b784-a75a51608e07", "ds_client_id": "a2ecda0a-ebda-48f6-b784-a75a51608e07",
"ds_impersonated_user_id": "6c83e6cd-4cac-4aeb-a20e-22ab78b1f020", # The id of the user. "ds_impersonated_user_id": "6c83e6cd-4cac-4aeb-a20e-22ab78b1f020", # The id of the user.
"private_key_file": "./app/private.key", # Create a new file in your repo source folder named private.key then copy and paste your RSA private key there and save it. "private_key_file": DOCUSIGN_PRIVATE_KEY_PATH, # Create a new file in your repo source folder named private.key then copy and paste your RSA private key there and save it.
"authorization_server": "account-d.docusign.com", "authorization_server": "account-d.docusign.com",
"doc_docx": "World_Wide_Corp_Battle_Plan_Trafalgar.docx", "doc_docx": DOCUSIGN_DOCUMENT_PATH_DOCX,
"doc_pdf": "World_Wide_Corp_lorem.pdf" "doc_pdf": DOCUSIGN_DOCUMENT_PATH_PDF,
} }
\ No newline at end of file
from .jwt_helper import create_api_client, get_jwt_token, get_private_key from .jwt_helper import create_api_client, get_jwt_token, get_private_key
\ No newline at end of file
from docusign_esign import ApiClient from docusign_esign import ApiClient
from os import path from os import path
def get_jwt_token(private_key, scopes, auth_server, client_id, impersonated_user_id): def get_jwt_token(private_key, scopes, auth_server, client_id, impersonated_user_id):
"""Get the jwt token""" """Get the jwt token"""
api_client = ApiClient() api_client = ApiClient()
...@@ -15,6 +16,7 @@ def get_jwt_token(private_key, scopes, auth_server, client_id, impersonated_user ...@@ -15,6 +16,7 @@ def get_jwt_token(private_key, scopes, auth_server, client_id, impersonated_user
) )
return response return response
def get_private_key(private_key_path): def get_private_key(private_key_path):
""" """
Check that the private key present in the file and if it is, get it from the file. Check that the private key present in the file and if it is, get it from the file.
...@@ -30,10 +32,11 @@ def get_private_key(private_key_path): ...@@ -30,10 +32,11 @@ def get_private_key(private_key_path):
return private_key return private_key
def create_api_client(base_path, access_token): def create_api_client(base_path, access_token):
"""Create api client and construct API headers""" """Create api client and construct API headers"""
api_client = ApiClient() api_client = ApiClient()
api_client.host = base_path api_client.host = base_path
api_client.set_default_header(header_name="Authorization", header_value=f"Bearer {access_token}") api_client.set_default_header(header_name="Authorization", header_value=f"Bearer {access_token}")
return api_client return api_client
\ No newline at end of file
from os import path
import sys import sys
import subprocess import subprocess
from docusign_esign import ApiClient from docusign_esign import ApiClient
from docusign_esign.client.api_exception import ApiException from docusign_esign.client.api_exception import ApiException
from app.jwt_helpers import get_jwt_token, get_private_key
from app.eSignature.examples.eg002_signing_via_email import Eg002SigningViaEmailController from .docusign_app.jwt_helpers import get_jwt_token, get_private_key
from app.jwt_config import DS_JWT from .docusign_app.eSignature.examples.eg002_signing_via_email import Eg002SigningViaEmailController
from .docusign_app.jwt_config import DS_JWT
# pip install DocuSign SDK # pip install DocuSign SDK
subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'docusign_esign']) subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'docusign_esign'])
...@@ -94,6 +94,3 @@ def main(): ...@@ -94,6 +94,3 @@ def main():
run_example(private_key, api_client) run_example(private_key, api_client)
else: else:
sys.exit("Please grant consent") sys.exit("Please grant consent")
main()
import sys
from docusign_esign import ApiClient, ApiException
from api.jwt_console import get_consent_url, get_token
from api.docusign_app.jwt_helpers import get_private_key
from api.models import DocumentNDA
from api.docusign_app.jwt_config import DS_JWT
from api.docusign_app.eSignature.examples.eg002_signing_via_email import Eg002SigningViaEmailController
def get_args(api_account_id, access_token, base_path):
document = DocumentNDA.objects.last()
signer_email = document.owner_email
signer_name = document.owner_name
cc_email = 'example@mail.com'
cc_name = 'name'
envelope_args = {
"signer_email": signer_email,
"signer_name": signer_name,
"cc_email": cc_email,
"cc_name": cc_name,
"status": "sent",
}
args = {
"account_id": api_account_id,
"base_path": base_path,
"access_token": access_token,
"envelope_args": envelope_args
}
return args
def run_docusign(private_key, api_client):
jwt_values = get_token(private_key, api_client)
args = get_args(jwt_values["api_account_id"], jwt_values["access_token"], jwt_values["base_path"])
envelope_id = Eg002SigningViaEmailController.worker(args, DS_JWT["doc_docx"], DS_JWT["doc_pdf"])
print("Your envelope has been sent.")
print(envelope_id)
def send_docusign_email():
api_client = ApiClient()
api_client.set_base_path(DS_JWT["authorization_server"])
api_client.set_oauth_host_name(DS_JWT["authorization_server"])
private_key = get_private_key(DS_JWT["private_key_file"]).encode("ascii").decode("utf-8")
try:
run_docusign(private_key, api_client)
except ApiException as err:
body = err.body.decode('utf8')
if "consent_required" in body:
consent_url = get_consent_url()
print("Open the following URL in your browser to grant consent to the application:")
print(consent_url)
consent_granted = input("Consent granted? Select one of the following: \n 1)Yes \n 2)No \n")
if consent_granted == "1":
run_docusign(private_key, api_client)
else:
sys.exit("Please grant consent")
return {'status': 'success'}
...@@ -10,28 +10,37 @@ ...@@ -10,28 +10,37 @@
crossorigin="anonymous"></script> crossorigin="anonymous"></script>
</head> </head>
<body> <body>
<form id='form' method="POST"> <h1 style="text-align: center; margin-top: 10vh" class="display-6">DocuSign Esign <br>
<div class="form-group"> <small class="text-muted">Подпишите документ с помощью docusign</small>
<label for="formGroupExampleInput">Введите свое имя</label> </h1>
<input name="owner_name" type="text" class="form-control" id="formGroupExampleInput" placeholder="Example input" required> <main style="background: lightgray; width: 70vh; margin-inline: auto">
</div> <form style="width: 70vh; margin: 10vh auto; padding: 20px 30px;" id='form' method="POST">
<div class="form-group"> <div class="form-group">
<label for="formGroupExampleInput2">Введите свою фамилию</label> <label for="formGroupExampleInput">Введите свое имя</label>
<input name="owner_surname" type="text" class="form-control" id="formGroupExampleInput2" placeholder="Another input" required> <input name="owner_name" type="text" class="form-control" id="formGroupExampleInput" placeholder="Вася"
</div> required>
<div class="form-group"> </div>
<label for="exampleInputEmail1">Введите свой Email</label> <div class="form-group">
<input name="owner_email" type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" <label for="formGroupExampleInput2">Введите свою фамилию</label>
placeholder="Enter email" required> <input name="owner_surname" type="text" class="form-control" id="formGroupExampleInput2"
</div> placeholder="Пупкин" required>
<button type="submit" class="btn btn-primary">Подтвердить и подписать документ</button> </div>
</form> <div class="form-group">
<label for="exampleInputEmail1">Введите свой Email</label>
<input name="owner_email" type="email" class="form-control" id="exampleInputEmail1"
aria-describedby="emailHelp"
placeholder="vasyaPupkin@.mail.com" required>
</div>
<button type="submit" class="btn btn-primary">Подтвердить и подписать документ
</button>
</form>
</main>
</body> </body>
<script> <script>
const sendForm = (e) => { const sendForm = (e) => {
e.preventDefault(); e.preventDefault();
fetch('http://localhost:8000', { fetch('http://localhost:8000/api/v1/', {
method: 'POST', method: 'POST',
body: new FormData(form) body: new FormData(form)
}).then(form.reset()) }).then(form.reset())
......
from datetime import datetime, timedelta from http.client import HTTPException
import jwt from django.shortcuts import redirect
from django.core.mail import send_mail
from docusign_esign import ApiClient, EnvelopesApi
from rest_framework.renderers import TemplateHTMLRenderer from rest_framework.renderers import TemplateHTMLRenderer
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from api.serializers import DocumentNDASerializer from api.serializers import DocumentNDASerializer
from core.settings import DOCUSIGN_PRIVATE_KEY_PATH, DOCUSIGN_INTEGRATION_KEY, DOCUSIGN_AUTH_SERVER, DOCUSIGN_API_SERVER from api.services import send_docusign_email
class IndexView(APIView): def index_view(request):
return redirect('send_email')
class SendDocumentView(APIView):
renderer_classes = [TemplateHTMLRenderer] renderer_classes = [TemplateHTMLRenderer]
template_name = 'index.html' template_name = 'index.html'
def get(self, request): def get(self, request):
return Response(template_name=self.template_name) return Response(template_name=self.template_name, status=200)
def post(self, request): def post(self, request):
serializer = DocumentNDASerializer(data=request.data) serializer = DocumentNDASerializer(data=request.data)
if serializer.is_valid(): if serializer.is_valid():
document = serializer.save() serializer.save()
send_docusign_email(request, document) try:
return Response(status=200, data=serializer.data) send_docusign_email()
return Response(status=200, data=serializer.data)
except HTTPException as e:
return Response({'error': e}, status=503)
return Response({'error': 'Bad Request'}, status=400) return Response({'error': 'Bad Request'}, status=400)
def gen_jwt_token(email):
with open(DOCUSIGN_PRIVATE_KEY_PATH, 'rb') as rsa_key:
private_key = rsa_key.read()
integration_key = DOCUSIGN_INTEGRATION_KEY
user_email = email
cur_time = datetime.utcnow()
exp_time = cur_time + timedelta(hours=1)
payload = {
'iss': integration_key,
'sub': user_email,
'aud': 'account-d.docusign.com',
'iat': cur_time,
'exp': exp_time,
'scope': 'signature impersonation'
}
return jwt.encode(payload, private_key, algorithm='RS256')
def send_docusign_email(request, user_data):
user = user_data
api_client = ApiClient()
api_client.host = DOCUSIGN_AUTH_SERVER
jwt_token = gen_jwt_token(user.owner_email)
api_client.set_default_header('Authorization', 'Bearer ' + jwt_token)
base_url = DOCUSIGN_API_SERVER
envelope_api = EnvelopesApi(api_client)
envelope_definition = {
'email_subject': 'Подписать документ',
'email_blurb': 'Пожалуйста, подпишите документ.',
'status': 'sent',
'recipients': {
'signers': [{
'email': user.owner_email,
'name': user.owner_name,
'recipientId': '1',
'clientUserId': user.pk,
'tabs': {
'textTabs': [{
'tabLabel': 'Name',
'value': user.owner_name
}]
}
}]
},
'documents': [{
'documentId': '1',
'name': 'document.docx',
'fileExtension': 'docx',
'documentBase64': 'BASE64_ENCODED_DOCUMENT'
}]
}
envelope_summary = envelope_api.create_envelope('1', envelope_definition=envelope_definition)
sign_url = f'{base_url}/envelopes/{envelope_summary.envelope_id}/views/recipient'
send_mail(
'Подписать документ',
f'Пожалуйста, подпишите документ по ссылке: {sign_url}',
'отправитель@example.com',
[user.email],
fail_silently=False,
)
return {'status': 'success'}
...@@ -15,6 +15,7 @@ from pathlib import Path ...@@ -15,6 +15,7 @@ from pathlib import Path
from dotenv import load_dotenv from dotenv import load_dotenv
load_dotenv() load_dotenv()
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'.
...@@ -116,8 +117,8 @@ AUTH_PASSWORD_VALIDATORS = [ ...@@ -116,8 +117,8 @@ AUTH_PASSWORD_VALIDATORS = [
CORS_ORIGIN_ALLOW_ALL = False CORS_ORIGIN_ALLOW_ALL = False
CORS_ORIGIN_WHITELIST = [ CORS_ORIGIN_WHITELIST = [
'http://loaclhost:8000', 'http://loaclhost:8000/api/v1/',
'http://127.0.0.1:8000' 'http://127.0.0.1:8000/api/v1/'
] ]
# Internationalization # Internationalization
...@@ -134,10 +135,10 @@ USE_TZ = True ...@@ -134,10 +135,10 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/ # https://docs.djangoproject.com/en/4.2/howto/static-files/
STATIC_URL = 'static/' STATIC_URL = '/static/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'demo') MEDIA_ROOT = os.path.join(BASE_DIR, 'api/docusign_app/static')
MEDIA_URL = '/demo/' MEDIA_URL = '/api/docusign_app/static/'
# Default primary key field type # Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
...@@ -145,13 +146,17 @@ MEDIA_URL = '/demo/' ...@@ -145,13 +146,17 @@ MEDIA_URL = '/demo/'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
DOCUSIGN_INTEGRATION_KEY = os.getenv('DOCUSIGN_INTEGRATION_KEY') DOCUSIGN_INTEGRATION_KEY = os.getenv('DOCUSIGN_INTEGRATION_KEY')
DOCUSIGN_PRIVATE_KEY_PATH = os.path.join(BASE_DIR, 'docusign_test_app-python/app/private.key') DOCUSIGN_PRIVATE_KEY_PATH = os.path.join(BASE_DIR, 'api/docusign_app/private.key')
DOCUSIGN_DOCUMENT_PATH_DOCX = os.path.join(BASE_DIR, 'api/docusign_app/static/demo_documents'
'/World_Wide_Corp_Battle_Plan_Trafalgar.docx')
DOCUSIGN_DOCUMENT_PATH_PDF = os.path.join(BASE_DIR, 'api/docusign_app/static/demo_documents/World_Wide_Corp_lorem.pdf')
DOCUSIGN_AUTH_SERVER = 'https://account-d.docusign.com' DOCUSIGN_AUTH_SERVER = 'https://account-d.docusign.com'
DOCUSIGN_API_SERVER = 'https://api-d.docusign.com' DOCUSIGN_API_SERVER = 'https://api-d.docusign.com'
EMAIL_USE_TLS = True EMAIL_USE_TLS = True
EMAIL_HOST = None EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = None EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = None EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD')
EMAIL_PORT = 587 EMAIL_PORT = 587
APPEND_SLASH = False
...@@ -17,10 +17,11 @@ Including another URLconf ...@@ -17,10 +17,11 @@ Including another URLconf
from django.contrib import admin from django.contrib import admin
from django.urls import path from django.urls import path
from api.views import IndexView from api.views import index_view, SendDocumentView
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('', IndexView.as_view()), path('', index_view, name='index'),
path('api/v1/', SendDocumentView.as_view(), name='send_email'),
] ]
...@@ -3,4 +3,3 @@ djangorestframework==3.14.* ...@@ -3,4 +3,3 @@ djangorestframework==3.14.*
psycopg2-binary==2.9.* psycopg2-binary==2.9.*
python-dotenv==1.0.* python-dotenv==1.0.*
docusign-esign==3.22.* docusign-esign==3.22.*
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment