W, [2020-09-20T20:36:26.688517 #3598] WARN -- : [6f542522-1ce0-49af-853c-c0b0a27bd8d9] DocusignConnector: Starting up: fetching token
W, [2020-09-21T00:19:24.588534 #3599] WARN -- : [057b014e-5142-4944-a191-cc971fcc3942] DocusignConnector: Token is about to expire: fetching token
E, [2020-09-21T00:19:26.258051 #3599] ERROR -- : [057b014e-5142-4944-a191-cc971fcc3942] DocusignConnector: Auth failed, refreshing token
E, [2020-09-21T00:19:27.096813 #3599] ERROR -- : [057b014e-5142-4944-a191-cc971fcc3942] DocusignConnector: Auth failed, refreshing token
E, [2020-09-21T00:19:28.024347 #3599] ERROR -- : [057b014e-5142-4944-a191-cc971fcc3942] DocusignConnector: Auth failed, refreshing token
E, [2020-09-21T00:19:28.910622 #3599] ERROR -- : [057b014e-5142-4944-a191-cc971fcc3942] DocusignConnector: Auth failed, refreshing token
E, [2020-09-21T00:19:29.768104 #3599] ERROR -- : [057b014e-5142-4944-a191-cc971fcc3942] DocusignConnector: Auth failed, refreshing token
E, [2020-09-21T00:19:30.619396 #3599] ERROR -- : [057b014e-5142-4944-a191-cc971fcc3942] DocusignConnector: Auth failed, refreshing token
E, [2020-09-21T00:19:31.527911 #3599] ERROR -- : [057b014e-5142-4944-a191-cc971fcc3942] DocusignConnector: Auth failed, refreshing token
E, [2020-09-21T00:19:32.369901 #3599] ERROR -- : [057b014e-5142-4944-a191-cc971fcc3942] DocusignConnector: Auth failed, refreshing token
E, [2020-09-21T00:19:33.297989 #3599] ERROR -- : [057b014e-5142-4944-a191-cc971fcc3942] DocusignConnector: Auth failed, refreshing token
E, [2020-09-21T00:19:34.182043 #3599] ERROR -- : [057b014e-5142-4944-a191-cc971fcc3942] DocusignConnector: Auth failed, refreshing token
E, [2020-09-21T00:19:34.627809 #3599] ERROR -- : [057b014e-5142-4944-a191-cc971fcc3942] Docusign API returned an error: {"headers":{"Cache-Control":"no-cache","Content-Length":"125","Content-Type":"application/json; charset=utf-8","X-DocuSign-TraceToken":"ea3a9427-5cc6-43e3-9d03-e58d3576067c","X-DocuSign-Node":"DA3DFE181","Date":"Mon, 21 Sep 2020 00:19:34 GMT"},"body":{"errorCode":"USER_AUTHENTICATION_FAILED","message":"One or both of Username and Password are invalid. Invalid access token"}}.
F, [2020-09-21T00:19:34.961063 #3599] FATAL -- : [057b014e-5142-4944-a191-cc971fcc3942]
###
#
# Manages the DocuSign API integration
# This is mostly adapted from https://github.com/docusign/eg-01-ruby-jwt/
#
###
require "docusign_esign"
module DocuSignConnector
class DocuSignConnectorError < PortalError
class NoTemplateError < DocuSignConnectorError; end
end
class DocuSignConnection
@@TOKEN_REPLACEMENT_IN_SECONDS = 10 * 60 # 10 minutes
@@TOKEN_EXPIRATION_IN_SECONDS = 60 * 60 # 1 hour
@@account = nil
@@account_id = nil
@@token = nil
@@expireIn = 0
@@private_key = nil
@@api_client = nil
@@envelopes_api = nil
@@templates_api = nil
# Set up the API client for later use
def initialize
configuration = DocuSign_eSign::Configuration.new
# configuration.debugging = true
@@api_client = DocuSign_eSign::ApiClient.new(configuration)
@@api_client.set_oauth_base_path(Rails.application.credentials.docusign[:oauth_base_path])
check_token
end
# Apparently the token expiration is not reported back through the SDK so
# we use the conservative defaults in the class to fake-check expiration.
def check_token
@now = Time.now.to_f # seconds since epoch
# Check that the token should be good
if @@token == nil || ((@now + @@TOKEN_REPLACEMENT_IN_SECONDS) > @@expireIn)
if @@token == nil
Rails.logger.warn "DocusignConnector: Starting up: fetching token"
else
Rails.logger.warn "DocusignConnector: Token is about to expire: fetching token"
end
self.update_token
end
end
# .
# .
# attr accessors for account_id, envelopes_api and templates_api cut for clarity
# .
# .
# .
# .
###
def update_token
rsa_pk = method_that_retrieves_the_private_key_as_text()
token = @@api_client.request_jwt_user_token(
Rails.application.credentials.docusign[:client_id],
Rails.application.credentials.docusign[:impersonated_user_guid],
rsa_pk)
@@account = get_account_info(token.access_token)
# puts @@account.to_yaml
@@api_client.config.host = @@account.base_uri
@@account_id = @@account.account_id
@@token = token.access_token
@@expireIn = Time.now.to_f + @@TOKEN_EXPIRATION_IN_SECONDS # would be better to receive the expires
# info from DocuSign but it is not yet returned by the SDK.
Rails.logger.info "DocusignConnector: Received token"
end
# Gets the account info for the JWT token in use.
def get_account_info(access_token)
# code here
response = @@api_client.get_user_info(access_token)
accounts = response.accounts
accounts.each do |acct|
if acct.is_default
return acct
end
end
end
end
# Method that creates the Envelope for the Offer
def self.create_offer_envelope(offer)
##
#
# removed code where 'template' is retrieved from the DB.
#
ds_template = template.docusign_template_id
# Get DocuSign instance
ds = DocuSignConnection.new
# Create the signers
signers_list = self.create_signers(offer)
# Recipients object:
recipients = DocuSign_eSign::Recipients.new(
signers: signers_list
)
# Create a composite template for the Server template + roles
template = DocuSign_eSign::CompositeTemplate.new(
compositeTemplate_id: offer,
serverTemplates: [DocuSign_eSign::ServerTemplate.new(
sequence: "1",
templateId: ds_template)
],
# Add the roles via an inlineTemplate
inlineTemplates: [
DocuSign_eSign::InlineTemplate.new(
sequence: "1",
recipients: recipients)
]
)
envelope_def = DocuSign_eSign::EnvelopeDefinition.new({
status: "sent",
emailSubject: "Subscription Documents for #{offer.deal.name}", # TODO: Define Subject
compositeTemplates: [template]
})
# Call the API method
envelopes_api = ds.envelopes_api
envelope = nil
self.enhanced_report_for do
envelope = envelopes_api.create_envelope(ds.account_id, envelope_def)
end
# Save envelope_id in SubscriptionDocument and update the signatories
subdoc = SubscriptionDocument.where(offer: offer.id).first
subdoc.docusign_envelope_id = envelope.envelope_id
subdoc.update_signatories_from_entity(offer.entity)
subdoc.save!
envelope.envelope_id
end
##
# DRY out add'l instrumentation around the API calls
# will also retry the request just in case the token expired
def self.enhanced_report_for
attempts = 0
begin
yield
rescue Exception => error
attempts += 1
if attempts > 10
api_response = {
headers: error.response_headers.to_h,
body: JSON.parse(error.response_body)
}
Rails.logger.error "Docusign API returned an error: #{api_response.to_json}."
Raven.extra_context(api_response: api_response) do
Raven.capture_exception(error)
end
else
sleep(0.5)
Rails.logger.error "DocusignConnector: Auth failed, refreshing token"
DocuSignConnection.new.update_token
retry
end
end
end
# Method that generates the Signers for the document
#
def self.create_signers(offer)
# Code cut for clarity - this method returns an array of objects created via the
# SDK's DocuSign_eSign::Signer.new, example:
DocuSign_eSign::Signer.new(
email: member.user.email,
name: member.user.full_name,
roleName: "signer_#{signer_n}",
recipientId: member.user.id,
clientUserId: member.user.id,
tabs: {
textTabs: [{
tabLabel: "PrintName",
value: member.user.full_name.upcase
},
{
tabLabel: "ResidentialAddress",
value: member.user.address
},
{
tabLabel: "EntityName",
value: offer.entity.legal_entity_name
}]
}
)
end
##
#
# Additional methods for embedded signing url removed for clarity
#
end