lib/docusign_rest/client.rb in docusign_rest-0.2.0 vs lib/docusign_rest/client.rb in docusign_rest-0.3.0

- old
+ new

@@ -5,10 +5,11 @@ class Client # Define the same set of accessors as the DocusignRest module attr_accessor *Configuration::VALID_CONFIG_KEYS attr_accessor :docusign_authentication_headers, :acct_id + attr_accessor :previous_call_log def initialize(options={}) # Merge the config values from the module and those passed to the client. merged_options = DocusignRest.options.merge(options) @@ -36,10 +37,13 @@ # Set the account_id from the configure block if present, but can't call # the instance var @account_id because that'll override the attr_accessor # that is automatically configured for the configure block @acct_id = account_id + + #initialize the log cache + @previous_call_log = [] end # Internal: sets the default request headers allowing for user overrides # via options[:headers] from within other requests. Additionally injects @@ -141,10 +145,11 @@ request = Net::HTTP::Post.new(uri.request_uri, content_type) request.body = "grant_type=password&client_id=#{integrator_key}&username=#{email}&password=#{password}&scope=api" http = initialize_net_http_ssl(uri) response = http.request(request) + generate_log(request, response, uri) JSON.parse(response.body) end # Public: gets info necessary to make additional requests to the DocuSign API @@ -167,11 +172,13 @@ # userName - Full name provided when signing up for DocuSign def get_login_information(options={}) uri = build_uri('/login_information') request = Net::HTTP::Get.new(uri.request_uri, headers(options[:headers])) http = initialize_net_http_ssl(uri) - http.request(request) + response = http.request(request) + generate_log(request, response, uri) + response end # Internal: uses the get_login_information method to determine the client's # accountId and then caches that value into an instance variable so we @@ -198,21 +205,19 @@ # Internal: takes in an array of hashes of signers and concatenates all the # hashes with commas # # embedded - Tells DocuSign if this is an embedded signer which determines - # weather or not to deliver emails. Also lets us authenticate + # whether or not to deliver emails. Also lets us authenticate # them when they go to do embedded signing. Behind the scenes # this is setting the clientUserId value to the signer's email. # name - The name of the signer # email - The email of the signer # role_name - The role name of the signer ('Attorney', 'Client', etc.). # tabs - Array of tab pairs grouped by type (Example type: 'textTabs') # { textTabs: [ { tabLabel: "label", name: "name", value: "value" } ] } # - # NOTE: The 'tabs' option is NOT supported in 'v1' of the REST API - # # Returns a hash of users that need to be embedded in the template to # create an envelope def get_template_roles(signers) template_roles = [] signers.each_with_index do |signer, index| @@ -237,10 +242,21 @@ template_roles << template_role end template_roles end + def get_sign_here_tabs(tabs) + Array(tabs).map do |tab| + { + documentId: tab[:document_id], + recipientId: tab[:recipient_id], + anchorString: tab[:anchor_string], + anchorXOffset: tab[:anchorXOffset], + anchorYOffset: tab[:anchorYOffset] + } + end + end # TODO (2014-02-03) jonk => document def get_signer_tabs(tabs) Array(tabs).map do |tab| { @@ -258,18 +274,24 @@ # TODO (2014-02-03) jonk => document def get_event_notification(event_notification) return {} unless event_notification { useSoapInterface: event_notification[:use_soap_interface] || false, - includeCertificatWithSoap: event_notification[:include_certificate_with_soap] || false, + includeCertificateWithSoap: event_notification[:include_certificate_with_soap] || false, url: event_notification[:url], loggingEnabled: event_notification[:logging], - 'EnvelopeEvents' => Array(event_notification[:envelope_events]).map do |envelope_event| + 'envelopeEvents' => Array(event_notification[:envelope_events]).map do |envelope_event| { includeDocuments: envelope_event[:include_documents] || false, envelopeEventStatusCode: envelope_event[:envelope_event_status_code] } + end, + 'recipientEvents' => Array(event_notification[:recipient_events]).map do |recipient_event| + { + includeDocuments: recipient_event[:include_documents] || false, + recipientEventStatusCode: recipient_event[:recipient_event_status_code] + } end } end @@ -281,11 +303,11 @@ # template - Includes other optional fields only used when # being called from a template # email - The signer's email # name - The signer's name # embedded - Tells DocuSign if this is an embedded signer which - # determines weather or not to deliver emails. Also + # determines whether or not to deliver emails. Also # lets us authenticate them when they go to do # embedded signing. Behind the scenes this is setting # the clientUserId value to the signer's email. # email_notification - Send an email or not # role_name - The signer's role, like 'Attorney' or 'Client', etc. @@ -310,21 +332,21 @@ doc_signer = { email: signer[:email], name: signer[:name], accessCode: '', addAccessCodeToEmail: false, - customFields: nil, + customFields: signer[:custom_fields], iDCheckConfigurationName: nil, iDCheckInformationInput: nil, inheritEmailNotificationConfiguration: false, note: '', phoneAuthentication: nil, recipientAttachment: nil, recipientId: "#{index + 1}", requireIdLookup: false, roleName: signer[:role_name], - routingOrder: index + 1, + routingOrder: signer[:routing_order] || index + 1, socialAuthentications: nil } if signer[:email_notification] doc_signer[:emailNotification] = signer[:email_notification] @@ -353,11 +375,11 @@ emailTabs: get_tabs(signer[:email_tabs], options, index), envelopeIdTabs: nil, fullNameTabs: get_tabs(signer[:full_name_tabs], options, index), listTabs: get_tabs(signer[:list_tabs], options, index), noteTabs: nil, - numberTabs: nil, + numberTabs: get_tabs(signer[:number_tabs], options, index), radioGroupTabs: get_tabs(signer[:radio_group_tabs], options, index), initialHereTabs: get_tabs(signer[:initial_here_tabs], options.merge!(initial_here_tab: true), index), signHereTabs: get_tabs(signer[:sign_here_tabs], options.merge!(sign_here_tab: true), index), signerAttachmentTabs: nil, ssnTabs: nil, @@ -371,10 +393,76 @@ end doc_signers end + # Internal: people to be Carbon Copied on the document that is created + # https://docs.docusign.com/esign/restapi/Envelopes/Envelopes/create/ + # + # Expecting options to be an array of hashes, with each hash representing a person to carbon copy + # + # email - The email of the recipient to be copied on the document + # name - The name of the recipient + # signer_count - Used to generate required attributes recipientId and routingOrder which must be unique in the document + # + def get_carbon_copies(options, signer_count) + copies = [] + (options || []).each do |cc| + signer_count += 1 + raise "Missing required data [:email, :name]" unless (cc[:email] && cc[:name]) + cc.merge!(recipient_id: signer_count, routing_order: signer_count) + copies << camelize_keys(cc) + end + copies + end + + # Public: Translate ruby oriented keys to camel cased keys recursively through the hash received + # + # The method expects symbol parameters in ruby form ":access_code" and translates them to camel cased "accessCode" + # + # example [{access_code: '12345', email_notification: {email_body: 'abcdef'}}] -> [{'accessCode': '12345', 'emailNotification': {'emailBody': 'abcdef'}}] + # + def camelize_keys(hash) + new_hash={} + hash.each do |k,v| + new_hash[camelize(k.to_s)] = (v.is_a?(Hash) ? camelize_keys(v) : v) + end + new_hash + end + + # Generic implementation to avoid having to pull in Rails dependencies + # + def camelize(str) + str.gsub(/_([a-z])/) { $1.upcase } + end + + # Internal: takes an array of hashes of certified deliveries + # + # email - The recipient email + # name - The recipient name + # recipient_id - The recipient's id + # embedded - Tells DocuSign if this is an embedded recipient which + # determines whether or not to deliver emails. + def get_certified_deliveries(certified_deliveries) + doc_certified_deliveries = [] + + certified_deliveries.each do |certified_delivery| + doc_certified_delivery = { + email: certified_delivery[:email], + name: certified_delivery[:name], + recipientId: certified_delivery[:recipient_id] + } + + if certified_delivery[:embedded] + doc_certified_delivery[:clientUserId] = certified_delivery[:client_id] || certified_delivery[:email] + end + + doc_certified_deliveries << doc_certified_delivery + end + doc_certified_deliveries + end + # TODO (2014-02-03) jonk => document def get_tabs(tabs, options, index) tab_array = [] Array(tabs).map do |tab| @@ -410,19 +498,27 @@ tab_hash[:optional] = tab[:optional] || false tab_hash[:tabLabel] = tab[:label] || 'Signature 1' tab_hash[:width] = tab[:width] if tab[:width] tab_hash[:height] = tab[:height] if tab[:height] tab_hash[:value] = tab[:value] if tab[:value] + tab_hash[:fontSize] = tab[:font_size] if tab[:font_size] + tab_hash[:fontColor] = tab[:font_color] if tab[:font_color] + tab_hash[:bold] = tab[:bold] if tab[:bold] + tab_hash[:italic] = tab[:italic] if tab[:italic] + tab_hash[:underline] = tab[:underline] if tab[:underline] tab_hash[:selected] = tab[:selected] if tab[:selected] tab_hash[:locked] = tab[:locked] || false tab_hash[:list_items] = tab[:list_items] if tab[:list_items] tab_hash[:groupName] = tab[:group_name] if tab.key?(:group_name) tab_hash[:radios] = get_tabs(tab[:radios], options, index) if tab.key?(:radios) + tab_hash[:validationMessage] = tab[:validation_message] if tab[:validation_message] + tab_hash[:validationPattern] = tab[:validation_pattern] if tab[:validation_pattern] + tab_array << tab_hash end tab_array end @@ -506,11 +602,10 @@ # Takes an optional array of files, which consist of documents to be used instead of templates # # Returns an array of server template hashes def get_composite_template(server_template_ids, signers, files) composite_array = [] - index = 0 server_template_ids.each_with_index do |template_id, idx| server_template_hash = { sequence: (idx+1).to_s, templateId: template_id, templateRoles: get_template_roles(signers), @@ -543,16 +638,18 @@ email: signer[:email], name: signer[:name], recipientId: signer[:recipient_id], roleName: signer[:role_name], clientUserId: signer[:client_id] || signer[:email], + requireSignOnPaper: signer[:require_sign_on_paper] || false, tabs: { textTabs: get_signer_tabs(signer[:text_tabs]), checkboxTabs: get_signer_tabs(signer[:checkbox_tabs]), numberTabs: get_signer_tabs(signer[:number_tabs]), fullNameTabs: get_signer_tabs(signer[:fullname_tabs]), - dateTabs: get_signer_tabs(signer[:date_tabs]) + dateTabs: get_signer_tabs(signer[:date_tabs]), + signHereTabs: get_sign_here_tabs(signer[:sign_here_tabs]) } } signers_array << signers_hash end template_hash = {sequence: sequence, recipients: { signers: signers_array }} @@ -575,10 +672,13 @@ # params - A hash of params (including files for uploading and a # customized request body) # headers={} - The fully merged, final request headers # boundary - Optional: you can give the request a custom boundary # + + headers = headers.dup.merge(parts: {post_body: {'Content-Type' => 'application/json'}}) + request = Net::HTTP::Post::Multipart.new( uri.request_uri, { post_body: post_body }.merge(file_params), headers ) @@ -601,57 +701,65 @@ # the local path of the file you wish to upload. Absolute # paths recommended. # file_name - The name you want to give to the file you are uploading # content_type - (for the request body) application/json is what DocuSign # is expecting - # email_subject - (Optional) short subject line for the email - # email_body - (Optional) custom text that will be injected into the + # email[subject] - (Optional) short subject line for the email + # email[body] - (Optional) custom text that will be injected into the # DocuSign generated email # signers - A hash of users who should receive the document and need # to sign it. More info about the options available for # this method are documented above it's method definition. + # carbon_copies - An array of hashes that includes users names and email who + # should receive a copy of the document once it is complete. # status - Options include: 'sent', 'created', 'voided' and determine # if the envelope is sent out immediately or stored for # sending at a later time # customFields - (Optional) A hash of listCustomFields and textCustomFields. # Each contains an array of corresponding customField hashes. # For details, please see: http://bit.ly/1FnmRJx - # headers - Allows a client to pass in some + # headers - Allows a client to pass in some headers + # web_sign - (Optional) If true, the signer is allowed to print the + # document and sign it on paper. False if not defined. # # Returns a JSON parsed response object containing: # envelopeId - The envelope's ID # status - Sent, created, or voided # statusDateTime - The date/time the envelope was created # uri - The relative envelope uri def create_envelope_from_document(options={}) ios = create_file_ios(options[:files]) file_params = create_file_params(ios) - post_body = { + post_hash = { emailBlurb: "#{options[:email][:body] if options[:email]}", emailSubject: "#{options[:email][:subject] if options[:email]}", documents: get_documents(ios), recipients: { - signers: get_signers(options[:signers]) + signers: get_signers(options[:signers]), + carbonCopies: get_carbon_copies(options[:carbon_copies],options[:signers].size) }, + eventNotification: get_event_notification(options[:event_notification]), status: "#{options[:status]}", customFields: options[:custom_fields] - }.to_json + } + post_hash[:enableWetSign] = options[:wet_sign] if options.has_key? :web_sign + post_body = post_hash.to_json uri = build_uri("/accounts/#{acct_id}/envelopes") http = initialize_net_http_ssl(uri) request = initialize_net_http_multipart_post_request( uri, post_body, file_params, headers(options[:headers]) ) response = http.request(request) + generate_log(request, response, uri) JSON.parse(response.body) end - # Public: allows a template to be dynamically created with several options. # # files - An array of hashes of file parameters which will be used # to create actual files suitable for upload in a multipart # request. @@ -704,10 +812,11 @@ request = initialize_net_http_multipart_post_request( uri, post_body, file_params, headers(options[:headers]) ) response = http.request(request) + generate_log(request, response, uri) JSON.parse(response.body) end # TODO (2014-02-03) jonk => document @@ -718,10 +827,11 @@ uri = build_uri("/accounts/#{acct_id}/templates/#{template_id}") http = initialize_net_http_ssl(uri) request = Net::HTTP::Get.new(uri.request_uri, headers(content_type)) response = http.request(request) + generate_log(request, response, uri) JSON.parse(response.body) end # Public: create an envelope for delivery from a template @@ -753,23 +863,27 @@ post_body = { status: options[:status], emailBlurb: options[:email][:body], emailSubject: options[:email][:subject], templateId: options[:template_id], + enableWetSign: options[:wet_sign], + brandId: options[:brand_id], eventNotification: get_event_notification(options[:event_notification]), templateRoles: get_template_roles(options[:signers]), - customFields: options[:custom_fields] + customFields: options[:custom_fields], + allowReassign: options[:allow_reassign] }.to_json uri = build_uri("/accounts/#{acct_id}/envelopes") http = initialize_net_http_ssl(uri) request = Net::HTTP::Post.new(uri.request_uri, headers(content_type)) request.body = post_body response = http.request(request) + generate_log(request, response, uri) JSON.parse(response.body) end # Public: create an envelope for delivery from a composite template @@ -780,14 +894,12 @@ # determine if the envelope is sent out immediately or # stored for sending at a later time # email/body - Sets the text in the email body # email/subject - Sets the text in the email subject line # files - Sets documents to be used instead of inline or server templates - # template_roles - See the get_template_roles method definition for a list - # of options to pass. Note: for consistency sake we call - # this 'signers' and not 'templateRoles' when we build up - # the request in client code. + # signers - See get_template_roles/get_inline_signers for a list + # of options to pass. # headers - Optional hash of headers to merge into the existing # required headers for a multipart request. # server_template_ids - Array of ids for templates uploaded to DocuSign. Templates # will be added in the order they appear in the array. # @@ -806,10 +918,12 @@ post_body = { emailBlurb: "#{options[:email][:body] if options[:email]}", emailSubject: "#{options[:email][:subject] if options[:email]}", status: options[:status], + brandId: options[:brand_id], + allowReassign: options[:allow_reassign], compositeTemplates: get_composite_template(options[:server_template_ids], options[:signers], options[:files]) }.to_json uri = build_uri("/accounts/#{acct_id}/envelopes") @@ -818,10 +932,11 @@ request = initialize_net_http_multipart_post_request( uri, post_body, file_params, headers(options[:headers]) ) response = http.request(request) + generate_log(request, response, uri) JSON.parse(response.body) end # Public returns the names specified for a given email address (existing docusign user) @@ -840,14 +955,45 @@ http = initialize_net_http_ssl(uri) request = Net::HTTP::Post.new(uri.request_uri, headers(content_type)) response = http.request(request) + generate_log(request, response, uri) JSON.parse(response.body) end + # Public adds the certified delivery recipients (Need to View) for a given envelope + # + # envelope_id - ID of the envelope for which you want to retrieve the + # signer info + # headers - optional hash of headers to merge into the existing + # required headers for a multipart request. + # certified_deliveries - A required hash of all the certified delivery recipients + # that need to be added to the envelope + # + # # The response returns the success or failure of each recipient being added + # to the envelope and the envelope ID + def add_envelope_certified_deliveries(options={}) + content_type = { 'Content-Type' => 'application/json' } + content_type.merge(options[:headers]) if options[:headers] + post_body = { + certifiedDeliveries: get_certified_deliveries(options[:certified_deliveries]), + }.to_json + + uri = build_uri("/accounts/#{acct_id}/envelopes/#{options[:envelope_id]}/recipients") + + http = initialize_net_http_ssl(uri) + + request = Net::HTTP::Post.new(uri.request_uri, headers(content_type)) + request.body = post_body + + response = http.request(request) + generate(request, response, uri) + JSON.parse(response.body) + end + # Public returns the URL for embedded signing # # envelope_id - the ID of the envelope you wish to use for embedded signing # name - the name of the signer # email - the email of the recipient @@ -875,10 +1021,11 @@ request = Net::HTTP::Post.new(uri.request_uri, headers(content_type)) request.body = post_body response = http.request(request) + generate_log(request, response, uri) JSON.parse(response.body) end # Public returns the URL for embedded sending # @@ -899,10 +1046,11 @@ request = Net::HTTP::Post.new(uri.request_uri, headers(content_type)) request.body = { returnUrl: options[:return_url] }.to_json response = http.request(request) + generate_log(request, response, uri) JSON.parse(response.body) end # Public returns the URL for embedded console # @@ -925,11 +1073,11 @@ request = Net::HTTP::Post.new(uri.request_uri, headers(content_type)) request.body = post_body response = http.request(request) - + generate_log(request, response, uri) parsed_response = JSON.parse(response.body) parsed_response['url'] end @@ -953,10 +1101,11 @@ uri = build_uri("/accounts/#{acct_id}/envelopes/#{options[:envelope_id]}/recipients?include_tabs=#{include_tabs}&include_extended=#{include_extended}") http = initialize_net_http_ssl(uri) request = Net::HTTP::Get.new(uri.request_uri, headers(content_type)) response = http.request(request) + generate_log(request, response, uri) JSON.parse(response.body) end # Public retrieves the envelope status @@ -969,10 +1118,11 @@ uri = build_uri("/accounts/#{acct_id}/envelopes/#{options[:envelope_id]}") http = initialize_net_http_ssl(uri) request = Net::HTTP::Get.new(uri.request_uri, headers(content_type)) response = http.request(request) + generate_log(request, response, uri) JSON.parse(response.body) end # Public retrieves the statuses of envelopes matching the given query @@ -983,24 +1133,27 @@ # Defaults to the time of the call. # # from_to_status - The status of the envelope checked for in the from_date - to_date period. # Defaults to 'changed' # + # envelope_ids - Comma joined list of envelope_ids which you want to query. + # # status - The current status of the envelope. Defaults to any status. # # Returns an array of hashes containing envelope statuses, ids, and similar information. def get_envelope_statuses(options={}) content_type = { 'Content-Type' => 'application/json' } content_type.merge(options[:headers]) if options[:headers] - query_params = options.slice(:from_date, :to_date, :from_to_status, :status) + query_params = options.slice(:from_date, :to_date, :from_to_status, :envelope_ids, :status) + # Note that Hash#to_query is an ActiveSupport monkeypatch uri = build_uri("/accounts/#{acct_id}/envelopes?#{query_params.to_query}") http = initialize_net_http_ssl(uri) request = Net::HTTP::Get.new(uri.request_uri, headers(content_type)) response = http.request(request) - + generate_log(request, response, uri) JSON.parse(response.body) end # Public retrieves the attached file from a given envelope @@ -1029,10 +1182,11 @@ uri = build_uri("/accounts/#{acct_id}/envelopes/#{options[:envelope_id]}/documents/#{options[:document_id]}") http = initialize_net_http_ssl(uri) request = Net::HTTP::Get.new(uri.request_uri, headers(content_type)) response = http.request(request) + generate_log(request, response, uri) return response.body if options[:return_stream] split_path = options[:local_save_path].split('/') split_path.pop #removes the document name and extension from the array path = split_path.join("/") #rejoins the array to form path to the folder that will contain the file @@ -1056,11 +1210,11 @@ uri = build_uri("/accounts/#{acct_id}/envelopes/#{options[:envelope_id]}/documents") http = initialize_net_http_ssl(uri) request = Net::HTTP::Get.new(uri.request_uri, headers(content_type)) response = http.request(request) - + generate_log(request, response, uri) JSON.parse(response.body) end # Public retrieves a PDF containing the combined content of all @@ -1069,10 +1223,11 @@ # envelope_id - ID of the envelope from which the doc will be retrieved # local_save_path - Local absolute path to save the doc to including the # filename itself # headers - Optional hash of headers to merge into the existing # required headers for a multipart request. + # params - Optional params; for example, certificate: true # # Example # # client.get_combined_document_from_envelope( # envelope_id: @envelope_response['envelopeId'], @@ -1084,14 +1239,16 @@ def get_combined_document_from_envelope(options={}) content_type = { 'Content-Type' => 'application/json' } content_type.merge(options[:headers]) if options[:headers] uri = build_uri("/accounts/#{acct_id}/envelopes/#{options[:envelope_id]}/documents/combined") + uri.query = URI.encode_www_form(options[:params]) if options[:params] http = initialize_net_http_ssl(uri) request = Net::HTTP::Get.new(uri.request_uri, headers(content_type)) response = http.request(request) + generate_log(request, response, uri) return response.body if options[:return_stream] split_path = options[:local_save_path].split('/') split_path.pop #removes the document name and extension from the array path = split_path.join("/") #rejoins the array to form path to the folder that will contain the file @@ -1130,11 +1287,11 @@ http = initialize_net_http_ssl(uri) request = Net::HTTP::Put.new(uri.request_uri, headers(content_type)) request.body = post_body response = http.request(request) - + generate_log(request, response, uri) response end # Public returns a hash of audit events for a given envelope @@ -1154,11 +1311,11 @@ uri = build_uri("/accounts/#{acct_id}/envelopes/#{options[:envelope_id]}/audit_events") http = initialize_net_http_ssl(uri) request = Net::HTTP::Get.new(uri.request_uri, headers(content_type)) response = http.request(request) - + generate_log(request, response, uri) JSON.parse(response.body) end # Public retrieves folder information. Helpful to use before client.search_folder_for_envelopes def get_folder_list(options={}) @@ -1168,10 +1325,11 @@ uri = build_uri("/accounts/#{@acct_id}/folders/") http = initialize_net_http_ssl(uri) request = Net::HTTP::Get.new(uri.request_uri, headers(content_type)) response = http.request(request) + generate_log(request, response, uri) JSON.parse(response.body) end # Public retrieves the envelope(s) from a specific folder based on search params. # @@ -1209,10 +1367,11 @@ uri = build_uri("/accounts/#{@acct_id}/folders/#{options[:folder_id]}/?#{q.join('&')}") http = initialize_net_http_ssl(uri) request = Net::HTTP::Get.new(uri.request_uri, headers(content_type)) response = http.request(request) + generate_log(request, response, uri) JSON.parse(response.body) end # TODO (2014-02-03) jonk => document @@ -1226,10 +1385,11 @@ http = initialize_net_http_ssl(uri) request = Net::HTTP::Post.new(uri.request_uri, headers(content_type)) request.body = post_body response = http.request(request) + generate_log(request, response, uri) JSON.parse(response.body) end # TODO (2014-02-03) jonk => document @@ -1253,10 +1413,11 @@ uri = build_uri("/accounts/#{account_id}") http = initialize_net_http_ssl(uri) request = Net::HTTP::Delete.new(uri.request_uri, headers(content_type)) response = http.request(request) + generate_log(request, response, uri) json = response.body json = '{}' if json.nil? || json == '' JSON.parse(json) end @@ -1271,11 +1432,13 @@ def get_templates uri = build_uri("/accounts/#{acct_id}/templates") http = initialize_net_http_ssl(uri) request = Net::HTTP::Get.new(uri.request_uri, headers({ 'Content-Type' => 'application/json' })) - JSON.parse(http.request(request).body) + response = http.request(request) + generate_log(request, response, uri) + JSON.parse(response.body) end # Public: Retrieves a list of templates used in an envelope # @@ -1285,11 +1448,13 @@ def get_templates_in_envelope(envelope_id) uri = build_uri("/accounts/#{acct_id}/envelopes/#{envelope_id}/templates") http = initialize_net_http_ssl(uri) request = Net::HTTP::Get.new(uri.request_uri, headers({ 'Content-Type' => 'application/json' })) - JSON.parse(http.request(request).body) + response = http.request(request) + generate_log(request, response, uri) + JSON.parse(response.body) end # Grabs envelope data. # Equivalent to the following call in the API explorer: @@ -1301,10 +1466,11 @@ uri = build_uri("/accounts/#{acct_id}/envelopes/#{envelope_id}") http = initialize_net_http_ssl(uri) request = Net::HTTP::Get.new(uri.request_uri, headers(content_type)) response = http.request(request) + generate_log(request, response, uri) JSON.parse(response.body) end # Public deletes a recipient for a given envelope @@ -1327,10 +1493,11 @@ http = initialize_net_http_ssl(uri) request = Net::HTTP::Delete.new(uri.request_uri, headers(content_type)) request.body = post_body response = http.request(request) + generate_log(request, response, uri) JSON.parse(response.body) end # Public voids an in-process envelope @@ -1351,15 +1518,17 @@ uri = build_uri("/accounts/#{acct_id}/envelopes/#{options[:folder_id]}") http = initialize_net_http_ssl(uri) request = Net::HTTP::Put.new(uri.request_uri, headers(content_type)) request.body = post_body - http.request(request) + response = http.request(request) + generate_log(request, response, uri) + response end # Public deletes a document for a given envelope - # See https://www.docusign.com/p/RESTAPIGuide/RESTAPIGuide.htm#REST API References/Remove Documents from a Draft Envelope.htm%3FTocPath%3DREST%2520API%2520References%7C_____54 + # See https://docs.docusign.com/esign/restapi/Envelopes/EnvelopeDocuments/delete/ # # envelope_id - ID of the envelope from which the doc will be retrieved # document_id - ID of the document to delete # # Returns the success or failure of each document being added to the envelope and @@ -1380,15 +1549,16 @@ http = initialize_net_http_ssl(uri) request = Net::HTTP::Delete.new(uri.request_uri, headers(content_type)) request.body = post_body response = http.request(request) + generate_log(request, response, uri) JSON.parse(response.body) end # Public adds a document to a given envelope - # See https://www.docusign.com/p/RESTAPIGuide/RESTAPIGuide.htm#REST API References/Add Document.htm%3FTocPath%3DREST%2520API%2520References%7C_____56 + # See https://docs.docusign.com/esign/restapi/Envelopes/EnvelopeDocuments/update/ # # envelope_id - ID of the envelope from which the doc will be added # document_id - ID of the document to add # file_path - Local or remote path to file # content_type - optional content type for file. Defaults to application/pdf. @@ -1410,24 +1580,25 @@ post_body = open(options[:file_path]).read http = initialize_net_http_ssl(uri) request = Net::HTTP::Put.new(uri.request_uri, headers(headers)) request.body = post_body - - http.request(request) + response = http.request(request) + generate_log(request, response, uri) + response end # Public adds signers to a given envelope - # See https://www.docusign.com/p/RESTAPIGuide/RESTAPIGuide.htm#REST%20API%20References/Add%20Recipients%20to%20an%20Envelope.htm%3FTocPath%3DREST%2520API%2520References|_____77 + # Seehttps://docs.docusign.com/esign/restapi/Envelopes/EnvelopeRecipients/update/ # # envelope_id - ID of the envelope to which the recipient will be added # signers - Array of hashes - # See https://www.docusign.com/p/RESTAPIGuide/RESTAPIGuide.htm#REST%20API%20References/Recipients/Signers%20Recipient.htm%3FTocPath%3DREST%2520API%2520References|Send%2520an%2520Envelope%2520or%2520Create%2520a%2520Draft%2520Envelope|Recipient%2520Parameters|_____7 + # See https://docs.docusign.com/esign/restapi/Envelopes/EnvelopeRecipients/update/#definitions # # TODO: This could be made more general as an add_envelope_recipient method # to handle recipient types other than Signer - # See: https://www.docusign.com/p/RESTAPIGuide/RESTAPIGuide.htm#REST%20API%20References/Recipient%20Parameter.htm%3FTocPath%3DREST%2520API%2520References|Send%2520an%2520Envelope%2520or%2520Create%2520a%2520Draft%2520Envelope|Recipient%2520Parameters|_____0 + # See: https://docs.docusign.com/esign/restapi/Envelopes/EnvelopeRecipients/update/#examples def add_envelope_signers(options = {}) content_type = { "Content-Type" => "application/json" } content_type.merge(options[:headers]) if options[:headers] uri = build_uri("/accounts/#{@acct_id}/envelopes/#{options[:envelope_id]}/recipients") @@ -1436,15 +1607,16 @@ http = initialize_net_http_ssl(uri) request = Net::HTTP::Put.new(uri.request_uri, headers(content_type)) request.body = post_body response = http.request(request) + generate_log(request, response, uri) JSON.parse(response.body) end # Public adds recipient tabs to a given envelope - # See https://www.docusign.com/p/RESTAPIGuide/RESTAPIGuide.htm#REST API References/Add Tabs for a Recipient.htm%3FTocPath%3DREST%2520API%2520References%7C_____86 + # See https://docs.docusign.com/esign/restapi/Envelopes/EnvelopeRecipients/update/ # # envelope_id - ID of the envelope from which the doc will be added # recipient - ID of the recipient to add tabs to # tabs - hash of tab (see example below) # { @@ -1486,9 +1658,34 @@ http = initialize_net_http_ssl(uri) request = Net::HTTP::Post.new(uri.request_uri, headers(content_type)) request.body = post_body response = http.request(request) + generate_log(request, response, uri) JSON.parse(response.body) + end + + private + + # Private: Generates a standardized log of the request and response pair + # to and from DocuSign for logging and API Certification. + # and resulting list is set to the publicly accessible: @previous_call_log + # For example: + # envelope = connection.create_envelope_from_document(doc) + # connection.previous_call_log.each {|line| logger.debug line } + def generate_log(request, response, uri) + log = ['--DocuSign REQUEST--'] + log << "#{request.method} #{uri.to_s}" + request.each_capitalized{ |k,v| log << "#{k}: #{v.gsub(/(?<="Password":")(.+?)(?=")/, '[FILTERED]')}" } + # Trims out the actual binary file to reduce log size + if request.body + request_body = request.body.gsub(/(?<=Content-Transfer-Encoding: binary).+?(?=-------------RubyMultipartPost)/m, "\n[BINARY BLOB]\n") + log << "Body: #{request_body}" + end + log << '--DocuSign RESPONSE--' + log << "HTTP/#{response.http_version} #{response.code} #{response.msg}" + response.each_capitalized{ |k,v| log << "#{k}: #{v}" } + log << "Body: #{response.body}" + @previous_call_log = log end end end