|
| 1 | +/* |
| 2 | + * Copyright (C) 2014 TopCoder Inc., All Rights Reserved. |
| 3 | + * |
| 4 | + * @version 1.0 |
| 5 | + * @author TCSASSEMBLER |
| 6 | + */ |
| 7 | +"use strict"; |
| 8 | +/*jslint unparam: true */ |
| 9 | + |
| 10 | +var _ = require("underscore"); |
| 11 | +var async = require("async"); |
| 12 | +var S = require("string"); |
| 13 | +var config = require("../config").config; |
| 14 | + |
| 15 | +/** |
| 16 | + * The TermsOfUse Handler |
| 17 | + * Note that just like the Java code, it can be reused for different templates and termsOfUseId |
| 18 | + * The contrcutor takes the termsOfUseId during initialization |
| 19 | + */ |
| 20 | +function TermsOfUseHandler(termsOfUseId) { |
| 21 | + this.termsOfUseId = termsOfUseId; |
| 22 | +} |
| 23 | +TermsOfUseHandler.prototype.termsOfUseId = 0; |
| 24 | +/** |
| 25 | + * The function that actually handles the document |
| 26 | + * All future document handlers must also follow the same method signature as used here (akin to a Java interface) |
| 27 | + * @param userId The user for which to handle the document |
| 28 | + * @param tabs Arrays of objects which have tabLabel and tabValue parameters. |
| 29 | + * This is actually not used here but is needed because the method signature needs to consistent for all document handlers |
| 30 | + * @param api The actionhero api object |
| 31 | + * @param dbConnectionMap The DB connection map |
| 32 | + * @param done The callback to call once done. It will be called with argument if there is error, otherwise will be called without argument. |
| 33 | + * The argument must have a message property. If the error is temporary, then it should also have a temporary property set to true. |
| 34 | + */ |
| 35 | +TermsOfUseHandler.prototype.handleDocument = function (userId, tabs, api, dbConnectionMap, done) { |
| 36 | + var sqlParams = { |
| 37 | + termsOfUseId: this.termsOfUseId, |
| 38 | + userId: userId |
| 39 | + }; |
| 40 | + async.waterfall([ |
| 41 | + function (cb) { |
| 42 | + api.dataAccess.executeQuery("get_terms_of_use", sqlParams, dbConnectionMap, cb); |
| 43 | + }, function (rows, cb) { |
| 44 | + if (rows.length === 0) { |
| 45 | + done({ |
| 46 | + message: "No terms of use exists for id: " + sqlParams.termsOfUseId, |
| 47 | + }); |
| 48 | + return; |
| 49 | + } |
| 50 | + api.dataAccess.executeQuery("check_user_terms_of_use_ban", sqlParams, dbConnectionMap, cb); |
| 51 | + }, function (rows, cb) { |
| 52 | + if (rows.length !== 0) { |
| 53 | + api.log("User with id: " + userId + " is not allowed to accept terms of use with id: " + sqlParams.termsOfUseId, 'error'); |
| 54 | + done(); |
| 55 | + return; |
| 56 | + } |
| 57 | + api.dataAccess.executeQuery("check_user_terms_of_use_exist", sqlParams, dbConnectionMap, cb); |
| 58 | + }, function (rows, cb) { |
| 59 | + if (rows.length !== 0) { |
| 60 | + api.log("User with id: " + userId + " has already accepted terms of use with id: " + sqlParams.termsOfUseId, 'warn'); |
| 61 | + done(); |
| 62 | + return; |
| 63 | + } |
| 64 | + api.dataAccess.executeQuery("insert_user_terms_of_use", sqlParams, dbConnectionMap, cb); |
| 65 | + }, function (notUsed, cb) { |
| 66 | + cb(); |
| 67 | + } |
| 68 | + ], function (err) { |
| 69 | + if (err) { |
| 70 | + //If we have an error here, it is because of unexpected error (like DB connection died) |
| 71 | + //So this needs to be considered a temporary failure, so that Docusign Connect can call us again later |
| 72 | + done({ |
| 73 | + message: "Unable to process terms of use. Try again later.", |
| 74 | + temporary: true |
| 75 | + }); |
| 76 | + } else { |
| 77 | + done(); |
| 78 | + } |
| 79 | + }); |
| 80 | +}; |
| 81 | + |
| 82 | + |
| 83 | +/** |
| 84 | + * Contains the template name, id and the handlers for the template |
| 85 | + * Note that there can be more than 1 handlers per template |
| 86 | + * Handlers that are not required for this contest are left empty |
| 87 | + */ |
| 88 | +var templates = [{ |
| 89 | + name: 'W9', |
| 90 | + templateId: config.docusign.w9TemplateId, |
| 91 | + handlers: [] |
| 92 | +}, { |
| 93 | + name: 'W-8BEN', |
| 94 | + templateId: config.docusign.w8benTemplateId, |
| 95 | + handlers: [] |
| 96 | +}, { |
| 97 | + name: 'TopCoder Assignment v2.0', |
| 98 | + templateId: config.docusign.assignmentTemplateId, |
| 99 | + handlers: [ |
| 100 | + new TermsOfUseHandler(config.docusign.assignmentDocTermsOfUseId) |
| 101 | + ] |
| 102 | +}, { |
| 103 | + name: 'Appirio Mutual NDA', |
| 104 | + templateId: config.docusign.mutualNDATemplateId, |
| 105 | + handlers: [] |
| 106 | +}, { |
| 107 | + name: 'Affidavit', |
| 108 | + templateId: config.docusign.affidavitTemplateId, |
| 109 | + handlers: [] |
| 110 | +}]; |
| 111 | + |
| 112 | +/** |
| 113 | + * Convenience function that writes the response and calls the actionhero next |
| 114 | + * @param connection actionhero connection |
| 115 | + * @param statusCode the status code to write |
| 116 | + * @param next The actionhero next callback |
| 117 | + * @param message If exists then this message is set to body, otherwise body is simply 'success' |
| 118 | + */ |
| 119 | +function writeResponse(connection, statusCode, next, message) { |
| 120 | + connection.rawConnection.responseHttpCode = statusCode; |
| 121 | + connection.response = { |
| 122 | + message: message || 'success' |
| 123 | + }; |
| 124 | + next(connection, true); |
| 125 | +} |
| 126 | + |
| 127 | +/** |
| 128 | + * The error to throw if connect key is missing or invalid |
| 129 | + */ |
| 130 | +var CONNECT_KEY_MISSING = 'Connect Key is missing or invalid.'; |
| 131 | + |
| 132 | +/** |
| 133 | + * The Docusign Callback Action which accepts JSON. |
| 134 | + * Performs the logic common to all Docusign documents. |
| 135 | + */ |
| 136 | +exports.action = { |
| 137 | + name: 'docusignCallback', |
| 138 | + description: 'docusignCallback', |
| 139 | + blockedConnectionTypes: [], |
| 140 | + outputExample: {}, |
| 141 | + version: 'v2', |
| 142 | + transaction : 'write', |
| 143 | + cacheEnabled : false, |
| 144 | + databases : ["informixoltp", "common_oltp"], |
| 145 | + inputs: { |
| 146 | + required: ['envelopeStatus', 'envelopeId', 'tabs', 'connectKey'], |
| 147 | + optional: [], |
| 148 | + }, |
| 149 | + run: function (api, connection, next) { |
| 150 | + api.log("Execute docusignCallback#run", 'debug'); |
| 151 | + var dbConnectionMap = connection.dbConnectionMap, |
| 152 | + envelopeStatus = connection.params.envelopeStatus, |
| 153 | + envelopeId = connection.params.envelopeId, |
| 154 | + connectKey = connection.params.connectKey, |
| 155 | + tabs = connection.params.tabs, |
| 156 | + sqlParams = {}, |
| 157 | + envelopeInfo; |
| 158 | + |
| 159 | + async.waterfall([ |
| 160 | + function (cb) { |
| 161 | + if (connectKey !== config.docusign.callbackConnectKey) { |
| 162 | + api.log(CONNECT_KEY_MISSING, 'error'); |
| 163 | + writeResponse(connection, 404, next, CONNECT_KEY_MISSING); |
| 164 | + return; |
| 165 | + } |
| 166 | + |
| 167 | + if (envelopeStatus !== 'Completed') { |
| 168 | + api.log('Status is not completed.', 'info'); |
| 169 | + writeResponse(connection, 200, next); |
| 170 | + return; |
| 171 | + } |
| 172 | + |
| 173 | + if (new S(envelopeId).isEmpty()) { |
| 174 | + api.log('envelopeId is null or empty', 'error'); |
| 175 | + writeResponse(connection, 200, next); |
| 176 | + return; |
| 177 | + } |
| 178 | + |
| 179 | + //Set completed = 1 for the envelope id |
| 180 | + sqlParams.envelopeId = envelopeId; |
| 181 | + api.dataAccess.executeQuery("complete_docusign_envelope", sqlParams, dbConnectionMap, cb); |
| 182 | + }, function (updatedCount, cb) { |
| 183 | + //updatedCount is the number of rows that were updated. |
| 184 | + if (updatedCount === 1) { |
| 185 | + //Get the docusign data (we need the templateId) for the envelope |
| 186 | + api.dataAccess.executeQuery("get_docusign_envelope_by_envelope_id", sqlParams, dbConnectionMap, cb); |
| 187 | + } else { |
| 188 | + api.log('No enevelope with id: ' + envelopeId + ' was found.', 'error'); |
| 189 | + writeResponse(connection, 200, next); |
| 190 | + return; |
| 191 | + } |
| 192 | + }, function (rows, cb) { |
| 193 | + envelopeInfo = rows[0]; |
| 194 | + |
| 195 | + //Find the template for the envelope |
| 196 | + var template = _.findWhere(templates, {templateId: envelopeInfo.docusign_template_id}); |
| 197 | + if (template === undefined) { |
| 198 | + api.log('No Template was found for template id: ' + envelopeInfo.docusign_template_id, 'warn'); |
| 199 | + writeResponse(connection, 200, next); |
| 200 | + return; |
| 201 | + } |
| 202 | + |
| 203 | + //Call the handlers for the template, one after the other |
| 204 | + async.eachSeries(template.handlers, function (handler, cbx) { |
| 205 | + handler.handleDocument(envelopeInfo.user_id, tabs, api, dbConnectionMap, cbx); |
| 206 | + }, function (err) { |
| 207 | + if (err) { |
| 208 | + cb(err); |
| 209 | + return; |
| 210 | + } |
| 211 | + cb(); |
| 212 | + }); |
| 213 | + } |
| 214 | + ], function (err) { |
| 215 | + if (err) { |
| 216 | + //All errors need to be communicated to the support staff |
| 217 | + api.tasks.enqueue("sendEmail", { |
| 218 | + subject : config.docusign.callbackFailedEmailSubject, |
| 219 | + template : 'docusign_callback_failure_email', |
| 220 | + toAddress : config.docusign.supportEmailAddress, |
| 221 | + fromAddress : config.docusign.fromEmailAddress, |
| 222 | + userId : envelopeInfo.user_id, |
| 223 | + templateId: envelopeInfo.docusign_template_id, |
| 224 | + envelopeId : envelopeInfo.docusign_envelope_id, |
| 225 | + message : err.message |
| 226 | + }, 'default'); |
| 227 | + |
| 228 | + //Only temporary errors are to return 500, otherwise 200 |
| 229 | + if (err.temporary === true) { |
| 230 | + writeResponse(connection, 500, next, err.message); |
| 231 | + } else { |
| 232 | + writeResponse(connection, 200, next, err.message); |
| 233 | + } |
| 234 | + } else { |
| 235 | + writeResponse(connection, 200, next); |
| 236 | + } |
| 237 | + }); |
| 238 | + } |
| 239 | +}; |
0 commit comments