Skip to content

Commit a22fb9c

Browse files
Merge pull request #65 from docusign/feature/connected-fields
Add Connected fields to the assistance request scenario
2 parents 16cea43 + 574c852 commit a22fb9c

File tree

17 files changed

+423
-71
lines changed

17 files changed

+423
-71
lines changed

backend/api/serializers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from rest_framework.serializers import Serializer, CharField, EmailField, URLField
1+
from rest_framework.serializers import Serializer, CharField, EmailField, URLField, BooleanField
22

33
class RequestMedicalRecordsSerializer(Serializer):
44
name=CharField()
@@ -18,6 +18,7 @@ class ApplyForPatientAssistanceSerializer(Serializer):
1818
first_name=CharField()
1919
last_name=CharField()
2020
email=EmailField()
21+
useWithoutExtension=BooleanField()
2122
return_url=URLField(
2223
max_length=200, min_length=None, allow_blank=False
2324
)

backend/api/urls.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
path('request-medical-records', views.request_medical_records),
66
path('covid19-consent-form', views.covid19_consent_form),
77
path('apply-for-patient-assistance', views.apply_for_patient_assistance),
8+
path('extensions', views.get_extension_apps),
89
]

backend/api/views.py

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from docusign.template import Template
66
from docusign.envelope import Envelope
77
from docusign.workflow import get_idv_workflow, is_sms_workflow
8+
from docusign.extensions import Extensions
89

