Skip to content

Instantly share code, notes, and snippets.

@mattlima
Last active September 21, 2020 13:50
Show Gist options
  • Save mattlima/2aaf639c20d3f35485d3bb68d2fe59fb to your computer and use it in GitHub Desktop.
Save mattlima/2aaf639c20d3f35485d3bb68d2fe59fb to your computer and use it in GitHub Desktop.

Sample error output

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]   

Connector code excerpt

###
#
# 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment