1/*global window, braintree*/ 2var actionkit = window.actionkit || {}; 3actionkit.donations = actionkit.donations || {}; 4 5(function (donations) { 6 donations.threeDSecureInstances = {}; 7 8 donations.addHiddenInput = function(form, name, value) { 9 var input = document.createElement('input'); 10 input.type = 'hidden'; 11 input.name = name; 12 input.value = value; 13 form.appendChild(input); 14 return input; 15 }; 16 17 donations.handleError = function(error) { 18 window.alert(error.message); 19 if (window.console) { 20 window.console.log(error); 21 } 22 }; 23 24 donations.initDataCollector = function(clientInstance, options) { 25 if (!braintree.dataCollector) { 26 console && console.log("Data collector not found."); 27 return; 28 } 29 if (!clientInstance.getConfiguration().gatewayConfiguration.kount) { 30 console && console.log("Kount not enabled."); 31 return; 32 } 33 braintree.dataCollector.create({ 34 client: clientInstance, 35 kount: true 36 }, function (dataCollectorError, dataCollectorInstance) { 37 if (dataCollectorError) { 38 donations.handleError(dataCollectorError); 39 return; 40 } 41 donations.addHiddenInput(options.form, 'device_data', dataCollectorInstance.deviceData); 42 }); 43 }; 44 45 donations.threeDSecureLookupComplete = function (data, next) { 46 // can be overridden, but must call next() 47 next(); 48 }; 49 50 donations.getAmount = function(form) { 51 // NOTE: update_total depends on javascript from Original/donate.html. 52 // Falls back to form.amount. Override if those aren't available. 53 if (!!window.update_total) { 54 return update_total(); 55 } else if (form.amount) { 56 return form.amount.value; 57 } else { 58 console && console.log("Error: No amount found!"); 59 throw "Error: no amount found"; 60 } 61 }; 62 63 donations.setAmountChanged = function(form) { 64 let orig = donations.getAmount(form); 65 form.amountChanged = function() { 66 return donations.getAmount(form) != orig; 67 }; 68 }; 69 70 donations.akBraintreeParamMap = { 71 "email": "email", 72 "first_name": "billingAddress.givenName", 73 "last_name": "billingAddress.familyName", 74 "address1": "billingAddress.streetAddress", 75 "address2": "billingAddress.extendedAddress", 76 "city": "billingAddress.locality", 77 "region": "billingAddress.regions", 78 "zip": "billingAddress.postalCode", 79 "postal": "billingAddress.postalCode", 80 }; 81 82 function removeNonAscii(s) { 83 return s.replace(/[^\x20-\x7E]/g, ''); 84 } 85 86 donations.getThreeDSecureParameters = function(form) { 87 let params = {}; 88 89 for (let akField in donations.akBraintreeParamMap) { 90 if (form[akField] && form[akField].value) { 91 let path = donations.akBraintreeParamMap[akField].split("."), 92 key = path.pop(), 93 d = params; 94 path.forEach(function(p) {d[p] = (d[p] || {}); d = d[p]}); 95 d[key] = form[akField].value; 96 } 97 } 98 // expand name to first + last 99 if (form.name && 100 form.name.value && 101 !params["billingAddress"]["givenName"] && 102 !params["billingAddress"]["familyName"] 103 ) { 104 let parts = /(\S+)\s+(.*)/.exec(form.name.value); 105 if (parts) { 106 params["billingAddress"] = (params["billingAddress"] || {}); 107 params["billingAddress"]["givenName"] = parts[1]; 108 params["billingAddress"]["familyName"] = parts[2]; 109 } 110 } 111 // strip non-ascii from name 112 if (params["billingAddress"]["givenName"]) { 113 params["billingAddress"]["givenName"] = removeNonAscii(params["billingAddress"]["givenName"]); 114 } 115 if (params["billingAddress"]["familyName"]) { 116 params["billingAddress"]["familyName"] = removeNonAscii(params["billingAddress"]["familyName"]); 117 } 118 return params; 119 }; 120 121 donations.hostedFieldsAreEmpty = function(hostedFieldsInstance) { 122 let state = hostedFieldsInstance.getState(); 123 for (let field in state.fields) { 124 if (!state.fields[field].isEmpty) { 125 return false;
126 } 127 } 128 return true; 129 }; 130 131 donations.shouldVaultFirst = function(options, hostedFieldsInstance) { 132 if (options.isRecurringUpdate) { 133 return !donations.hostedFieldsAreEmpty(hostedFieldsInstance); 134 } else { 135 return options.form["donation_type"] && options.form["donation_type"].value == "recurring"; 136 } 137 }; 138 139 donations.handleVaultError = function(xhr, textStatus) { 140 let err = xhr.responseText; 141 try { 142 resp = JSON.parse(err); 143 if (resp.form_errors) { 144 actionkit.errors = resp.form_errors; 145 actionkit.forms.onValidationErrors(actionkit.errors); 146 return; 147 } else if (resp.error) { 148 err = resp.error; 149 } 150 } catch (e) { 151 console && console.log(e); 152 } 153 donations.handleError({"message": err}); 154 }; 155 156 donations.vaultThenVerify = function(nonce, bin, form) { 157 let formData, vaultUrl; 158 159 disable_tokenized_fields(form); 160 formData = new FormData(form); 161 actUrl = form.action; 162 163 vaultUrl = actUrl.replace(/\/act\/?$/, "/vault_card/"); 164 formData.set("payment_method_nonce", nonce); 165 $.ajax({ 166 url: vaultUrl, 167 data: formData, 168 method: "POST", 169 type: "POST", 170 processData: false, 171 contentType: false, 172 success: function(data) { 173 donations.threeDSecureVerify(data.nonce, bin, form, true); 174 }, 175 error: donations.handleVaultError 176 }); 177 }; 178 179 donations.threeDSecureVerify = function(nonce, bin, form, alreadyVaulted) { 180 let threeDSecureParameters = donations.getThreeDSecureParameters(form), 181 threeDSecureInstance = donations.threeDSecureInstances[form.id]; 182 183 // These are all required: 184 threeDSecureParameters.onLookupComplete = donations.threeDSecureLookupComplete; 185 threeDSecureParameters.nonce = nonce; 186 threeDSecureParameters.bin = bin; 187 threeDSecureParameters.amount = donations.getAmount(form); 188 189 threeDSecureInstance.verifyCard(threeDSecureParameters, function (err, response) { 190 if (err) { 191 donations.handleError(err); 192 return; 193 } 194 donations.addHiddenInput(form, 'payment_method_nonce', response.nonce); 195 donations.addHiddenInput(form, 'card_prefix', response.details.bin); 196 if (alreadyVaulted) { 197 donations.addHiddenInput(form, 'already_vaulted', 1); 198 } 199 disable_tokenized_fields(form); 200 form.submit(); 201 }); 202 203 }; 204 205 function disable_tokenized_fields(form) { 206 // disable tokenized fields so they don't get submitted 207 toRemove = ["bank_account", "routing_number", 208 "account_type", "ownership_type"]; 209 toRemove.forEach(function(el) { 210 if (form[el]) { form[el].setAttribute('disabled', true); } 211 }); 212 } 213 214 donations.tokenizeAndSubmitCreditCard = function(hostedFieldsInstance, options) { 215 let form = options.form, 216 threeDSecureInstance = donations.threeDSecureInstances[form.id]; 217 if (!hostedFieldsInstance && 218 options.submitOnEmpty && 219 options.submitOnEmpty()) { 220 disable_tokenized_fields(form); 221 form.submit(); 222 return; 223 } 224 225 // if amount changed but not card, run 3DSecure verify on existing nonce 226 if ( 227 threeDSecureInstance && 228 options.isRecurringUpdate && 229 form.amountChanged() && 230 donations.hostedFieldsAreEmpty(hostedFieldsInstance) 231 ) { 232 donations.threeDSecureVerify(options.nonce, options.bin, form); 233 return; 234 } 235 236 hostedFieldsInstance.tokenize(function (tokenizeErr, payload) { 237 if (tokenizeErr) { 238 if (options.submitOnEmpty && options.submitOnEmpty() && 239 tokenizeErr.code == 'HOSTED_FIELDS_FIELDS_EMPTY') { 240 disable_tokenized_fields(form); 241 form.submit(); 242 } else { 243 donations.handleError(tokenizeErr); 244 } 245 return; 246 } 247 if (threeDSecureInstance) { 248 if (donations.shouldVaultFirst(options, hostedFieldsInstance)) { 249 donations.vaultThenVerify(payload.nonce, payload.details.bin, form); 250 } else { 251 donations.threeDSecureVerify(payload.nonce, payload.details.bin, form); 252 } 253 } else { 254 donations.addHiddenInput(form, 'payment_method_nonce', payload.nonce); 255 donations.addHiddenInput(form, 'card_prefix', payload.details.bin); 256 disable_tokenized_fields(form); 257 form.submit(); 258 } 259 }); 260 }; 261 262 donations.tokenizeAndSubmitACH = function(hostedFieldsInstance, options) {
263 var bankDetails = { 264 ownershipType: options.form['ownership_type'].value, 265 billingAddress: { 266 streetAddress: options.form['address1'].value, 267 extendedAddress: options.form['address2'] && options.form['address2'].value, 268 locality: options.form['city'].value, 269 region: options.form['state'].value, 270 postalCode: options.form['zip'].value, 271 } 272 }; 273 274 if (bankDetails.ownershipType === 'personal') { 275 bankDetails.firstName = options.form['first_name'].value; 276 bankDetails.lastName = options.form['last_name'].value; 277 } else { 278 bankDetails.businessName = options.form['business_name'].value; 279 } 280 281 mandate_text = $('#ak-mandate, .ak-mandate').first().text() 282 var tokenize_params = { mandateText: mandate_text }; 283 var ach_method = options.form["ach_method"] && options.form["ach_method"].value; 284 if (ach_method == 'bank_login') { 285 bankDetails.displayName = $('#ak-client-name, .ak-client-name').first().text(); 286 tokenize_params.bankLogin = bankDetails; 287 } else { 288 bankDetails.accountNumber = options.form['bank_account'].value; 289 bankDetails.routingNumber = options.form['routing_number'].value; 290 bankDetails.accountType = options.form['account_type'].value; 291 tokenize_params.bankDetails = bankDetails; 292 293 if (!bankDetails.accountNumber && 294 options.submitOnEmpty && 295 options.submitOnEmpty()) { 296 disable_tokenized_fields(options.form); 297 options.form.submit(); 298 return; 299 } 300 } 301 302 donations.usBankAccountInstance.tokenize(tokenize_params, 303 function (tokenizeErr, payload) { 304 if (tokenizeErr) { 305 if (ach_method == 'bank_login') { 306 actionkit.forms.onValidationErrors( 307 {'ach_method:login_canceled': actionkit.forms.errorMessage('ach_method:login_canceled')} 308 ); 309 } else { 310 message = 'There was an error submitting your bank information: ' + tokenizeErr; 311 donations.handleError(message); 312 actionkit.forms.onValidationErrors({'routing_number': message}); 313 } 314 return; 315 } 316 donations.addHiddenInput(options.form, 'payment_method_nonce', payload.nonce); 317 disable_tokenized_fields(options.form); 318 var submit = options.submit || options.form.querySelector('[type=submit]'); 319 $(submit).html("Processing, please wait..."); 320 options.form.submit(); 321 }); 322 }; 323 324 donations.tokenizeAndSubmit = function(hostedFieldsInstance, options) { 325 var payment_method = options.form["payment_method"] && options.form["payment_method"].value; 326 if (payment_method && payment_method == 'ach') { 327 return donations.tokenizeAndSubmitACH(hostedFieldsInstance, options); 328 } else { 329 return donations.tokenizeAndSubmitCreditCard(hostedFieldsInstance, options); 330 } 331 }; 332 333 donations.tokenizeOnSubmit = function(hostedFieldsInstance, options) { 334 options.form.addEventListener('submit', function(event) { 335 event.preventDefault(); 336 donations.tokenizeAndSubmit(hostedFieldsInstance, options); 337 }, false); 338 }; 339 340 function activateSubmit(hostedFieldsInstance, options) { 341 var submit = options.submit || options.form.querySelector('[type=submit]'); 342 submit.removeAttribute('disabled'); 343 donations.tokenizeOnSubmit(hostedFieldsInstance, options); 344 } 345 346 donations.initHostedFields = function(clientInstance, options) { 347 if ($.isEmptyObject(options.fields)) { 348 if (options.ach) { 349 donations.initUSBankAccount(clientInstance, 350 null, 351 options); 352 } else { 353 activateSubmit(null, options); 354 } 355 } else { 356 braintree.hostedFields.create({ 357 client: clientInstance, 358 fields: options.fields, 359 styles: options.styles 360 }, function(hostedFieldsErr, hostedFieldsInstance) { 361 if (hostedFieldsErr) { 362 donations.handleError(hostedFieldsErr); 363 return; 364 } 365 366 if (options.ach) { 367 donations.initUSBankAccount(clientInstance, 368 hostedFieldsInstance, 369 options); 370 } else { 371 activateSubmit(hostedFieldsInstance, options); 372 } 373 }); 374 } 375 }; 376 377 donations.initThreeDSecure = function(clientInstance, options) { 378 if (!options.use_3d_secure) { 379 return; 380 } 381 braintree.threeDSecure.create({ 382 version: 2, // Will use 3DS 2 whenever possible 383 client: clientInstance 384 }, function (threeDSecureErr, threeDSecureInstance) { 385 if (threeDSecureErr) { 386 donations.handleError({message: 'There was an error creating the 3DSecure instance.'}); 387 return; 388 } 389 390 donations.threeDSecureInstances[options.form.id] = threeDSecureInstance; 391 }); 392 }; 393 394 donations.initUSBankAccount = function(clientInstance, hostedFieldsInstance, options) { 395 braintree.usBankAccount.create({ 396 client: clientInstance 397 }, function (usBankAccountErr, usBankAccountInstance) { 398 if (usBankAccountErr) { 399 donations.handleError({message: 'There was an error creating the USBankAccount instance.'}); 400 return; 401 } 402 donations.usBankAccountInstance = usBankAccountInstance; 403 activateSubmit(hostedFieldsInstance, options); 404 }); 405 }; 406 407 donations.initClient = function(clientToken, options) { 408 if (! options.form) { 409 donations.handleError({message: 'Configuration error: need form.'}); 410 } 411 braintree.client.create({ 412 authorization: clientToken 413 }, function (clientErr, clientInstance) { 414 if (clientErr) { 415 donations.handleError(clientErr); 416 return; 417 } 418 donations.initDataCollector(clientInstance, options); 419 donations.initHostedFields(clientInstance, options); 420 donations.initThreeDSecure(clientInstance, options); 421 }); 422 if (options.isRecurringUpdate) { 423 donations.setAmountChanged(options.form); 424 } 425 }; 426 427}(actionkit.donations));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.