910
from .serializers import (
1011
RequestMedicalRecordsSerializer,
@@ -72,19 +73,51 @@ def apply_for_patient_assistance(request):
7273
"""
7374
The Apply for Patient Assistance flow
7475
"""
75-
serializer = ApplyForPatientAssistanceSerializer(data=request.data)
76-
serializer.is_valid(raise_exception=True)
76+
try:
77+
serializer = ApplyForPatientAssistanceSerializer(data=request.data)
78+
serializer.is_valid(raise_exception=True)
7779

78-
args = serializer.validated_data.copy()
80+
args = serializer.validated_data.copy()
7981

80-
template_request = Template.make_application_for_participation(args)
81-
template_id = Template.create(request.session, template_request)
82+
template_request = Template.make_application_for_participation(args, request.session)
83+
template_id = Template.create(request.session, template_request)
8284

83-
args["template_id"] = template_id
85+
args["template_id"] = template_id
8486

85-
envelope_definition = Envelope.create_application_for_participation_definition(args)
87+
envelope_definition = Envelope.create_application_for_participation_definition(args)
8688

87-
envelope_id = Envelope.send(request.session, envelope_definition)
88-
view_url = Envelope.get_view_url(request.session, envelope_id, args)
89+
envelope_id = Envelope.send(request.session, envelope_definition)
90+
view_url = Envelope.get_view_url(request.session, envelope_id, args)
8991

90-
return Response({"view_url": view_url, "client_id": os.environ.get('CLIENT_ID')})
92+
return Response({"view_url": view_url, "client_id": os.environ.get('CLIENT_ID')})
93+
except Exception as err:
94+
print(err)
95+
96+
@api_view(['GET'])
97+
@error_processing
98+
def get_extension_apps(request):
99+
"""
100+
Retrieve the list of extensions
101+
"""
102+
try:
103+
extensions = Extensions.get_extension_apps(request.session)
104+
actual_extension_app_ids = [item["appId"] for item in extensions]
105+
106+
required_ids = {
107+
Extensions.get_address_extension_id(),
108+
Extensions.get_phone_extension_id(),
109+
Extensions.get_ssn_extension_id(),
110+
}
111+
112+
# Check missing apps one by one
113+
missing_app_ids = [app_id for app_id in required_ids if app_id not in actual_extension_app_ids]
114+
for app_id in missing_app_ids:
115+
result = Extensions.get_extension_app(request.session, app_id)
116+
if result and len(result) > 0 and not "code" in result:
117+
actual_extension_app_ids.append(result[0]["appId"])
118+
119+
has_all_app_ids = all(app_id in actual_extension_app_ids for app_id in required_ids)
120+
121+
return Response({"areExtensionsPresent": has_all_app_ids})
122+
except Exception as err:
123+
print(err)

backend/auth/views.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,11 @@ def jwt_auth(request):
7070
"""
7171
Endpoint of user authorization using JWT
7272
"""
73-
auth_data = DsClient.jwt_auth()
73+
try:
74+
auth_data = DsClient.jwt_auth()
7475

75-
SessionData.set_auth_data(request.session, auth_data)
76+
SessionData.set_auth_data(request.session, auth_data)
7677

77-
return Response({"message": "Logged in with JWT"})
78+
return Response({"message": "Logged in with JWT"})
79+
except Exception as err:
80+
print(err.body)

backend/docusign/ds_config.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@
66
TOKEN_EXPIRATION_IN_SECONDS = 3600
77
TOKEN_REPLACEMENT_IN_SECONDS = 10 * 60
88

9-
CODE_GRANT_SCOPES = ['signature', 'click.manage']
10-
PERMISSION_SCOPES = ['signature', 'impersonation', 'click.manage']
9+
CODE_GRANT_SCOPES = ['signature', 'click.manage', 'adm_store_unified_repo_read']
10+
PERMISSION_SCOPES = ['signature', 'impersonation', 'click.manage', 'adm_store_unified_repo_read']
11+
12+
CONNECTED_FIELDS_BASE_HOST = 'https://api-d.docusign.com'
13+
PHONE_EXTENSION_ID = "d16f398f-8b9a-4f94-b37c-af6f9c910c04"
14+
SMARTY_EXTENSION_ID = "04bfc1ae-1ba0-42d0-8c02-264417a7b234"
15+
SSN_EXTENSION_ID = "b1adf7ad-38fd-44ba-85f5-38ea87f4af23"
1116

1217
DS_RETURN_URL = os.environ.get('REACT_APP_DS_RETURN_URL')
1318
DS_AUTH_SERVER = os.environ.get('DS_AUTH_SERVER')

backend/docusign/extensions.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import requests
2+
from docusign_esign import ExtensionData, ConnectionInstance
3+
from docusign.ds_config import SMARTY_EXTENSION_ID, PHONE_EXTENSION_ID, SSN_EXTENSION_ID, CONNECTED_FIELDS_BASE_HOST
4+
5+
class Extensions:
6+
@staticmethod
7+
def get_extension_apps(session):
8+
headers = {
9+
"Authorization": "Bearer " + session['access_token'],
10+
"Accept": "application/json",
11+
"Content-Type": "application/json"
12+
}
13+
14+
url = f"{CONNECTED_FIELDS_BASE_HOST}/v1/accounts/{session['account_id']}/connected-fields/tab-groups"
15+
response = requests.get(url, headers=headers)
16+
17+
return response.json()
18+
19+
def get_extension_app(session, appId):
20+
headers = {
21+
"Authorization": "Bearer " + session['access_token'],
22+
"Accept": "application/json",
23+
"Content-Type": "application/json"
24+
}
25+
26+
url = f"{CONNECTED_FIELDS_BASE_HOST}/v1/accounts/{session['account_id']}/connected-fields/tab-groups?appId={appId}"
27+
response = requests.get(url, headers=headers)
28+
29+
return response.json()
30+
31+
@staticmethod
32+
def get_address_extension_id():
33+
return SMARTY_EXTENSION_ID
34+
35+
@staticmethod
36+
def get_phone_extension_id():
37+
return PHONE_EXTENSION_ID
38+
39+
@staticmethod
40+
def get_ssn_extension_id():
41+
return SSN_EXTENSION_ID
42+
43+
@staticmethod
44+
def get_extension_by_app_id(objects, app_id):
45+
return next((obj for obj in objects if obj["appId"] == app_id), None)
46+
47+
@staticmethod
48+
def extract_verification_data(selected_app_id, tab):
49+
extension_data = tab["extensionData"]
50+
51+
return {
52+
"app_id": selected_app_id,
53+
"extension_group_id": extension_data["extensionGroupId"] if "extensionGroupId" in extension_data else "",
54+
"publisher_name": extension_data["publisherName"] if "publisherName" in extension_data else "",
55+
"application_name": extension_data["applicationName"] if "applicationName" in extension_data else "",
56+
"action_name": extension_data["actionName"] if "actionName" in extension_data else "",
57+
"action_input_key": extension_data["actionInputKey"] if "actionInputKey" in extension_data else "",
58+
"action_contract": extension_data["actionContract"] if "actionContract" in extension_data else "",
59+
"extension_name": extension_data["extensionName"] if "extensionName" in extension_data else "",
60+
"extension_contract": extension_data["extensionContract"] if "extensionContract" in extension_data else "",
61+
"required_for_extension": extension_data["requiredForExtension"] if "requiredForExtension" in extension_data else "",
62+
"tab_label": tab["tabLabel"],
63+
"connection_key": (
64+
extension_data["connectionInstances"][0]["connectionKey"]
65+
if "connectionInstances" in extension_data and extension_data["connectionInstances"]
66+
else ""
67+
),
68+
"connection_value": (
69+
extension_data["connectionInstances"][0]["connectionValue"]
70+
if "connectionInstances" in extension_data and extension_data["connectionInstances"]
71+
else ""
72+
)
73+
}
74+
75+
@staticmethod
76+
def get_extension_data(verification_data):
77+
return ExtensionData(
78+
extension_group_id = verification_data["extension_group_id"],
79+
publisher_name = verification_data["publisher_name"],
80+
application_id = verification_data["app_id"],
81+
application_name = verification_data["application_name"],
82+
action_name = verification_data["action_name"],
83+
action_contract = verification_data["action_contract"],
84+
extension_name = verification_data["extension_name"],
85+
extension_contract = verification_data["extension_contract"],
86+
required_for_extension = verification_data["required_for_extension"],
87+
action_input_key = verification_data["action_input_key"],
88+
extension_policy = 'MustVerifyToSign',
89+
connection_instances = [ConnectionInstance(
90+
connection_key = verification_data["connection_key"],
91+
connection_value = verification_data["connection_value"],
92+
)]
93+
)
-8.9 KB
Binary file not shown.

backend/docusign/template.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,12 @@ def make_covid_19_consent_form(cls, args):
7979
return cls.make_request(template_name, document, signer)
8080

8181
@classmethod
82-
def make_application_for_participation(cls, args):
82+
def make_application_for_participation(cls, args, session):
8383
"""
8484
Make template_request for application_for_participation endpoint
8585
"""
8686
template_name = "ApplicationForParticipationTemplate"
8787
document = create_document("Application_for_participation_v2.pdf")
88-
signer = make_application_for_participation_signer(args)
88+
signer = make_application_for_participation_signer(args, session)
8989

9090
return cls.make_request(template_name, document, signer)

0 commit comments

Comments
 (0)