PageSourceSearch

https://act.npd.ca/resources/ak_braintree_vzero.js

js npd.ca collected 2026-09-24 21:54:39 UTC 16,485 bytes, 427 lines download raw bytes

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.