├── public ├── favicon.ico ├── images │ ├── stars_lg.png │ └── default-card.png ├── js │ └── creditoffers.js └── css │ └── style.css ├── helpers ├── index.js └── sanitize.js ├── .gitignore ├── viewmodels ├── index.js ├── preQualProduct.js └── product.js ├── validation ├── index.js ├── customValidators.js ├── stateCodes.js └── customerInfo.js ├── views ├── error.jade ├── includes │ ├── raw-json-modal.jade │ ├── login-modal.jade │ ├── customer-form.jade │ └── prefill-accept-modal.jade ├── offers.jade ├── layout.jade └── index.jade ├── package.json ├── creditoffers ├── index.js ├── users.js ├── prequalification.js ├── products.js └── client.js ├── config.js.sample ├── oauth └── index.js ├── bin └── www ├── app.js ├── routes ├── index.js └── offers.js ├── README.md ├── LICENSE.md └── credit-offers_notices.md /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/CreditOffers-API-reference-app/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/images/stars_lg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/CreditOffers-API-reference-app/HEAD/public/images/stars_lg.png -------------------------------------------------------------------------------- /public/images/default-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capitalone/CreditOffers-API-reference-app/HEAD/public/images/default-card.png -------------------------------------------------------------------------------- /helpers/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Capital One Services, LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and limitations under the License. 14 | 15 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | module.exports = { 20 | sanitize: require('./sanitize') 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore config files. Users should alter the sample. 2 | config.js 3 | 4 | # Thumbs 5 | Thumbs.db 6 | 7 | # Logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # node-waf configuration 27 | .lock-wscript 28 | 29 | # Compiled binary addons (http://nodejs.org/api/addons.html) 30 | build/Release 31 | 32 | # Dependency directories 33 | node_modules 34 | jspm_packages 35 | 36 | # Optional npm cache directory 37 | .npm 38 | 39 | # Optional REPL history 40 | .node_repl_history 41 | 42 | # Optional IDEA file 43 | .idea 44 | 45 | # Package-lock JSON 46 | package-lock.json 47 | -------------------------------------------------------------------------------- /viewmodels/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Capital One Services, LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and limitations under the License. 14 | 15 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | var product = require('./product') 20 | var preQualProduct = require('./preQualProduct') 21 | 22 | module.exports = { 23 | product: product, 24 | preQualProduct: preQualProduct 25 | } 26 | -------------------------------------------------------------------------------- /validation/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Capital One Services, LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and limitations under the License. 14 | 15 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | module.exports = { 20 | models: { 21 | customerInfo: require('./customerInfo') 22 | }, 23 | customValidators: require('./customValidators'), 24 | stateCodes: require('./stateCodes') 25 | } 26 | -------------------------------------------------------------------------------- /views/error.jade: -------------------------------------------------------------------------------- 1 | //- Copyright 2017 Capital One Services, LLC 2 | //- 3 | //- Licensed under the Apache License, Version 2.0 (the "License"); 4 | //- you may not use this file except in compliance with the License. 5 | //- You may obtain a copy of the License at 6 | //- 7 | //- http://www.apache.org/licenses/LICENSE-2.0 8 | //- 9 | //- Unless required by applicable law or agreed to in writing, software 10 | //- distributed under the License is distributed on an "AS IS" BASIS, 11 | //- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | //- See the License for the specific language governing permissions and limitations under the License. 13 | //- 14 | //- SPDX-Copyright: Copyright (c) Capital One Services, LLC 15 | //- SPDX-License-Identifier: Apache-2.0 16 | 17 | extends ./layout.jade 18 | 19 | block content 20 | div.container 21 | h1=message 22 | h2=error.status 23 | if error.stack 24 | pre= error.stack 25 | -------------------------------------------------------------------------------- /validation/customValidators.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Capital One Services, LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and limitations under the License. 14 | 15 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | var stateCodes = require('./stateCodes') 20 | var _ = require('lodash') 21 | 22 | module.exports = { 23 | isUSState: function (value) { 24 | return _.includes(stateCodes, value) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "credit-offers", 3 | "version": "2.0.0", 4 | "main": "./bin/www", 5 | "licenses": [ 6 | { 7 | "type": "Apache-2.0", 8 | "url": "http://www.apache.org/licenses/LICENSE-2.0" 9 | } 10 | ], 11 | "dependencies": { 12 | "bcrypt": "^1.0.3", 13 | "body-parser": "1.13.3", 14 | "bootstrap": "3.3.7", 15 | "cookie-parser": "1.3.5", 16 | "cookie-session": "^2.0.0-beta.3", 17 | "csurf": "1.8.3", 18 | "debug": "2.2.0", 19 | "ejs": "~2.5.5", 20 | "express": "4.13.4", 21 | "express-validator": "2.21.0", 22 | "font-awesome": "4.7.0", 23 | "helmet": "1.1.0", 24 | "html-entities": "1.2.0", 25 | "jade": "1.11.0", 26 | "lodash": "4.1.0", 27 | "moment": "2.16.0", 28 | "morgan": "1.6.1", 29 | "request": "2.69.0", 30 | "sanitize-html": "1.13.0", 31 | "serve-favicon": "2.3.0" 32 | }, 33 | "engines": { 34 | "node": ">=4.0.0" 35 | }, 36 | "repository": { 37 | "type": "git", 38 | "url": "https://github.com/capitalone/CreditOffers-API-reference-app" 39 | }, 40 | "scripts": { 41 | "start": "node ./bin/www" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /views/includes/raw-json-modal.jade: -------------------------------------------------------------------------------- 1 | //- Copyright 2017 Capital One Services, LLC 2 | //- 3 | //- Licensed under the Apache License, Version 2.0 (the "License"); 4 | //- you may not use this file except in compliance with the License. 5 | //- You may obtain a copy of the License at 6 | //- 7 | //- http://www.apache.org/licenses/LICENSE-2.0 8 | //- 9 | //- Unless required by applicable law or agreed to in writing, software 10 | //- distributed under the License is distributed on an "AS IS" BASIS, 11 | //- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | //- See the License for the specific language governing permissions and limitations under the License. 13 | //- 14 | //- SPDX-Copyright: Copyright (c) Capital One Services, LLC 15 | //- SPDX-License-Identifier: Apache-2.0 16 | 17 | div#card-json.modal.fade(tabindex='-1', role='dialog') 18 | div.modal-dialog 19 | div.modal-content 20 | div.modal-header 21 | button.close(type='button', data-dismiss='modal', aria-label='close') 22 | span(aria-hidden='true') × 23 | h4.modal-title Raw Card JSON 24 | div.modal-body 25 | pre.raw-json 26 | -------------------------------------------------------------------------------- /creditoffers/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Capital One Services, LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and limitations under the License. 14 | 15 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | var ApiClient = require('./client') 20 | var Prequalification = require('./prequalification') 21 | var Products = require('./products') 22 | 23 | /** 24 | * The Credit Offers API client 25 | * @param options {object} Client options (host url, API version) 26 | * @param oauth {object} The OAuth handler 27 | */ 28 | function CreditOffers (options, oauth) { 29 | if (!this instanceof CreditOffers) { 30 | return new CreditOffers(options, oauth) 31 | } 32 | 33 | var client = new ApiClient(options, oauth) 34 | 35 | this.prequalification = new Prequalification(client) 36 | this.products = new Products(client) 37 | } 38 | 39 | module.exports = CreditOffers 40 | -------------------------------------------------------------------------------- /config.js.sample: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Capital One Services, LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and limitations under the License. 14 | */ 15 | 16 | /* 17 | * Copy this file to config.js and enter your configuration info. 18 | * config.js should not be under version control since it contains your 19 | * client_id and client_secret. 20 | */ 21 | var creditOffersHost = 'https://api-sandbox.capitalone.com' 22 | 23 | module.exports = { 24 | // Settings for connecting to the Credit Offers API 25 | creditOffers: { 26 | client: { 27 | // The URL of the Credit Offers environment you are connecting to. 28 | url: creditOffersHost, 29 | apiVersion: 3 30 | }, 31 | oauth: { 32 | tokenURL: creditOffersHost + '/oauth2/token', 33 | // The clientId and clientSecret you received when registering your app. 34 | clientID: 'COF_CLIENT_ID', 35 | clientSecret: 'COF_CLIENT_SECRET' 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /validation/stateCodes.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Capital One Services, LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and limitations under the License. 14 | 15 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | module.exports = [ 20 | 'AL', 21 | 'AK', 22 | 'AS', 23 | 'AZ', 24 | 'AR', 25 | 'CA', 26 | 'CO', 27 | 'CT', 28 | 'DE', 29 | 'DC', 30 | 'FM', 31 | 'FL', 32 | 'GA', 33 | 'GU', 34 | 'HI', 35 | 'ID', 36 | 'IL', 37 | 'IN', 38 | 'IA', 39 | 'KS', 40 | 'KY', 41 | 'LA', 42 | 'ME', 43 | 'MH', 44 | 'MD', 45 | 'MA', 46 | 'MI', 47 | 'MN', 48 | 'MS', 49 | 'MO', 50 | 'MT', 51 | 'NE', 52 | 'NV', 53 | 'NH', 54 | 'NJ', 55 | 'NM', 56 | 'NY', 57 | 'NC', 58 | 'ND', 59 | 'MP', 60 | 'OH', 61 | 'OK', 62 | 'OR', 63 | 'PW', 64 | 'PA', 65 | 'PR', 66 | 'RI', 67 | 'SC', 68 | 'SD', 69 | 'TN', 70 | 'TX', 71 | 'UT', 72 | 'VT', 73 | 'VI', 74 | 'VA', 75 | 'WA', 76 | 'WV', 77 | 'WI', 78 | 'WY', 79 | 'DC', 80 | 'AA', 81 | 'AE', 82 | 'AP' 83 | ] 84 | -------------------------------------------------------------------------------- /helpers/sanitize.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Capital One Services, LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and limitations under the License. 14 | 15 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | var sanitizeHtml = require('sanitize-html') 20 | var Entities = require('html-entities').XmlEntities; 21 | 22 | // For decoding xml entities in responses from the Capital One API 23 | entities = new Entities(); 24 | 25 | // HTML tags to be kept in responses from the Capital One API 26 | var allowedTags = [ 27 | 'sup' // The registered trademark symbol may be returned in a tag for proper display 28 | ] 29 | 30 | /** 31 | * The Capital One API may return values to be displayed as HTML. 32 | * This function decodes and sanitizes those values 33 | */ 34 | exports.sanitizeHtmlForDisplay = function sanitizeHtmlForDisplay (value) { 35 | if (!value) return value 36 | 37 | // Decode entities first 38 | var decodedValue = entities.decode(value) 39 | 40 | // Then sanitize 41 | return sanitizeHtml(decodedValue, { 42 | allowedTags: allowedTags 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /creditoffers/users.js: -------------------------------------------------------------------------------- 1 | module.exports = [{ 2 | username: "rhubbard", 3 | password: "XXX", 4 | details: { 5 | "firstName": "Ray", 6 | "middleName": "G", 7 | "lastName": "Hubbard", 8 | "nameSuffix": "Jr.", 9 | "dateOfBirth": "1946-11-13", 10 | "addresses": [{ 11 | "addressLine1": "1230 Duck Fuss Lane", 12 | "addressLine2": "Apt 15X", 13 | "addressLine3": "Room 192", 14 | "addressLine4": "Bed 16", 15 | "city": "Beaumont", 16 | "stateCode": "TX", 17 | "postalCode": "77701", 18 | "addressType": "PhysicalPrimary" 19 | 20 | }], 21 | "telephoneNumbers": [{ 22 | "phoneNumberType": "Home", 23 | "telephoneNumber": "2025550110" 24 | }], 25 | "emailAddresses": [{ 26 | "emailAddress": "ray@wyliehubbard.com" 27 | }], 28 | "annualIncome": 75000, 29 | "bankAccountSummary": "CheckingOnly" 30 | } 31 | }, 32 | { 33 | username: "jsmith", 34 | password: "XXX", 35 | details: { 36 | "firstName": "Jane", 37 | "lastName": "Smith", 38 | "dateOfBirth": "1980-05-15", 39 | "addresses": [{ 40 | "addressLine1": "3828 Piermont Dr", 41 | "addressLine2": "Unit A", 42 | "city": "Albuquerque", 43 | "stateCode": "NM", 44 | "postalCode": "87111", 45 | "addressType": "Mailing" 46 | 47 | }], 48 | "emailAddresses": [{ 49 | "emailAddress": "jane@example.com" 50 | }], 51 | "telephoneNumbers": [{ 52 | "phoneNumberType": "Work", 53 | "telephoneNumber": "2025550246" 54 | }, { 55 | "phoneNumberType": "Mobile", 56 | "telephoneNumber": "2025551357" 57 | }], 58 | "annualIncome": 100000, 59 | "bankAccountSummary": "Neither" 60 | } 61 | } 62 | ] 63 | -------------------------------------------------------------------------------- /views/includes/login-modal.jade: -------------------------------------------------------------------------------- 1 | //- Copyright 2017 Capital One Services, LLC 2 | //- 3 | //- Licensed under the Apache License, Version 2.0 (the "License"); 4 | //- you may not use this file except in compliance with the License. 5 | //- You may obtain a copy of the License at 6 | //- 7 | //- http://www.apache.org/licenses/LICENSE-2.0 8 | //- 9 | //- Unless required by applicable law or agreed to in writing, software 10 | //- distributed under the License is distributed on an "AS IS" BASIS, 11 | //- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | //- See the License for the specific language governing permissions and limitations under the License. 13 | //- 14 | //- SPDX-Copyright: Copyright (c) Capital One Services, LLC 15 | //- SPDX-License-Identifier: Apache-2.0 16 | - sandboxEnv = this.process.env.C1_ENV == 'sandbox' 17 | div#login.modal.fade(tabindex='-1', role='dialog') 18 | div.modal-dialog 19 | div.modal-content 20 | form(action='/login', method='POST', name="login") 21 | input(type="hidden" name="_csrf" value="#{csrfToken}") 22 | div.modal-header 23 | button.close(type='button', data-dismiss='modal', aria-label='close') 24 | span(aria-hidden='true') × 25 | h4.modal-title Sign In 26 | div.modal-body 27 | div.form-group(class={required: truth}) 28 | label(for='username')= 'Username' 29 | input.form-control(type='text', name='username', required='true', value=sandboxEnv ? 'jsmith' : '') 30 | div.form-group(class={required: truth}) 31 | label(for='password')= 'Password' 32 | input.form-control(type='password', name='password', required='true', value=sandboxEnv ? 'Reference@123' : '') 33 | div.modal-footer 34 | button.btn.btn-default(type='button', data-dismiss='modal') 35 | | Close 36 | button.btn.btn-primary(type='submit') 37 | | Sign In 38 | -------------------------------------------------------------------------------- /viewmodels/preQualProduct.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Capital One Services, LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and limitations under the License. 14 | 15 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | /** @module Defines a consistent model for displaying pre-qualification products, which contain less info than normal products **/ 20 | 21 | var _ = require('lodash') 22 | var sanitize = require('../helpers').sanitize.sanitizeHtmlForDisplay 23 | 24 | module.exports = function preQualProduct (apiProduct) { 25 | var viewModel = _.pick(apiProduct, [ 26 | 'productId', 27 | 'code', 28 | 'priority', 29 | 'tier', 30 | 'additionalInformationUrl', 31 | 'introPurchaseApr', 32 | 'purchaseApr', 33 | 'introBalanceTransferApr', 34 | 'annualMembershipFee', 35 | 'creditRating' 36 | ]) 37 | 38 | viewModel.productDisplayName = sanitize(apiProduct.productDisplayName || '???') 39 | viewModel.images = { 40 | cardName: _.find(apiProduct.images, { imageType: 'CardName' }), 41 | cardArt: _.find(apiProduct.images, { imageType: 'CardArt' }), 42 | banner: _.find(apiProduct.images, { imageType: 'Banner' }) 43 | } 44 | 45 | // Normalize to the keys used by the products API 46 | viewModel.applyNowLink = apiProduct.applyNowLink 47 | 48 | var marketingCopy = _.map(apiProduct.marketingCopy || [], sanitize) 49 | viewModel.mainMarketingCopy = _.take(marketingCopy, 2) 50 | viewModel.extraMarketingCopy = _.drop(marketingCopy, 2) 51 | 52 | // Store the raw JSON value to display as reference in the UI 53 | viewModel.rawJSON = JSON.stringify(apiProduct) 54 | 55 | return viewModel 56 | } 57 | -------------------------------------------------------------------------------- /oauth/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Capital One Services, LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and limitations under the License. 14 | 15 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | var request = require('request') 20 | var debug = require('debug')('credit-offers:oauth') 21 | 22 | /** 23 | * Provides functions for oauth token management 24 | */ 25 | module.exports = function (options) { 26 | debug('Initializing oauth module', options) 27 | var clientID = options.clientID 28 | var clientSecret = options.clientSecret 29 | var tokenURL = options.tokenURL 30 | 31 | /** 32 | * Get a new access token using client credentials 33 | */ 34 | function withToken (callback) { 35 | debug('Getting a new access token') 36 | var reqOptions = { 37 | url: tokenURL, 38 | method: 'POST', 39 | form: { 40 | 'client_id': clientID, 41 | 'client_secret': clientSecret, 42 | 'grant_type': 'client_credentials' 43 | } 44 | } 45 | 46 | debug('Sending OAuth request', reqOptions) 47 | 48 | request(reqOptions, function (error, response, body) { 49 | if (error) { 50 | return callback(error) 51 | } 52 | if (response.status >= 400) { 53 | return callback(new Error('OAuth access token exchange failed')) 54 | } 55 | 56 | if (!body) { 57 | var missingTokenError = new Error('OAuth response body did not include an access token') 58 | console.error(missingTokenError) 59 | return callback(missingTokenError) 60 | } 61 | 62 | debug('Received token response', body) 63 | try { 64 | var newToken = JSON.parse(body) 65 | } catch (parseError) { 66 | return callback(parseError) 67 | } 68 | debug('Parsed new token', newToken) 69 | 70 | return callback(null, newToken) 71 | }) 72 | } 73 | 74 | return { 75 | withToken: withToken 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /public/js/creditoffers.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | if(!$('.signed-in').length) { 3 | $('.apply-btn').addClass('disabled').parent().attr('title', 'Apply Now Link Disabled') 4 | } 5 | 6 | $('.signed-in .apply-btn').click(function(evt) { 7 | let href = $(this).attr('href') 8 | $('form[name="prefill-acceptance"]').data('link', href) 9 | 10 | if (href.indexOf('/mpid/') > -1) { 11 | $('#prefill-acceptance button[type="submit"]').removeClass('hidden') 12 | $('#prefill-acceptance .modal-body .applicant-key-text').remove() 13 | $('#prefill-acceptance .deny-prefill').remove() 14 | } else { 15 | $('a.deny-prefill').attr('href', href) 16 | } 17 | 18 | $('#prefill-acceptance').modal().on('hidden.bs.modal', () => { 19 | $('#prefill-acceptance .text-danger').addClass('hidden') 20 | }) 21 | 22 | evt.preventDefault() 23 | }) 24 | 25 | //Prefill append applicantDetailsKey to link url 26 | $('form[name="prefill-acceptance"]').submit(function(evt) { 27 | let applicantDetailsKey 28 | let link = $(this).data('link') 29 | let $spinner = $(this).find('.spinner') 30 | $spinner.removeClass('hidden') 31 | $.ajax({ 32 | type: "POST", 33 | url: this.action, 34 | data: $(this).serialize(), // serializes the form's elements. 35 | xhrFields: { 36 | withCredentials: true 37 | }, 38 | success: function(data) { 39 | applicantDetailsKey = data.applicantDetailsKey 40 | link += encodeURIComponent(`?applicantDetailsKey=${applicantDetailsKey}`) 41 | }, 42 | error: function(err) { 43 | $('#prefill-acceptance .text-danger.hidden').removeClass('hidden') 44 | }, 45 | complete: function() { 46 | if (link.indexOf('/mpid/') > -1) { 47 | $('#prefill-acceptance button[type="submit"]').addClass('hidden') 48 | $('#prefill-acceptance .modal-body').append( 49 | ` 50 |
You have been generated an Applicant Details Key:
${applicantDetailsKey}
51 |

In production, this would be appended to the end of the Apply Now link.

52 | ` 53 | ) 54 | } else { 55 | let win = window.open(link, '_blank') 56 | if (win) { 57 | win.focus(); 58 | } else { 59 | alert('Please allow popups for this website'); 60 | } 61 | } 62 | $spinner.addClass('hidden') 63 | } 64 | }); 65 | evt.preventDefault(); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /creditoffers/prequalification.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Capital One Services, LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and limitations under the License. 14 | 15 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | var request = require('request') 20 | var debug = require('debug')('credit-offers:api-client') 21 | var moment = require('moment') 22 | var _ = require('lodash') 23 | 24 | /** 25 | * Contains all functions for interacting with the credit offer prequalification API 26 | * @param {object} client The API client 27 | */ 28 | function Prequalification (client) { 29 | if (!this instanceof Prequalification) { 30 | return new Prequalification(client) 31 | } 32 | 33 | this.client = client 34 | } 35 | module.exports = Prequalification 36 | 37 | /** 38 | * Initiate a pre-qualification check for a customer. 39 | * The response will include products for which the customer may be 40 | * pre-qualified. If they do not qualify, at least one product will still 41 | * be returned. 42 | * @param customerInfo {object} Represents the customer info to pass to the API 43 | */ 44 | Prequalification.prototype.create = function create (customerInfo, callback) { 45 | this.client.sendRequest({ 46 | url: '/credit-offers/prequalifications', 47 | useOAuth: true, 48 | method: 'POST', 49 | body: customerInfo 50 | }, callback) 51 | } 52 | 53 | /** 54 | * Acknowledges that a set of pre-qualification results have been displayed 55 | * to the consumer. 56 | * @param prequalificationId {string} The unique identifier of the prequalification request to acknowledge 57 | */ 58 | Prequalification.prototype.acknowledge = function acknowledge (prequalificationId, callback) { 59 | if (!prequalificationId) { return callback(new Error('Unable to acknowledge prequalification without an ID')) } 60 | 61 | this.client.sendRequest({ 62 | url: '/credit-offers/prequalifications/' + encodeURIComponent(prequalificationId), 63 | useOAuth: true, 64 | method: 'POST', 65 | body: { 'hasBeenAcknowledged': true } 66 | }, callback) 67 | } 68 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* 3 | Copyright 2017 Capital One Services, LLC 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and limitations under the License. 15 | */ 16 | 17 | /** 18 | * Module dependencies. 19 | */ 20 | 21 | var app = require('../app'); 22 | var debug = require('debug')('credit-offers:server'); 23 | var http = require('http'); 24 | 25 | /** 26 | * Get port from environment and store in Express. 27 | */ 28 | 29 | var port = normalizePort(process.env.PORT || '3000'); 30 | app.set('port', port); 31 | 32 | /** 33 | * Create HTTP server. 34 | */ 35 | 36 | var server = http.createServer(app); 37 | 38 | /** 39 | * Listen on provided port, on all network interfaces. 40 | */ 41 | 42 | server.listen(port); 43 | server.on('error', onError); 44 | server.on('listening', onListening); 45 | 46 | /** 47 | * Normalize a port into a number, string, or false. 48 | */ 49 | 50 | function normalizePort(val) { 51 | var port = parseInt(val, 10); 52 | 53 | if (isNaN(port)) { 54 | // named pipe 55 | return val; 56 | } 57 | 58 | if (port >= 0) { 59 | // port number 60 | return port; 61 | } 62 | 63 | return false; 64 | } 65 | 66 | /** 67 | * Event listener for HTTP server "error" event. 68 | */ 69 | 70 | function onError(error) { 71 | if (error.syscall !== 'listen') { 72 | throw error; 73 | } 74 | 75 | var bind = typeof port === 'string' 76 | ? 'Pipe ' + port 77 | : 'Port ' + port; 78 | 79 | // handle specific listen errors with friendly messages 80 | switch (error.code) { 81 | case 'EACCES': 82 | console.error(bind + ' requires elevated privileges'); 83 | process.exit(1); 84 | break; 85 | case 'EADDRINUSE': 86 | console.error(bind + ' is already in use'); 87 | process.exit(1); 88 | break; 89 | default: 90 | throw error; 91 | } 92 | } 93 | 94 | /** 95 | * Event listener for HTTP server "listening" event. 96 | */ 97 | 98 | function onListening() { 99 | var addr = server.address(); 100 | var bind = typeof addr === 'string' 101 | ? 'pipe ' + addr 102 | : 'port ' + addr.port; 103 | debug('Listening on ' + bind); 104 | } 105 | -------------------------------------------------------------------------------- /viewmodels/product.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Capital One Services, LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and limitations under the License. 14 | 15 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | /** @module Defines a consistent model for displaying products from the API **/ 20 | 21 | var _ = require('lodash') 22 | var sanitize = require('../helpers').sanitize.sanitizeHtmlForDisplay 23 | 24 | module.exports = function product(apiProduct) { 25 | var viewModel = _.pick(apiProduct, [ 26 | 'productId', 27 | 'activeFrom', 28 | 'activeTo', 29 | 'publishedDate', 30 | 'applyNowLink', 31 | 'productType', 32 | 'brandName', 33 | 'categoryTags', 34 | 'processingNetwork', 35 | 'creditRating', 36 | 'rewards', 37 | 'balanceTransfer', 38 | 'introBalanceTransferApr', 39 | 'introBalanceTransferAprPeriod', 40 | 'balanceTransferApr', 41 | 'balanceTransferGracePeriod', 42 | 'introPurchaseApr', 43 | 'introPurchaseAprPeriod', 44 | 'purchaseApr', 45 | 'purchaseGracePeriod', 46 | 'annualMembershipFee', 47 | 'foreignTransactionFee', 48 | 'fraudCoverage', 49 | 'latePaymentFee', 50 | 'penaltyApr', 51 | 'cashAdvanceFee', 52 | 'cashAdvanceApr', 53 | 'cashAdvanceGracePeriod', 54 | 'generalDescription', 55 | 'promotionalCopy', 56 | 'overLimitFee', 57 | 'minimumDeposit', 58 | 'additionalMarketingCopy', 59 | 'productCount', 60 | 'productMetrics' 61 | ]) 62 | 63 | viewModel.productDisplayName = sanitize(apiProduct.productDisplayName || '???') 64 | viewModel.images = { 65 | cardName: _.find(apiProduct.images, { 66 | imageType: 'CardName' 67 | }), 68 | cardArt: _.find(apiProduct.images, { 69 | imageType: 'CardArt' 70 | }), 71 | banner: _.find(apiProduct.images, { 72 | imageType: 'Banner' 73 | }) 74 | } 75 | 76 | var marketingCopy = _.map(apiProduct.marketingCopy || [], sanitize) 77 | viewModel.mainMarketingCopy = _.take(marketingCopy, 2) 78 | viewModel.extraMarketingCopy = _.drop(marketingCopy, 2) 79 | 80 | // Store the raw JSON value to display as reference in the UI 81 | viewModel.rawJSON = JSON.stringify(apiProduct) 82 | 83 | return viewModel 84 | } 85 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Capital One Services, LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and limitations under the License. 14 | 15 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | var express = require('express') 20 | var path = require('path') 21 | var favicon = require('serve-favicon') 22 | var logger = require('morgan') 23 | var cookieParser = require('cookie-parser') 24 | var cookieSession = require('cookie-session') 25 | var bodyParser = require('body-parser') 26 | var expressValidator = require('express-validator') 27 | var helmet = require('helmet') 28 | var csrf = require('csurf') 29 | 30 | var validation = require('./validation') 31 | 32 | var CreditOffers = require('./creditoffers') 33 | var oauth = require('./oauth') 34 | var index = require('./routes/index') 35 | var offers = require('./routes/offers') 36 | 37 | var app = express() 38 | var config = require('./config') 39 | 40 | // view engine setup 41 | app.set('views', path.join(__dirname, 'views')) 42 | app.set('view engine', 'jade') 43 | 44 | // uncomment after placing your favicon in /public 45 | app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))) 46 | app.use(logger('dev')) 47 | app.use(bodyParser.json()) 48 | app.use(bodyParser.urlencoded({ 49 | extended: false 50 | })) 51 | app.use(expressValidator({ 52 | customValidators: validation.customValidators 53 | })) 54 | app.use(cookieParser()) 55 | app.use(cookieSession({ 56 | name: 'session', 57 | secret: "b34ejefAt7a3eme5e7paCrEgatadEqEs", 58 | 59 | // Cookie Options 60 | maxAge: 24 * 60 * 60 * 1000 // 24 hours 61 | })) 62 | app.use(express.static(path.join(__dirname, 'public'))) 63 | // Include the bootstrap package 64 | app.use('/css', express.static(path.join(__dirname, 'node_modules/bootstrap/dist/css'))) 65 | app.use('/js', express.static(path.join(__dirname, 'node_modules/bootstrap/dist/js'))) 66 | // Include font awesome 67 | app.use('/css', express.static(path.join(__dirname, 'node_modules/font-awesome/css'))) 68 | app.use('/fonts', express.static(path.join(__dirname, 'node_modules/font-awesome/fonts'))) 69 | 70 | app.use(helmet()) 71 | 72 | // Initialize the routing 73 | var client = new CreditOffers(config.creditOffers.client, oauth(config.creditOffers.oauth)) 74 | app.use('/', index(client)) 75 | app.use('/offers', offers(client)) 76 | 77 | // catch 404 and forward to error handler 78 | app.use(function(req, res, next) { 79 | var err = new Error('Not Found') 80 | err.status = 404 81 | next(err) 82 | }) 83 | 84 | // error handlers 85 | 86 | // development error handler 87 | // will print stacktrace 88 | if (app.get('env') === 'development') { 89 | app.use(function(err, req, res, next) { 90 | res.status(err.status || 500) 91 | res.render('error', { 92 | message: err.message, 93 | error: err 94 | }) 95 | }) 96 | } 97 | 98 | // production error handler 99 | // no stacktraces leaked to user 100 | app.use(function(err, req, res, next) { 101 | res.status(err.status || 500) 102 | res.render('error', { 103 | message: err.message, 104 | error: {} 105 | }) 106 | }) 107 | 108 | module.exports = app 109 | -------------------------------------------------------------------------------- /views/includes/customer-form.jade: -------------------------------------------------------------------------------- 1 | //- Copyright 2017 Capital One Services, LLC 2 | //- 3 | //- Licensed under the Apache License, Version 2.0 (the "License"); 4 | //- you may not use this file except in compliance with the License. 5 | //- You may obtain a copy of the License at 6 | //- 7 | //- http://www.apache.org/licenses/LICENSE-2.0 8 | //- 9 | //- Unless required by applicable law or agreed to in writing, software 10 | //- distributed under the License is distributed on an "AS IS" BASIS, 11 | //- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | //- See the License for the specific language governing permissions and limitations under the License. 13 | //- 14 | //- SPDX-Copyright: Copyright (c) Capital One Services, LLC 15 | //- SPDX-License-Identifier: Apache-2.0 16 | 17 | //- A re-usable bootstrap text input 18 | - stateCodes = typeof stateCodes != 'undefined' ? stateCodes : [] 19 | - bankAccountSummaryOptions = typeof bankAccountSummaryOptions != 'undefined' ? bankAccountSummaryOptions : [] 20 | - user = typeof user != 'undefined' ? user : { address : {}} 21 | - var _default 22 | 23 | mixin textinput(name, label, required) 24 | div.form-group(class={required: required}) 25 | label(for=name)= label 26 | input.form-control(type='text', name=name, placeholder=attributes.placeholder, required=required || false, value=attributes.default) 27 | 28 | mixin select(name, label, required) 29 | div.form-group(class={required: required}) 30 | label(for=name)= label 31 | select.form-control(name=name) 32 | block 33 | 34 | mixin stateOptions() 35 | each stateCode in stateCodes 36 | if stateCode == user.address.stateCode 37 | option(value=stateCode, selected='selected')= stateCode 38 | else 39 | option(value=stateCode)= stateCode 40 | 41 | mixin bankAccountSummary() 42 | if bankAccountSummaryOptions.length 43 | +select('bankAccountSummary', 'Type of Bank Accounts') 44 | each option in bankAccountSummaryOptions 45 | - let value = option.value 46 | - let key = option.key 47 | if key == user.bankAccountSummary || (option.default && typeof _default != 'undefined') 48 | - _default = option 49 | option(value=value, selected='selected')= key 50 | else 51 | option(value=value)= key 52 | 53 | +textinput('firstName', 'First Name', true)(default=user.firstName) 54 | +textinput('middleName', 'Middle Name') 55 | +textinput('lastName', 'Last Name', true)(default=user.lastName) 56 | +textinput('nameSuffix', 'Suffix')(placeholder='E.g. Jr, Sr') 57 | +textinput('addressLine1', 'Address Line 1', true)(default=user.address.addressLine1) 58 | +textinput('addressLine2', 'Address Line 2') 59 | +textinput('addressLine3', 'Address Line 3') 60 | +textinput('addressLine4', 'Address Line 4') 61 | +textinput('city', 'City', true)(default=user.address.city) 62 | +select('stateCode', 'State', true) 63 | +stateOptions() 64 | +textinput('postalCode', 'Zip Code', true)(default=user.address.postalCode) 65 | +select('addressType', 'Address Type') 66 | option(value='') 67 | option(value='Home') Home 68 | option(value='Business') Business 69 | +textinput('taxId', 'Last Four Digits of SSN', true) 70 | +textinput('dateOfBirth', 'Birth Date', false)(placeholder='YYYY-MM-DD')(default=user.dateOfBirth) 71 | 72 | div.form-note The following information is optional. However, it can help us show you offers that might match your needs and circumstances better. 73 | +textinput('emailAddress', 'Email Address')(default=user.emailAddress) 74 | +select('primaryBenefit', 'Most Desired Credit Card Benefit') 75 | option(value='NotSure') Not sure 76 | option(value='LowInterest') Low Interest Rate 77 | option(value='TravelRewards') Travel Rewards 78 | option(value='CashBack') Cash Back Rewards 79 | +select('selfAssessedCreditRating', 'Credit Rating') 80 | option(value='Excellent') Excellent 81 | option(value='Average') Average 82 | option(value='Rebuilding') Rebuilding 83 | +textinput('annualIncome', 'Annual Income (dollars)')(default=user.annualIncome) 84 | +bankAccountSummary() 85 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Capital One Services, LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and limitations under the License. 14 | 15 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | var express = require('express') 20 | var csrf = require('csurf') 21 | var _ = require('lodash') 22 | var productViewModel = require('../viewmodels').product 23 | 24 | module.exports = function(client) { 25 | var csrfProtection = csrf({ 26 | cookie: true 27 | }) 28 | var router = express.Router() 29 | 30 | // The supported card types 31 | var allType = { 32 | name: 'all', 33 | display: 'All Cards' 34 | }, 35 | cardTypes = [ 36 | allType, 37 | { 38 | name: 'consumer', 39 | display: 'Consumer Cards' 40 | }, 41 | { 42 | name: 'business', 43 | display: 'Business Cards' 44 | } 45 | ] 46 | 47 | // How many products to pull at a time 48 | var productCount = 10 49 | 50 | /* GET home page. */ 51 | router.get('/', csrfProtection, function(req, res, next) { 52 | var requestedCardType = _.find(cardTypes, { 53 | name: req.query.cardType 54 | }) 55 | 56 | if (!requestedCardType) { 57 | res.redirect('/?cardType=' + cardTypes[0].name) 58 | return 59 | } 60 | 61 | var onComplete = function(err, data) { 62 | if (err) { 63 | return next(err) 64 | } 65 | cards = _.map(_.get(data, 'products', []), productViewModel) 66 | res.render('index', { 67 | csrfToken: req.csrfToken(), 68 | title: 'Credit Offers Reference App', 69 | currentCardType: requestedCardType.name, 70 | cardTypes: cardTypes, 71 | cards: cards, 72 | user: req.session.user || { address: {}}, 73 | stateCodes: require('../validation/stateCodes'), 74 | bankAccountSummaryOptions: [ 75 | { value: 'CheckingAndSavings', key: 'Checking & Savings' }, 76 | { value: 'CheckingOnly', key: 'Checking Only' }, 77 | { value: 'SavingsOnly', key: 'Savings Only' }, 78 | { value: 'Neither', key: 'Neither', default: true }] 79 | }) 80 | } 81 | 82 | if (requestedCardType === allType) { 83 | client.products.getAllCards({ 84 | limit: productCount 85 | }, onComplete) 86 | } else { 87 | client.products.getCards(requestedCardType.name, { 88 | limit: productCount 89 | }, onComplete) 90 | } 91 | }) 92 | 93 | router.post('/login', csrfProtection, function(req, res, next) { 94 | let user = require('../creditoffers/users').find((user) => { return user.username == req.body.username }) || {} 95 | require('bcrypt').compare(req.body.password, user.password, (err, matched) => { 96 | if (matched) { 97 | user = user.details || {} 98 | user.address = user.addresses[0] || {} 99 | user.emailAddress = (user.emailAddresses[0] || {}).emailAddress 100 | req.session.user = user 101 | } 102 | res.redirect('/') 103 | }) 104 | }) 105 | 106 | router.get('/logout', csrfProtection, function(req, res, next) { 107 | req.session = null 108 | res.redirect('/') 109 | }) 110 | 111 | router.post('/prefill-acceptance', csrfProtection, function(req, res, next) { 112 | client.products.postPrefillAcceptance(req.session.user, (err, response) => { 113 | if (err || !response.applicantDetailsKey) res.status(400).send() 114 | else res.json(response) 115 | }) 116 | }) 117 | 118 | return router 119 | } 120 | -------------------------------------------------------------------------------- /creditoffers/products.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Capital One Services, LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and limitations under the License. 14 | 15 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | var request = require('request') 20 | var debug = require('debug')('credit-offers:api-client') 21 | var _ = require('lodash') 22 | 23 | /** 24 | * Contains all functions for interacting with the credit offer product listings API 25 | * @param {object} client The API client 26 | */ 27 | function Products(client) { 28 | if (!this instanceof Products) { 29 | return new Products(client) 30 | } 31 | 32 | this.client = client 33 | } 34 | module.exports = Products 35 | 36 | /** 37 | * Retrieve summary information on all products 38 | * @param {object} pagingOptions Optionally control the number of results and starting offset 39 | * in the result set 40 | */ 41 | Products.prototype.getAll = function getAll(pagingOptions, callback) { 42 | var query = _.pick(pagingOptions, ['limit', 'offset']) 43 | 44 | this.client.sendRequest({ 45 | url: '/credit-offers/products', 46 | useOAuth: true, 47 | method: 'GET', 48 | qs: query 49 | }, callback) 50 | } 51 | 52 | /** 53 | * Retrieve summary information on all card products 54 | * @param {object} pagingOptions Optionally control the number of results and starting offset 55 | * in the result set 56 | */ 57 | Products.prototype.getAllCards = function getAllCards(pagingOptions, callback) { 58 | var query = _.pick(pagingOptions, ['limit', 'offset']) 59 | 60 | this.client.sendRequest({ 61 | url: '/credit-offers/products/cards', 62 | useOAuth: true, 63 | method: 'GET', 64 | qs: query 65 | }, callback) 66 | } 67 | 68 | /** 69 | * Retrieve detailed information on all card products of a specific type 70 | * @param {string} cardType The type of card (BusinessCard, ConsumerCard) 71 | * @param {object} pagingOptions Optionally control the number of results and starting offset 72 | * in the result set 73 | */ 74 | Products.prototype.getCards = function getCards(cardType, pagingOptions, callback) { 75 | if (!cardType) { 76 | callback(new Error('A card type must be specified')) 77 | } 78 | var query = _.pick(pagingOptions, ['limit', 'offset']) 79 | 80 | this.client.sendRequest({ 81 | url: '/credit-offers/products/cards/' + encodeURIComponent(cardType), 82 | useOAuth: true, 83 | method: 'GET', 84 | qs: query 85 | }, callback) 86 | } 87 | 88 | /** 89 | * Retrieve summary information on all products 90 | * @param {string} cardType The type of card (BusinessCard, ConsumerCard) 91 | * @param {string} productId The ID of the consumer card product for which to retrieve details 92 | */ 93 | Products.prototype.getCardDetail = function getCardDetail(cardType, productId, callback) { 94 | if (!cardType) { 95 | callback(new Error('A card type must be specified')) 96 | } 97 | if (!productId) { 98 | callback(new Error('A product ID must be specified in order to retrieve product details')) 99 | } 100 | 101 | this.client.sendRequest({ 102 | url: '/credit-offers/products/' + encodeURIComponent(cardType) + '/' + encodeURIComponent(productId), 103 | useOAuth: true, 104 | method: 'GET' 105 | }, callback) 106 | } 107 | 108 | Products.prototype.postPrefillAcceptance = function postPrefillAcceptance(user, callback) { 109 | this.client.sendRequest({ 110 | url: '/credit-offers/applicant-details', 111 | useOAuth: true, 112 | method: 'POST', 113 | body: user 114 | }, callback) 115 | } 116 | -------------------------------------------------------------------------------- /views/offers.jade: -------------------------------------------------------------------------------- 1 | //- Copyright 2017 Capital One Services, LLC 2 | //- 3 | //- Licensed under the Apache License, Version 2.0 (the "License"); 4 | //- you may not use this file except in compliance with the License. 5 | //- You may obtain a copy of the License at 6 | //- 7 | //- http://www.apache.org/licenses/LICENSE-2.0 8 | //- 9 | //- Unless required by applicable law or agreed to in writing, software 10 | //- distributed under the License is distributed on an "AS IS" BASIS, 11 | //- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | //- See the License for the specific language governing permissions and limitations under the License. 13 | //- 14 | //- SPDX-Copyright: Copyright (c) Capital One Services, LLC 15 | //- SPDX-License-Identifier: Apache-2.0 16 | 17 | extends ./layout 18 | 19 | block content 20 | div#prequal-offers 21 | div.header 22 | div.container 23 | if error 24 | span.error= error 25 | else if isPrequalified 26 | h2 You prequalify for the following offers: 27 | else if products && products.length 28 | h2 We're sorry, we can't prequalify you based on the information that you provided, but you can check out these great offers: 29 | else 30 | h2 We weren't able to find any offers 31 | 32 | div.cards.container 33 | each card in products 34 | div.card-info(data-raw-json="#{card.rawJSON}") 35 | div.main-info 36 | div.details 37 | p.card-title 38 | !{card.productDisplayName} 39 | a.json-toggle.pull-right(href="#", title="See raw JSON") {...} 40 | div.summary 41 | +cardNameImage(card) 42 | +marketingCopy(card) 43 | div.apply.json-toggle.pull-right(title="Apply Now Link Disabled in Sandbox") 44 | +applyButton(card) 45 | p with CapitalOne 46 | sup ® 47 | div.additional-info 48 | div.info 49 | div.info-label Purchases Intro APR 50 | div.info-value= card.introPurchaseApr.introPurchaseAprDescription || 'N/A' 51 | div.info 52 | div.info-label Balance Transfers Intro APR 53 | div.info-value= card.introBalanceTransferApr.introBalanceTransferAprDescription || 'N/A' 54 | div.info 55 | div.info-label Regular APR 56 | div.info-value= card.purchaseApr.purchaseAprDescription || 'N/A' 57 | div.info 58 | div.info-label Annual Fee 59 | div.info-value= card.annualMembershipFee || 'N/A' 60 | div.info 61 | div.info-label Credit Needed 62 | div.info-value= (card.creditRating || []).join(', ') || 'N/A' 63 | 64 | block modals 65 | include ./includes/raw-json-modal 66 | include ./includes/prefill-accept-modal 67 | 68 | block scripts 69 | script. 70 | $(function() { 71 | var prequalificationId = '#{prequalificationId}' 72 | if (!prequalificationId) { 73 | console.warn('Displayed offers without a prequalification ID') 74 | return 75 | } 76 | 77 | // Tell the server that we have displayed the products to the user 78 | $.post('/offers/acknowledge/' + encodeURIComponent(prequalificationId)) 79 | .done(function () { 80 | console.log('Successfully acknowledged prequalification products') 81 | $('.acknowledgement .status-icon').addClass('success') 82 | $('.acknowledgement .status-text').text('Acknowledged') 83 | }) 84 | .fail(function (res, st, err) { 85 | console.warn('Failed to acknowledge prequalification products: ' + err) 86 | $('.acknowledgement .status-icon').addClass('failure') 87 | $('.acknowledgement .status-text').text('Acknowledgement Failed') 88 | $('.acknowledgement') 89 | .attr('data-toggle', 'tooltip') 90 | .attr('title', 'err') 91 | .tooltip() 92 | }) 93 | .always(function () { 94 | $('.acknowledgement .status-icon').removeClass('fa-circle-o').addClass('fa-circle') 95 | }) 96 | }) 97 | -------------------------------------------------------------------------------- /creditoffers/client.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Capital One Services, LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and limitations under the License. 14 | 15 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | var request = require('request') 20 | var _ = require('lodash') 21 | var format = require('util').format 22 | var debug = require('debug')('credit-offers:api-client') 23 | var util = require('util') 24 | 25 | // Default to a secure call to the API endpoint 26 | var defaultOptions = { 27 | url: 'https://api.capitalone.com', 28 | apiVersion: 3 29 | } 30 | 31 | /** 32 | * The API client class 33 | * @param options {object} Client options (host url, API version) 34 | */ 35 | function ApiClient (options, oauth) { 36 | if (!this instanceof ApiClient) { 37 | return new ApiClient(options) 38 | } 39 | 40 | // Store the supplied options, using default values if not specified 41 | this.options = _.defaults({}, options, defaultOptions) 42 | this.oauth = oauth 43 | debug('Initializing API client', this.options) 44 | } 45 | module.exports = ApiClient 46 | 47 | /** 48 | * Send a request to the API and parse the response, handling oauth and errors as needed 49 | */ 50 | ApiClient.prototype.sendRequest = function _sendRequest (reqOptions, callback) { 51 | var defaultRequestSettings = { 52 | baseUrl: this.options.url, 53 | json: true, 54 | headers: { 55 | 'Accept': 'application/json; v=' + this.options.apiVersion 56 | } 57 | } 58 | 59 | // Populate the above request defaults if not passed in 60 | _.defaults(reqOptions, defaultRequestSettings) 61 | var send = function() { 62 | debug('Sending request', reqOptions) 63 | request(reqOptions, function (err, response, body) { 64 | processResponse(err, response, body, callback) 65 | }) 66 | } 67 | 68 | if (reqOptions.useOAuth) 69 | { 70 | // Wrap the request in a call to get the oauth token 71 | this.oauth.withToken(function (err, token) { 72 | if (err) { return callback(err) } 73 | reqOptions.auth = { bearer: token.access_token } 74 | send() 75 | }) 76 | } 77 | else { 78 | send() 79 | } 80 | } 81 | 82 | function processResponse (err, response, body, callback) { 83 | if (err) { return callback(err) } 84 | 85 | if (response.statusCode >= 400) { 86 | // If the status code is an error, look for more info in the response body 87 | debug('Received error status code ' + response.statusCode) 88 | return processResponseErrors(body, callback) 89 | } else if (response.statusCode >= 200) { 90 | // Pass the body contents back to the caller 91 | debug('Received response', util.inspect(body, false, null)) 92 | return callback(null, body) 93 | } else { 94 | // Unknown status code 95 | var errorMessage = 'Received unexpected status code: ' + response.statusCode 96 | console.error(errorMessage) 97 | return callback(new Error(errorMessage)) 98 | } 99 | } 100 | 101 | function processResponseErrors (responseBody, callback) { 102 | if (!responseBody) { 103 | return callback(new Error('The request failed with no error details returned')) 104 | } 105 | 106 | var errorCode = responseBody.code || '' 107 | var errorDescription = responseBody.description || '' 108 | var documentationUrl = responseBody.documentationUrl || '' 109 | var message = format('Received an error from the API: code=%s | description=%s | documentation=%s', errorCode, errorDescription, documentationUrl) 110 | console.error(message) 111 | callback(new Error(message)) 112 | } 113 | -------------------------------------------------------------------------------- /views/layout.jade: -------------------------------------------------------------------------------- 1 | //- Copyright 2017 Capital One Services, LLC 2 | //- 3 | //- Licensed under the Apache License, Version 2.0 (the "License"); 4 | //- you may not use this file except in compliance with the License. 5 | //- You may obtain a copy of the License at 6 | //- 7 | //- http://www.apache.org/licenses/LICENSE-2.0 8 | //- 9 | //- Unless required by applicable law or agreed to in writing, software 10 | //- distributed under the License is distributed on an "AS IS" BASIS, 11 | //- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | //- See the License for the specific language governing permissions and limitations under the License. 13 | //- 14 | //- SPDX-Copyright: Copyright (c) Capital One Services, LLC 15 | //- SPDX-License-Identifier: Apache-2.0 16 | 17 | //- Common Card mixins 18 | 19 | mixin cardNameImage(card) 20 | - var image = card.images && (card.images.cardArt || card.images.cardName || card.images.banner) 21 | if image 22 | img.card-name(src=image.url, alt=image.alternateText)&attributes(attributes) 23 | else 24 | img.card-name(src='/images/default-card.png')&attributes(attributes) 25 | 26 | mixin applyButton(card) 27 | a.btn.btn.btn-lg.btn-success.apply-btn(href="#{card.applyNowLink}", target="_blank") 28 | i.fa.fa-lock(aria-hidden="true") 29 | | APPLY NOW 30 | 31 | mixin collapsibleMarketingCopy(card) 32 | ul 33 | each copyLine in card.mainMarketingCopy 34 | li !{copyLine} 35 | if card.extraMarketingCopy && card.extraMarketingCopy.length 36 | ul.collapse 37 | each copyLine in card.extraMarketingCopy 38 | li !{copyLine} 39 | a.show-hide(href='#') 40 | | Show More 41 | 42 | mixin marketingCopy(card) 43 | div.marketing-copy 44 | if card.mainMarketingCopy 45 | +collapsibleMarketingCopy(card) 46 | else 47 | span.missing-text 'No Info' 48 | 49 | doctype html(lang='en') 50 | head 51 | meta(charset='utf-8') 52 | meta(http-equiv='x-ua-compatible', content='ie=edge') 53 | title=title 54 | meta(name='description', content='') 55 | meta(name='viewport', content='width=device-width, initial-scale=1') 56 | link(rel='stylesheet', href='/css/bootstrap.min.css') 57 | link(rel='stylesheet', href='/css/bootstrap-theme.min.css') 58 | link(rel='stylesheet', href='/css/font-awesome.min.css') 59 | link(rel='stylesheet', href='/css/style.css') 60 | script(src='https://code.jquery.com/jquery-2.2.0.min.js') 61 | script(src='/js/bootstrap.min.js') 62 | script(src='/js/creditoffers.js') 63 | body(class=user && user.firstName && user.firstName.length ? 'signed-in' : '') 64 | nav.navbar.navbar-default.navbar-fixed-top 65 | div.container 66 | div.navbar-header 67 | a.navbar-brand(href="/") 68 | i.fa.fa-credit-card-alt(aria-hidden="true") 69 | | CreditSite 70 | block navbar-custom 71 | block content 72 | block modals 73 | block scripts 74 | script. 75 | $(function () { 76 | $('.marketing-copy a.show-hide').click(function (e) { 77 | var $el = $(e.target), 78 | copy = $el.siblings('.collapse') 79 | e.preventDefault() 80 | copy.collapse('toggle') 81 | }) 82 | 83 | $('.marketing-copy .collapse') 84 | .on('show.bs.collapse', function (e) { 85 | $(e.target).siblings('a.show-hide').text('Show Less') 86 | }) 87 | .on('hide.bs.collapse', function (e) { 88 | $(e.target).siblings('a.show-hide').text('Show More') 89 | }) 90 | }); 91 | $(function () { 92 | $('.card-info [data-toggle="tooltip"]').tooltip({delay: 0, placement: 'right'}); 93 | }); 94 | $(function () { 95 | $('.card-info a.json-toggle').click(function (evt) { 96 | var card = $(this).closest('.card-info'), 97 | json = JSON.stringify(card.data('rawJson'), null, 2); 98 | evt.preventDefault(); 99 | 100 | $('#card-json .raw-json').text(json); 101 | $('#card-json').modal('show'); 102 | }); 103 | }); 104 | -------------------------------------------------------------------------------- /views/includes/prefill-accept-modal.jade: -------------------------------------------------------------------------------- 1 | //- Copyright 2017 Capital One Services, LLC 2 | //- 3 | //- Licensed under the Apache License, Version 2.0 (the "License"); 4 | //- you may not use this file except in compliance with the License. 5 | //- You may obtain a copy of the License at 6 | //- 7 | //- http://www.apache.org/licenses/LICENSE-2.0 8 | //- 9 | //- Unless required by applicable law or agreed to in writing, software 10 | //- distributed under the License is distributed on an "AS IS" BASIS, 11 | //- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | //- See the License for the specific language governing permissions and limitations under the License. 13 | //- 14 | //- SPDX-Copyright: Copyright (c) Capital One Services, LLC 15 | //- SPDX-License-Identifier: Apache-2.0 16 | div#prefill-acceptance.modal.fade(tabindex='-1', role='dialog') 17 | div.modal-dialog 18 | div.modal-content 19 | form(action='/prefill-acceptance', method='POST', name="prefill-acceptance") 20 | input(type="hidden" name="_csrf" value="#{csrfToken}") 21 | div.modal-header 22 | button.close(type='button', data-dismiss='modal', aria-label='close') 23 | span(aria-hidden='true') × 24 | h4.modal-title Prefill Acceptance 25 | div.modal-body 26 | | Save time by allowing us to prefill some of your application information. 27 | .bold Information we will attempt to prefill includes: 28 | ul.list-group 29 | li.list-group-item 30 | .container-fluid 31 | .col-xs-6 32 | | Name (First, Middle, Last & Suffix): 33 | .col-xs-6 34 | | !{user.firstName} !{user.middleName} !{user.lastName} 35 | li.list-group-item 36 | .container-fluid 37 | .col-xs-6 38 | | Date of Birth: 39 | .col-xs-6 40 | | !{user.dateOfBirth} 41 | li.list-group-item 42 | .container-fluid 43 | .col-xs-2 44 | | Address: 45 | .col-xs-10 46 | | !{user.address.addressLine1} !{user.address.addressLine2} 47 | if user.address.addressLine3 || user.address.addressLine4 48 | br 49 | | !{user.address.addressLine3} !{user.address.addressLine4} 50 | br 51 | | !{user.address.city}, !{user.address.stateCode} !{user.address.postalCode} 52 | li.list-group-item 53 | .container-fluid 54 | .col-xs-4 Phone Numbers: 55 | .col-xs-8 56 | if user.telephoneNumbers && user.telephoneNumbers.length 57 | each num in user.telephoneNumbers 58 | | (!{num.phoneNumberType}) !{num.telephoneNumber} 59 | br 60 | li.list-group-item 61 | .container-fluid 62 | .col-xs-4 Email Addresses: 63 | .col-xs-8 64 | if user.emailAddresses && user.emailAddresses.length 65 | each address in user.emailAddresses 66 | | !{address.emailAddress} 67 | br 68 | 69 | li.list-group-item 70 | .container-fluid 71 | .col-xs-3 Income: 72 | .col-xs-9 !{user.annualIncome} 73 | li.list-group-item 74 | .container-fluid 75 | .col-xs-8 Type of Bank Account you may have (if any): 76 | .col-xs-4 77 | each bankAccountSummary in bankAccountSummaryOptions 78 | if bankAccountSummary.value === user.bankAccountSummary 79 | | !{bankAccountSummary.key} 80 | 81 | div.modal-footer 82 | div 83 | span.text-danger.hidden 84 | | Prefill attempt failed 85 | i.spinner.fa.fa-spinner.fa-pulse.fa-fw.hidden 86 | |    87 | button.btn.btn-default(type='button', data-dismiss='modal') 88 | | Close 89 | button.btn.btn-primary(type='submit') 90 | | Accept 91 | a.deny-prefill(target="_blank") Don't prefill information 92 | -------------------------------------------------------------------------------- /routes/offers.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Capital One Services, LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and limitations under the License. 14 | 15 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | /* Defines routes related to finding and displaying credit offers */ 20 | 21 | var express = require('express') 22 | var util = require('util') 23 | var _ = require('lodash') 24 | var csrf = require('csurf') 25 | var debug = require('debug')('credit-offers:offers') 26 | var productViewModel = require('../viewmodels').preQualProduct 27 | var validation = require('../validation') 28 | 29 | module.exports = function(client) { 30 | var router = express.Router() 31 | var csrfProtection = csrf({ 32 | cookie: true 33 | }) 34 | 35 | // POST customer info to check for offers 36 | router.post('/', 37 | csrfProtection, 38 | function(req, res, next) { 39 | // Strip out the CSRF token 40 | delete req.body._csrf 41 | 42 | // Validate the request body 43 | // NOTE: In a larger app, it would be worth pulling this logic out into 44 | // more reusable middleware 45 | req.checkBody(validation.models.customerInfo) 46 | var errors = req.validationErrors(true) 47 | if (errors) { 48 | debug('Validation errors in request body!', util.inspect(errors, false, null)) 49 | if (req.xhr) { 50 | return res.status(400).json(errors) 51 | } else { 52 | var failSummary = _(errors).map(function(error) { 53 | return error.msg 54 | }).value().join('; ') 55 | next(new Error('Validation failed: ' + failSummary)) 56 | return 57 | } 58 | } 59 | 60 | // Strip out empty fields 61 | req.body = _.omitBy(req.body, function(value, key) { 62 | return value == '' 63 | }) 64 | 65 | // Custom body sanitizing 66 | req.sanitizeBody('annualIncome').toInt() 67 | 68 | next() 69 | }, 70 | function(req, res, next) { 71 | var customerInfo = getCustomerInfo(req.body) 72 | 73 | client.prequalification.create(customerInfo, function(err, response) { 74 | if (err) { 75 | return next(err) 76 | } 77 | 78 | var apiProducts = response.products || [] 79 | var productViewModels = _(apiProducts) 80 | .sortBy('priority') // Display in the priority order given by the API 81 | .map(productViewModel) // Transform to a view model for easier display 82 | .value() 83 | 84 | var viewModel = { 85 | csrfToken: req.csrfToken(), 86 | title: 'Credit Offers', 87 | isPrequalified: response.isPrequalified, 88 | prequalificationId: response.prequalificationId, 89 | products: productViewModels, 90 | user: req.session.user || { address: {}} 91 | } 92 | res.render('offers', viewModel) 93 | }) 94 | }) 95 | 96 | function getCustomerInfo(body) { 97 | // Build the customer info (moving address into its own object) 98 | var customerProps = [ 99 | 'firstName', 100 | 'middleName', 101 | 'lastName', 102 | 'nameSuffix', 103 | 'taxId', 104 | 'dateOfBirth', 105 | 'emailAddress', 106 | 'annualIncome', 107 | 'selfAssessedCreditRating', 108 | 'bankAccountSummary', 109 | 'requestedBenefit' 110 | ] 111 | var addressProps = [ 112 | 'addressLine1', 113 | 'addressLine2', 114 | 'addressLine3', 115 | 'addressLine4', 116 | 'city', 117 | 'stateCode', 118 | 'postalCode', 119 | 'addressType' 120 | ] 121 | var customerInfo = _.pick(body, customerProps) 122 | customerInfo.address = _.pick(body, addressProps) 123 | 124 | return customerInfo 125 | } 126 | 127 | // POST acknowledgement that prequal offers were displayed 128 | router.post('/acknowledge/:id', function(req, res, next) { 129 | debug('Received acknowledgement of ' + req.params.id) 130 | var id = req.params.id 131 | if (!id) { 132 | res.status(400).send() 133 | return 134 | } 135 | 136 | client.prequalification.acknowledge(id, function(err, response) { 137 | if (err) { 138 | debug('Error in API call', err) 139 | res.status(500).send() 140 | return 141 | } 142 | res.status(200).send() 143 | }) 144 | }) 145 | 146 | return router 147 | } 148 | -------------------------------------------------------------------------------- /validation/customerInfo.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Capital One Services, LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and limitations under the License. 14 | 15 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | /** @module Validation schema for incoming customer information **/ 20 | module.exports = { 21 | // Name rules 22 | 'firstName': { 23 | matches: { 24 | options: [/^[A-Za-z '-]{1,35}$/] 25 | }, 26 | errorMessage: 'First Name is invalid' 27 | }, 28 | 'middleName': { 29 | optional: { options: { checkFalsy: true } }, 30 | matches: { 31 | options: [/^[A-Za-z -]{0,1}$/] 32 | }, 33 | errorMessage: 'Middle Name is invalid' 34 | }, 35 | 'lastName': { 36 | matches: { 37 | options: [/^[A-Za-z '-]{2,35}$/] 38 | }, 39 | errorMessage: 'Last Name is invalid' 40 | }, 41 | 'nameSuffix': { 42 | matches: { 43 | options: [/^[A-Za-z. -]{0,9}$/] 44 | }, 45 | errorMessage: 'Name Suffix is invalid' 46 | }, 47 | 48 | // Address rules 49 | 'addressLine1': { 50 | notEmpty: { errorMessage: 'Address Line 1 is required' }, 51 | isLength: { 52 | options: [{ min: 1, max: 60 }], 53 | errorMessage: 'Address Line 1 cannot be longer than 60 characters' 54 | }, 55 | errorMessage: 'Address Line 1 is invalid' 56 | }, 57 | 'addressLine2': { 58 | optional: { options: { checkFalsy: true } }, 59 | isLength: { 60 | options: [{ min: 0, max: 60 }], 61 | errorMessage: 'Address Line 2 cannot be longer than 60 characters' 62 | }, 63 | errorMessage: 'Address Line 2 is invalid' 64 | }, 65 | 'addressLine3': { 66 | optional: { options: { checkFalsy: true } }, 67 | isLength: { 68 | options: [{ min: 0, max: 60 }], 69 | errorMessage: 'Address Line 3 cannot be longer than 60 characters' 70 | }, 71 | errorMessage: 'Address Line 3 is invalid' 72 | }, 73 | 'addressLine4': { 74 | optional: { options: { checkFalsy: true } }, 75 | isLength: { 76 | options: [{ min: 0, max: 60 }], 77 | errorMessage: 'Address Line 4 cannot be longer than 60 characters' 78 | }, 79 | errorMessage: 'Address Line 4 is invalid' 80 | }, 81 | 'city': { 82 | notEmpty: { errorMessage: 'City is required' }, 83 | isLength: { 84 | options: [{ min: 0, max: 60 }], 85 | errorMessage: 'City cannot be longer than 35 characters' 86 | }, 87 | errorMessage: 'City is invalid' 88 | }, 89 | 'stateCode': { 90 | notEmpty: { errorMessage: 'State is required' }, 91 | isUSState: true, 92 | errorMessage: 'State is invalid' 93 | }, 94 | 'postalCode': { 95 | notEmpty: { errorMessage: 'Postal Code is required' }, 96 | isNumeric: true, 97 | isLength: { options: [{ min: 5, max: 5 }] }, 98 | errorMessage: 'Postal Code is invalid' 99 | }, 100 | 'addressType': { 101 | optional: { options: { checkFalsy: true } }, 102 | isIn: { options: [ 'Home', 'Business' ] }, 103 | errorMessage: 'Address Type is invalid' 104 | }, 105 | 106 | // Other values 107 | 'taxId': { 108 | notEmpty: { errorMessage: 'Tax ID is required' }, 109 | matches: { 110 | options: [/^(\d{4}|\d{9})$/], 111 | errorMessage: 'Either the full nine digits or last four digits of the Tax ID are required' 112 | }, 113 | errorMessage: 'Tax ID is invalid' 114 | }, 115 | 'dateOfBirth': { 116 | optional: { options: { checkFalsy: true } }, 117 | matches: { 118 | options: [/^\d{4}-\d{2}-\d{2}$/], 119 | }, 120 | errorMessage: 'Date of Birth is invalid' 121 | }, 122 | 'emailAddress': { 123 | optional: { options: { checkFalsy: true } }, 124 | isEmail: true, 125 | errorMessage: 'Email is invalid' 126 | }, 127 | 'annualIncome': { 128 | optional: { options: { checkFalsy: true } }, 129 | isNumeric: true, 130 | errorMessage: 'Annual Income is invalid' 131 | }, 132 | 'selfAssessedCreditRating': { 133 | optional: { options: { checkFalsy: true } }, 134 | isIn: { options: [['Excellent', 'Average', 'Rebuilding']] }, 135 | errorMessage: 'Self-Assessed Credit Rating is invalid' 136 | }, 137 | 'bankAccountSummary': { 138 | optional: { options: { checkFalsy: true } }, 139 | isIn: { options: [['CheckingAndSavings', 'CheckingOnly', 'SavingsOnly', 'Neither']] }, 140 | errorMessage: 'Bank Account Summary is invalid' 141 | }, 142 | 'requestedBenefit': { 143 | optional: { options: { checkFalsy: true } }, 144 | isIn: { options: [['LowInterest', 'TravelRewards', 'CashBack', 'NotSure']] }, 145 | errorMessage: 'Requested Benefit is invalid' 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /views/index.jade: -------------------------------------------------------------------------------- 1 | //- Copyright 2017 Capital One Services, LLC 2 | //- 3 | //- Licensed under the Apache License, Version 2.0 (the "License"); 4 | //- you may not use this file except in compliance with the License. 5 | //- You may obtain a copy of the License at 6 | //- 7 | //- http://www.apache.org/licenses/LICENSE-2.0 8 | //- 9 | //- Unless required by applicable law or agreed to in writing, software 10 | //- distributed under the License is distributed on an "AS IS" BASIS, 11 | //- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | //- See the License for the specific language governing permissions and limitations under the License. 13 | //- 14 | //- SPDX-Copyright: Copyright (c) Capital One Services, LLC 15 | //- SPDX-License-Identifier: Apache-2.0 16 | 17 | extends ./layout.jade 18 | 19 | block navbar-custom 20 | .btn-toolbar.navbar-right 21 | button.btn.btn-md.navbar-btn.btn-success(role='button', data-toggle='modal', data-target='#customer-info') 22 | i.btn-img.fa.fa-credit-card(aria-hidden="true") 23 | | Find Pre-Qualified Offers 24 | a.btn.btn-md.navbar-btn.btn-success.auth-btn&attributes(user.firstName ? { 'class': 'logout-btn', 'href': '/logout' } : { 'data-toggle': 'modal', 'data-target': '#login', 'role': 'button' }) 25 | i.btn-img.fa(aria-hidden="true", class=user.firstName && user.firstName.length ? 'fa-sign-out' : 'fa-sign-in') 26 | = user.firstName ? 'Sign Out' : 'Sign In' 27 | 28 | 29 | block content 30 | div.header 31 | div.container 32 | div.row 33 | div.col-md-7 34 | h2 CapitalOne 35 | sup ® 36 | | Credit Cards 37 | p These are some of the cards on offer from CapitalOne 38 | sup ® 39 | | . 40 | div.filters.col-md-5 41 | div.pull-right 42 | div.btn-group(role="group", aria-label="Card Types") 43 | each cardType in cardTypes 44 | a.btn.btn-sm.btn-primary(href='/?cardType='+cardType.name, disabled=currentCardType===cardType.name)= cardType.display 45 | div.cards.container 46 | each card in cards 47 | // NOTE: In a real app, avoid storing truth in the DOM! 48 | div.card-info(data-raw-json="#{card.rawJSON}") 49 | div.main-info 50 | div.details 51 | p.card-title 52 | span 53 | | !{card.productDisplayName} 54 | if card.productMetrics && card.productMetrics.length 55 | - metric = card.productMetrics[0].ratingsAndReviews 56 | - review = metric.productReviews 57 | span.total-stars 58 | span.actual-stars(style='width:#{metric.productRating*20}%;') 59 | if review 60 | a.reviews(href="#{review.link}", target="_blank") !{review.count} reviews 61 | a.json-toggle.pull-right(href="#", title="See raw JSON") {...} 62 | div.summary 63 | +cardNameImage(card)(data-toggle='tooltip', title='#{(card.productKeywords || []).join(", ")}') 64 | +marketingCopy(card) 65 | div.apply.json-toggle.pull-right 66 | +applyButton(card) 67 | p with CapitalOne 68 | sup ® 69 | div.additional-info 70 | div.info 71 | div.info-label Purchases Intro APR 72 | div.info-value= card.introPurchaseApr.introPurchaseAprDescription || 'N/A' 73 | div.info 74 | div.info-label Balance Transfers Intro APR 75 | div.info-value= card.introBalanceTransferApr.introBalanceTransferAprDescription || 'N/A' 76 | div.info 77 | div.info-label Regular APR 78 | div.info-value= card.purchaseApr.purchaseAprDescription || 'N/A' 79 | div.info 80 | div.info-label Annual Fee 81 | div.info-value= card.annualMembershipFee || 'N/A' 82 | div.info 83 | div.info-label Credit Needed 84 | div.info-value= (card.creditRating || []).join(', ' ) || 'N/A' 85 | 86 | block modals 87 | div#customer-info.modal.fade(tabindex='-1', role='dialog') 88 | div.modal-dialog 89 | div.modal-content 90 | form(action='/offers', method='POST', name="prequalification") 91 | input(type="hidden" name="_csrf" value="#{csrfToken}") 92 | div.modal-header 93 | button.close(type='button', data-dismiss='modal', aria-label='close') 94 | span(aria-hidden='true') × 95 | h4.modal-title Tell us a little about yourself 96 | div.modal-body 97 | include ./includes/customer-form 98 | div.modal-footer 99 | span.text-danger.hidden 100 | | Errors detected. 101 | |    102 | button.btn.btn-default(type='button', data-dismiss='modal') 103 | | Close 104 | button.btn.btn-primary(type='submit') 105 | | See Offers 106 | include ./includes/raw-json-modal 107 | include ./includes/login-modal 108 | include ./includes/prefill-accept-modal 109 | -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Capital One Services, LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and limitations under the License. 14 | 15 | SPDX-Copyright: Copyright (c) Capital One Services, LLC 16 | SPDX-License-Identifier: Apache-2.0 17 | */ 18 | 19 | body { 20 | padding-bottom: 20px; 21 | padding-top: 100px; 22 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; } 23 | 24 | @media (min-width: 768px) { 25 | body { 26 | padding-top: 50px; } 27 | } 28 | 29 | .form-group.required > label:after { 30 | content: " *"; 31 | color: red; } 32 | 33 | .form-note { 34 | margin-top: 25px; 35 | margin-bottom: 10px; 36 | color: #888; 37 | padding: 5px 0; 38 | border-bottom: 1px solid #ccc; 39 | } 40 | 41 | .bold { 42 | font-weight: bold; 43 | padding: 5px 0; 44 | } 45 | 46 | .header { 47 | background-color: #efefef; 48 | padding-bottom: 10px; } 49 | .header .filters { 50 | margin-top: 15px; } 51 | 52 | .cards .card-info { 53 | margin-top: 15px; } 54 | 55 | .card-info { 56 | display: flex; 57 | flex-flow: column nowrap; 58 | border: 1px solid #aaa; 59 | background-color: #fff; } 60 | .card-info img.card-name { 61 | width: 200px; } 62 | .card-info .card-title { 63 | font-size: 1.5em; 64 | font-weight: 600; } 65 | .card-info .total-stars { 66 | width: 80px; 67 | height: 16px; 68 | display: inline-block; 69 | background-size: 16px 48px; 70 | margin: 0 5px; 71 | background-image: url('/images/stars_lg.png'); 72 | background-position: 0 -16px; 73 | } 74 | .card-info .actual-stars { 75 | height: 16px; 76 | display: inline-block; 77 | background-size: 16px 48px; 78 | background-image: url('/images/stars_lg.png'); 79 | } 80 | .card-info a.reviews { 81 | font-size: 12px; 82 | } 83 | .card-info .main-info { 84 | display: flex; 85 | flex-flow: row nowrap; } 86 | .card-info .main-info .details { 87 | padding: 10px 20px 20px 20px; 88 | flex-grow: 1; } 89 | .card-info .main-info .apply { 90 | text-align: center; 91 | padding: 20px; 92 | color: #555; 93 | background-color: #eee; } 94 | .card-info .main-info .apply > .btn { 95 | margin-bottom: 5px; } 96 | .card-info .summary { 97 | display: flex; 98 | flex-flow: row nowrap; 99 | align-items: flex-start; } 100 | .card-info .additional-info { 101 | display: flex; 102 | flex-flow: row nowrap; 103 | align-items: stretch; } 104 | .card-info .additional-info > * { 105 | flex-grow: 1; } 106 | .card-info .info .info-label { 107 | display: flex; 108 | flex-flow: column nowrap; 109 | justify-content: center; 110 | height: 54px; 111 | padding: 10px 20px; 112 | text-align: center; 113 | background-color: #0a4469; 114 | color: white; } 115 | .card-info .info:not(:last-child) .info-label { 116 | border-right: 1px solid #fff; } 117 | .card-info .info .info-value { 118 | text-align: center; 119 | padding: 10px; } 120 | .card-info .json-toggle { 121 | font-size: 0.5em; 122 | text-decoration: none; 123 | background-color: #eee; 124 | padding: 0.5em; 125 | border-radius: 0.2em; 126 | color: #888; } 127 | .card-info .json-toggle:hover { 128 | text-decoration: none; 129 | background-color: #efefef; 130 | color: #333; } 131 | 132 | .marketing-copy > ul:first-child { 133 | margin-bottom: 0; } 134 | .marketing-copy ul.collapse { 135 | margin-top: 0; } 136 | .marketing-copy a.show-hide { 137 | display: inline-block; 138 | margin-top: 10px; 139 | margin-left: 40px; } 140 | .marketing-copy a.show-hide:focus { 141 | text-decoration: none; } 142 | .marketing-copy .collapse + a.show-hide::after, .marketing-copy .collapsing + a.show-hide::after { 143 | font-family: FontAwesome; 144 | content: " \f078"; } 145 | .marketing-copy .collapse.in + a.show-hide::after { 146 | font-family: FontAwesome; 147 | content: " \f077"; } 148 | 149 | #card-json .raw-json { 150 | max-height: 400px; } 151 | 152 | .btn i { 153 | margin-right: 10px; } 154 | 155 | #prequal-offers > .cards { 156 | margin-bottom: 40px; } 157 | 158 | .modal-footer > div { 159 | padding-bottom: 5px; 160 | } 161 | 162 | footer#prequal-stats-summary { 163 | display: flex; 164 | flex-flow: row nowrap; 165 | align-items: center; 166 | height: 40px; 167 | padding-left: 15px; 168 | padding-right: 15px; 169 | background-color: #666; 170 | color: #ddd; 171 | box-shadow: inset 0 10px 10px -10px rgba(0,0,0,0.5); } 172 | #prequal-stats-summary i { 173 | margin-right: 10px; } 174 | #prequal-stats-summary .stat-text { 175 | display: flex; 176 | flex-grow: 1; 177 | flex-flow: row nowrap; } 178 | #prequal-stats-summary .stat:not(:last-child) { 179 | margin-right: 15px; } 180 | #prequal-stats-summary .stat-title { 181 | font-weight: bold; } 182 | 183 | .acknowledgement .status-icon { 184 | margin-left: 5px; } 185 | .acknowledgement .status-icon.success { 186 | color: #61b961; } 187 | .acknowledgement .status-icon.failure { 188 | color: #e42626; } 189 | 190 | /* Bootstrap overrides */ 191 | .navbar-default { 192 | border: 0; 193 | background-image: none; 194 | background-color: #03A9F4; } 195 | .navbar-default .navbar-brand, 196 | .navbar-default .navbar-brand:hover, 197 | .navbar-default .navbar-brand:focus { 198 | color: #fff; 199 | text-shadow: none; } 200 | .navbar-default .navbar-brand i { 201 | margin-right: 10px; } 202 | 203 | .btn[disabled] { 204 | cursor: default; } 205 | 206 | .btn-success { 207 | background-image: none; 208 | background-color: #8BC34A; 209 | box-shadow: none; 210 | border: none; 211 | text-shadow: none; } 212 | .btn-success:hover, .btn-success:focus { 213 | background-image: none; 214 | background-color: #9ace5d; 215 | background-position: 0; } 216 | 217 | .btn-primary { 218 | background-image: none; 219 | background-color: #1976D2; 220 | box-shadow: none; 221 | border: none; 222 | text-shadow: none; } 223 | .btn-primary:hover, .btn-primary:focus { 224 | background-image: none; 225 | background-color: #2196F3; 226 | background-position: 0; } 227 | .btn-primary[disabled], .btn-primary[disabled]:hover, .btn-primary[disabled]:focus { 228 | background-color: #1976D2; } 229 | 230 | 231 | #prefill-acceptance [class*="col-"], #prefill-acceptance .container-fluid { 232 | padding: 0; 233 | } 234 | 235 | #prefill-acceptance .container-fluid > [class*="col-"]:last-child { 236 | text-align: right; 237 | } 238 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Capital One built this project to help the users of this API. We are no longer actively enhancing this API. We have archived the project as of Jul 23 2019 where it will be available in a read-only state. 2 | 3 | # Credit Offers API Reference Application Code 4 | 5 | Credit Offers is a card acquisition service that provides prequalified credit offer listings based on end customer provided information. Affiliates are able to provide these offers directly without need of a web redirect. If a prequalified offer is not available, a default offer is returned by us. 6 | 7 | ## Software Requirements Including version 8 | This is version 2.0 of the Credit Offers API Reference Application Code. For software requirements, see Build/Install Instructions below. 9 | 10 | This reference app illustrates the use of the Credit Offers API to 11 | 12 | * Retrieve and display card products (Business or Consumer) using the `/credit-offers/products/cards/{cardType}` endpoint 13 | * Collect customer information and retrieve a list of targeted product offers for display using the `/credit-offers/prequalifications` endpoint 14 | * Send acknowledgement to Capital One that the targeted product offers have been displayed using the `/credit-offers/prequalifications/{prequalificationId}` endpoint 15 | * Retrieve and display prequalification summary info using the `/credit-offers/prequalifications-summary` endpoint 16 | * Exemplify signing in and submitting example user data to the `/credit-offers/prefill-acceptance` endpoint 17 | 18 | Some additional API features that are **not** directly illustrated by this app include: 19 | 20 | * Using the `limit` and `offset` parameters to retrieve multiple pages of products 21 | * Using the `/credit-offers/products` endpoint to retrieve summaries of *all* products 22 | * Using the `/credit-offers/products/cards` endpoint to retrieve summaries of all card products 23 | * Using the `/credit-offers/products/cards/{cardType}/{productId}` endpoint to retrieve information about a single specific card product 24 | 25 | If you encounter any issues using this reference code, please submit them in the form of GitHub issues. 26 | 27 | ## Build/Install Instructions 28 | ### Dependencies 29 | * [Node.js](https://nodejs.org) 4.X or higher 30 | 31 | All other dependencies are loaded with [npm](https://www.npmjs.com/). All dependencies are cross-platform. Notable dependencies are listed below. 32 | * [express](http://expressjs.com/) - Minimalist web framework for Node.js 33 | 34 | ### config.js 35 | You'll need to set up your `config.js` file before you can run the app. 36 | 37 | * Create this file by copying and renaming [config.js.sample](https://github.com/capitalone/CreditOffers-API-reference-app/blob/master/config.js.sample). Be careful not to put `config.js` into version control. (We've added it to the repository's `.gitignore` for you.) 38 | * Make sure that you've registered an app on [Capital One's developer portal](https://developer.capitalone.com/). 39 | * Edit the `clientID` and `clientSecret` values in `config.js` to specify the **Client ID** and **Client Secret** that were provided when you registered the app. 40 | 41 | ### Start the app 42 | From the project root: 43 | 44 | `npm install` 45 | `npm start` 46 | 47 | ### Try it out 48 | 49 | Navigate to http://localhost:3000. This will retrieve a list of Consumer card products from the API and display simple information about each. From here, you can try a few simple things: 50 | 51 | * Toggle the card type to 'Business' to request and display a list of business card products from the API 52 | * Click on the 'Find Pre-Qualified Offers' button to launch a simple customer information form and test out the pre-qualification API behavior. The results screen will also perform two asynchronous calls: 53 | * POST to `/credit-offers/prequalifications/{prequalificationId}` to acknowledge that the results were displayed to the customer 54 | * GET from the `/credit-offers/prequalifications-summary` endpoint to display simple pre-qualification statistics at the bottom of the page 55 | * Click on the 'Sign in' button and use prepared credentials to simulate a session. Now, click on any products 'Apply Now' button. A confirmation window will launch, asking you to confirm whether you would like to prefill application information. Confirming will initiate a POST request to the `/credit-offers/prefill-acceptance` endpoint and append the returned applicantDetailsKey to the application link. 56 | * username / password 57 | * jsmith / Reference@123 58 | * rhubbard / Reference@123 59 | 60 | #### A note about errors 61 | 62 | For demonstration purposes, API and server-side validation errors are displayed in an error page in the UI. A full production-ready application should have more robust error handling, and keep a smooth user experience. 63 | 64 | ### Viewing more details 65 | 66 | To get a deeper look at the messages being passed, start the app with the following command `DEBUG=credit-offers:* NODE_DEBUG=request npm start`. This will activate detailed debug logging to the console, showing the details of the request to the API and the response received. 67 | 68 | ## Best Practices 69 | This application makes use of the [helmet](https://www.npmjs.com/package/helmet) library for safer http headers, the [csurf](https://www.npmjs.com/package/csurf) library to avoid cross-site request forgery attacks, the [express-validator](https://www.npmjs.com/package/express-validator) library to validate customer info on the server side, and the [sanitize-html](https://www.npmjs.com/package/sanitize-html) library to safely sanitize values from the API before displaying them as HTML to the user. However, when developing and hosting a real world application, make sure to be aware of the [security](http://expressjs.com/en/advanced/best-practice-security.html) and [performance](http://expressjs.com/en/advanced/best-practice-performance.html) best practices for the Express framework. In particular, hosting with TLS is strongly recommended and free certificates can be acquired at https://letsencrypt.org/. 70 | 71 | ## Architecture 72 | This is a [Node.js](https://nodejs.org) 4.x and higher app built with [Express](http://expressjs.com/) 4.13.1. Because of the simple nature, there is no session management or data persistence. 73 | 74 | The Node.js https library is verbose and repetitive for our narrow use case, so we also used [request](https://github.com/request/request) for calls to the Credit Offers API. 75 | 76 | The application structure follows the pattern generated by the [Express application generator](http://expressjs.com/en/starter/generator.html). 77 | 78 | ## Roadmap 79 | This reference app code is intended as a starting place for developers who want to use the Credit Offers API. As such, it will be updated with new functionality only when the Credit Offers API is updated with new functionality. 80 | 81 | ## Contributors 82 | We welcome your interest in Capital One’s Open Source Projects (the “Project”). Any Contributor to the Project must accept and sign a CLA indicating agreement to the license terms. Except for the license granted in this CLA to Capital One and to recipients of software distributed by Capital One, You reserve all right, title, and interest in and to your Contributions; this CLA does not impact your rights to use your own contributions for any other purpose. 83 | 84 | ##### [Link to Agreement] (https://docs.google.com/forms/d/19LpBBjykHPox18vrZvBbZUcK6gQTj7qv1O5hCduAZFU/viewform) 85 | 86 | This project adheres to the [Open Source Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code. 87 | 88 | [code-of-conduct]: http://www.capitalone.io/codeofconduct/ 89 | 90 | ### Contribution Guidelines 91 | We encourage any contributions that align with the intent of this project and add more functionality or languages that other developers can make use of. To contribute to the project, please submit a PR for our review. Before contributing any source code, familiarize yourself with the Apache License 2.0 (license.md), which controls the licensing for this project. 92 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /credit-offers_notices.md: -------------------------------------------------------------------------------- 1 | # Dependency Licenses 2 | *Generated using [license-checker](https://github.com/davglass/license-checker), with some manual adjustments* 3 | 4 | * [accepts@1.2.13](https://github.com/jshttp/accepts) - MIT 5 | * [acorn-globals@1.0.9](https://github.com/ForbesLindesay/acorn-globals) - MIT 6 | * [acorn@1.2.2](https://github.com/marijnh/acorn) - MIT 7 | * [acorn@2.7.0](https://github.com/ternjs/acorn) - MIT 8 | * [align-text@0.1.4](https://github.com/jonschlinkert/align-text) - MIT 9 | * [amdefine@1.0.0](https://github.com/jrburke/amdefine) - BSD-3-Clause AND MIT 10 | * [ansi-regex@2.0.0](https://github.com/sindresorhus/ansi-regex) - MIT 11 | * [ansi-styles@2.2.1](https://github.com/chalk/ansi-styles) - MIT 12 | * [array-flatten@1.1.1](https://github.com/blakeembrey/array-flatten) - MIT 13 | * [asap@1.0.0](undefined) - MIT 14 | * [asn1@0.2.3](https://github.com/mcavage/node-asn1) - MIT 15 | * [assert-plus@0.2.0](https://github.com/mcavage/node-assert-plus) - MIT 16 | * [assert-plus@1.0.0](https://github.com/mcavage/node-assert-plus) - MIT 17 | * [async@0.2.10](https://github.com/caolan/async) - MIT 18 | * [async@2.1.2](https://github.com/caolan/async) - MIT 19 | * [aws-sign2@0.6.0](https://github.com/mikeal/aws-sign) - Apache-2.0 20 | * [aws4@1.5.0](https://github.com/mhart/aws4) - MIT 21 | * [base64-url@1.2.2](https://github.com/joaquimserafim/base64-url) - ISC 22 | * [basic-auth@1.0.4](https://github.com/jshttp/basic-auth) - MIT 23 | * [bcrypt-pbkdf@1.0.0](undefined) - BSD-4-Clause 24 | * [bl@1.0.3](https://github.com/rvagg/bl) - MIT 25 | * [bluebird@3.4.6](https://github.com/petkaantonov/bluebird) - MIT 26 | * [body-parser@1.13.3](https://github.com/expressjs/body-parser) - MIT 27 | * [boom@2.10.1](https://github.com/hapijs/boom) - BSD-3-Clause 28 | * [bootstrap@3.3.7](https://github.com/twbs/bootstrap) - MIT 29 | * [bytes@2.1.0](https://github.com/visionmedia/bytes.js) - MIT 30 | * [bytes@2.4.0](https://github.com/visionmedia/bytes.js) - MIT 31 | * [camelcase@1.2.1](https://github.com/sindresorhus/camelcase) - MIT 32 | * [camelize@1.0.0](https://github.com/substack/camelize) - MIT 33 | * [caseless@0.11.0](https://github.com/mikeal/caseless) - Apache-2.0 34 | * [center-align@0.1.3](https://github.com/jonschlinkert/center-align) - MIT 35 | * [chalk@1.1.3](https://github.com/chalk/chalk) - MIT 36 | * [character-parser@1.2.1](https://github.com/ForbesLindesay/character-parser) - MIT 37 | * [clean-css@3.4.20](https://github.com/jakubpawlowicz/clean-css) - MIT 38 | * [cliui@2.1.0](https://github.com/bcoe/cliui) - ISC 39 | * [combined-stream@1.0.5](https://github.com/felixge/node-combined-stream) - MIT 40 | * [commander@2.6.0](https://github.com/tj/commander.js) - MIT 41 | * [commander@2.8.1](https://github.com/tj/commander.js) - MIT 42 | * [commander@2.9.0](https://github.com/tj/commander.js) - MIT 43 | * [connect@3.4.0](https://github.com/senchalabs/connect) - MIT 44 | * [constantinople@3.0.2](https://github.com/ForbesLindesay/constantinople) - MIT 45 | * [content-disposition@0.5.1](https://github.com/jshttp/content-disposition) - MIT 46 | * [content-security-policy-builder@1.0.0](https://github.com/helmetjs/content-security-policy-builder) - MIT 47 | * [content-type@1.0.2](https://github.com/jshttp/content-type) - MIT 48 | * [cookie-parser@1.3.5](https://github.com/expressjs/cookie-parser) - MIT 49 | * [cookie-session@1.3.2](https://github.com/expressjs/cookie-session) - MIT 50 | * [cookie-signature@1.0.6](https://github.com/visionmedia/node-cookie-signature) - MIT 51 | * [cookie@0.1.3](https://github.com/jshttp/cookie) - MIT 52 | * [cookie@0.1.5](https://github.com/jshttp/cookie) - MIT 53 | * [core-util-is@1.0.2](https://github.com/isaacs/core-util-is) - MIT 54 | * [credit-offers@2.0.0](https://github.com/capitalone/CreditOffers-API-reference-app) - Apache-2.0 55 | * [cryptiles@2.0.5](https://github.com/hapijs/cryptiles) - BSD-3-Clause 56 | * [csrf@3.0.3](https://github.com/pillarjs/csrf) - MIT 57 | * [css-parse@1.0.4](undefined) - MIT 58 | * [css-stringify@1.0.5](undefined) - MIT 59 | * [css@1.0.8](undefined) - MIT 60 | * [csurf@1.8.3](https://github.com/expressjs/csurf) - MIT 61 | * [dashdash@1.14.0](https://github.com/trentm/node-dashdash) - MIT 62 | * [dashify@0.2.2](https://github.com/jonschlinkert/dashify) - MIT 63 | * [debug@2.2.0](https://github.com/visionmedia/debug) - MIT 64 | * [decamelize@1.2.0](https://github.com/sindresorhus/decamelize) - MIT 65 | * [delayed-stream@1.0.0](https://github.com/felixge/node-delayed-stream) - MIT 66 | * [depd@1.0.1](https://github.com/dougwilson/nodejs-depd) - MIT 67 | * [depd@1.1.0](https://github.com/dougwilson/nodejs-depd) - MIT 68 | * [destroy@1.0.4](https://github.com/stream-utils/destroy) - MIT 69 | * [dns-prefetch-control@0.1.0](https://github.com/helmetjs/dns-prefetch-control) - MIT 70 | * [dom-serializer@0.1.0](https://github.com/cheeriojs/dom-renderer) - MIT 71 | * [domelementtype@1.1.3](https://github.com/FB55/domelementtype) - BSD* 72 | * [domelementtype@1.3.0](https://github.com/FB55/domelementtype) - BSD* 73 | * [domhandler@2.3.0](https://github.com/fb55/DomHandler) - BSD* 74 | * [domutils@1.5.1](https://github.com/FB55/domutils) - BSD* 75 | * [dont-sniff-mimetype@1.0.0](https://github.com/helmetjs/dont-sniff-mimetype) - MIT 76 | * [ecc-jsbn@0.1.1](https://github.com/quartzjer/ecc-jsbn) - MIT 77 | * [ee-first@1.1.1](https://github.com/jonathanong/ee-first) - MIT 78 | * [ejs@2.3.4](https://github.com/mde/ejs) - Apache-2.0 79 | * [entities@1.1.1](https://github.com/fb55/node-entities) - BSD-like 80 | * [escape-html@1.0.2](https://github.com/component/escape-html) - MIT 81 | * [escape-html@1.0.3](https://github.com/component/escape-html) - MIT 82 | * [escape-string-regexp@1.0.5](https://github.com/sindresorhus/escape-string-regexp) - MIT 83 | * [etag@1.7.0](https://github.com/jshttp/etag) - MIT 84 | * [express-validator@2.21.0](https://github.com/ctavan/express-validator) - MIT 85 | * [express@4.13.4](https://github.com/expressjs/express) - MIT 86 | * [extend@3.0.0](https://github.com/justmoon/node-extend) - MIT 87 | * [extsprintf@1.0.2](https://github.com/davepacheco/node-extsprintf) - MIT* 88 | * [finalhandler@0.4.0](https://github.com/pillarjs/finalhandler) - MIT 89 | * [finalhandler@0.4.1](https://github.com/pillarjs/finalhandler) - MIT 90 | * [font-awesome@4.7.0](https://github.com/FortAwesome/Font-Awesome) - (OFL-1.1 AND MIT) 91 | * [forever-agent@0.6.1](https://github.com/mikeal/forever-agent) - Apache-2.0 92 | * [form-data@1.0.1](https://github.com/form-data/form-data) - MIT 93 | * [forwarded@0.1.0](https://github.com/jshttp/forwarded) - MIT 94 | * [frameguard@1.0.0](https://github.com/helmetjs/frameguard) - MIT 95 | * [fresh@0.3.0](https://github.com/jshttp/fresh) - MIT 96 | * [generate-function@2.0.0](https://github.com/mafintosh/generate-function) - MIT 97 | * [generate-object-property@1.2.0](https://github.com/mafintosh/generate-object-property) - MIT 98 | * [getpass@0.1.6](https://github.com/arekinath/node-getpass) - MIT 99 | * [graceful-readlink@1.0.1](https://github.com/zhiyelee/graceful-readlink) - MIT 100 | * [har-validator@2.0.6](https://github.com/ahmadnassri/har-validator) - ISC 101 | * [has-ansi@2.0.0](https://github.com/sindresorhus/has-ansi) - MIT 102 | * [hawk@3.1.3](https://github.com/hueniverse/hawk) - BSD-3-Clause 103 | * [helmet-csp@1.0.3](https://github.com/helmetjs/csp) - MIT 104 | * [helmet@1.1.0](https://github.com/helmetjs/helmet) - MIT 105 | * [hide-powered-by@1.0.0](https://github.com/helmetjs/hide-powered-by) - MIT 106 | * [hoek@2.16.3](https://github.com/hapijs/hoek) - BSD-3-Clause 107 | * [hpkp@1.0.0](https://github.com/helmetjs/hpkp) - MIT 108 | * [hsts@1.0.0](https://github.com/helmetjs/hsts) - MIT 109 | * [html-entities@1.2.0](https://github.com/mdevils/node-html-entities) - MIT 110 | * [htmlparser2@3.9.2](https://github.com/fb55/htmlparser2) - MIT 111 | * [http-errors@1.3.1](https://github.com/jshttp/http-errors) - MIT 112 | * [http-signature@1.1.1](https://github.com/joyent/node-http-signature) - MIT 113 | * [iconv-lite@0.4.11](https://github.com/ashtuchkin/iconv-lite) - MIT 114 | * [iconv-lite@0.4.13](https://github.com/ashtuchkin/iconv-lite) - MIT 115 | * [ienoopen@1.0.0](https://github.com/helmetjs/ienoopen) - MIT 116 | * [inherits@2.0.3](https://github.com/isaacs/inherits) - ISC 117 | * [ipaddr.js@1.0.5](https://github.com/whitequark/ipaddr.js) - MIT 118 | * [is-buffer@1.1.4](https://github.com/feross/is-buffer) - MIT 119 | * [is-my-json-valid@2.15.0](https://github.com/mafintosh/is-my-json-valid) - MIT 120 | * [is-promise@1.0.1](https://github.com/then/is-promise) - MIT 121 | * [is-promise@2.1.0](https://github.com/then/is-promise) - MIT 122 | * [is-property@1.0.2](https://github.com/mikolalysenko/is-property) - MIT 123 | * [is-typedarray@1.0.0](https://github.com/hughsk/is-typedarray) - MIT 124 | * [isarray@1.0.0](https://github.com/juliangruber/isarray) - MIT 125 | * [isstream@0.1.2](https://github.com/rvagg/isstream) - MIT 126 | * [jade@1.11.0](https://github.com/jadejs/jade) - MIT 127 | * [jodid25519@1.0.2](https://github.com/meganz/jodid25519) - MIT 128 | * [jsbn@0.1.0](https://github.com/andyperlitch/jsbn) - BSD 129 | * [json-schema@0.2.3](https://github.com/kriszyp/json-schema) - AFLv2.1,BSD 130 | * [json-stringify-safe@5.0.1](https://github.com/isaacs/json-stringify-safe) - ISC 131 | * [jsonpointer@4.0.0](https://github.com/janl/node-jsonpointer) - MIT 132 | * [jsprim@1.3.1](https://github.com/davepacheco/node-jsprim) - MIT 133 | * [jstransformer@0.0.2](https://github.com/jstransformers/jstransformer) - MIT 134 | * [kind-of@3.0.4](https://github.com/jonschlinkert/kind-of) - MIT 135 | * [lazy-cache@1.0.4](https://github.com/jonschlinkert/lazy-cache) - MIT 136 | * [lodash._baseassign@3.2.0](https://github.com/lodash/lodash) - MIT 137 | * [lodash._basecallback@3.3.1](https://github.com/lodash/lodash) - MIT 138 | * [lodash._basecopy@3.0.1](https://github.com/lodash/lodash) - MIT 139 | * [lodash._baseeach@3.0.4](https://github.com/lodash/lodash) - MIT 140 | * [lodash._baseisequal@3.0.7](https://github.com/lodash/lodash) - MIT 141 | * [lodash._basereduce@3.0.2](https://github.com/lodash/lodash) - MIT 142 | * [lodash._bindcallback@3.0.1](https://github.com/lodash/lodash) - MIT 143 | * [lodash._createassigner@3.1.1](https://github.com/lodash/lodash) - MIT 144 | * [lodash._getnative@3.9.1](https://github.com/lodash/lodash) - MIT 145 | * [lodash._isiterateecall@3.0.9](https://github.com/lodash/lodash) - MIT 146 | * [lodash.assign@3.2.0](https://github.com/lodash/lodash) - MIT 147 | * [lodash.isarguments@3.1.0](https://github.com/lodash/lodash) - MIT 148 | * [lodash.isarray@3.0.4](https://github.com/lodash/lodash) - MIT 149 | * [lodash.isfunction@3.0.6](https://github.com/lodash/lodash) - MIT 150 | * [lodash.isstring@3.0.1](https://github.com/lodash/lodash) - MIT 151 | * [lodash.istypedarray@3.0.6](https://github.com/lodash/lodash) - MIT 152 | * [lodash.keys@3.1.2](https://github.com/lodash/lodash) - MIT 153 | * [lodash.pairs@3.0.1](https://github.com/lodash/lodash) - MIT 154 | * [lodash.reduce@3.1.2](https://github.com/lodash/lodash) - MIT 155 | * [lodash.restparam@3.6.1](https://github.com/lodash/lodash) - MIT 156 | * [lodash.some@3.2.3](https://github.com/lodash/lodash) - MIT 157 | * [lodash@4.1.0](https://github.com/lodash/lodash) - MIT 158 | * [lodash@4.16.4](https://github.com/lodash/lodash) - MIT 159 | * [lodash@4.16.6](https://github.com/lodash/lodash) - MIT 160 | * [longest@1.0.1](https://github.com/jonschlinkert/longest) - MIT 161 | * [media-typer@0.3.0](https://github.com/jshttp/media-typer) - MIT 162 | * [merge-descriptors@1.0.1](https://github.com/component/merge-descriptors) - MIT 163 | * [methods@1.1.2](https://github.com/jshttp/methods) - MIT 164 | * [mime-db@1.24.0](https://github.com/jshttp/mime-db) - MIT 165 | * [mime-types@2.1.12](https://github.com/jshttp/mime-types) - MIT 166 | * [mime@1.3.4](https://github.com/broofa/node-mime) - MIT 167 | * [minimist@0.0.8](https://github.com/substack/minimist) - MIT 168 | * [mkdirp@0.5.1](https://github.com/substack/node-mkdirp) - MIT 169 | * [morgan@1.6.1](https://github.com/expressjs/morgan) - MIT 170 | * [ms@0.7.1](https://github.com/guille/ms.js) - MIT* 171 | * [negotiator@0.5.3](https://github.com/jshttp/negotiator) - MIT 172 | * [nocache@1.0.0](https://github.com/helmetjs/nocache) - MIT 173 | * [node-uuid@1.4.7](https://github.com/broofa/node-uuid) - MIT 174 | * [oauth-sign@0.8.2](https://github.com/mikeal/oauth-sign) - Apache-2.0 175 | * [on-finished@2.3.0](https://github.com/jshttp/on-finished) - MIT 176 | * [on-headers@1.0.1](https://github.com/jshttp/on-headers) - MIT 177 | * [optimist@0.3.7](https://github.com/substack/node-optimist) - MIT/X11 178 | * [parseurl@1.3.1](https://github.com/pillarjs/parseurl) - MIT 179 | * [path-to-regexp@0.1.7](https://github.com/component/path-to-regexp) - MIT 180 | * [pinkie-promise@2.0.1](https://github.com/floatdrop/pinkie-promise) - MIT 181 | * [pinkie@2.0.4](https://github.com/floatdrop/pinkie) - MIT 182 | * [platform@1.3.0](https://github.com/bestiejs/platform.js) - MIT 183 | * [process-nextick-args@1.0.7](https://github.com/calvinmetcalf/process-nextick-args) - MIT 184 | * [promise@2.0.0](https://github.com/then/promise) - MIT 185 | * [promise@6.1.0](https://github.com/then/promise) - MIT 186 | * [proxy-addr@1.0.10](https://github.com/jshttp/proxy-addr) - MIT 187 | * [qs@4.0.0](https://github.com/hapijs/qs) - BSD-3-Clause 188 | * [qs@6.0.2](https://github.com/ljharb/qs) - BSD-3-Clause 189 | * [random-bytes@1.0.0](https://github.com/crypto-utils/random-bytes) - MIT 190 | * [range-parser@1.0.3](https://github.com/jshttp/range-parser) - MIT 191 | * [raw-body@2.1.7](https://github.com/stream-utils/raw-body) - MIT 192 | * [readable-stream@2.0.6](https://github.com/nodejs/readable-stream) - MIT 193 | * [regexp-quote@0.0.0](https://github.com/dbrock/node-regexp-quote) - MIT 194 | * [repeat-string@1.5.4](https://github.com/jonschlinkert/repeat-string) - MIT 195 | * [request@2.69.0](https://github.com/request/request) - Apache-2.0 196 | * [right-align@0.1.3](https://github.com/jonschlinkert/right-align) - MIT 197 | * [rndm@1.2.0](https://github.com/crypto-utils/rndm) - MIT 198 | * [sanitize-html@1.13.0](https://github.com/punkave/sanitize-html) - MIT 199 | * [send@0.13.1](https://github.com/pillarjs/send) - MIT 200 | * [send@0.13.2](https://github.com/pillarjs/send) - MIT 201 | * [serve-favicon@2.3.0](https://github.com/expressjs/serve-favicon) - MIT 202 | * [serve-static@1.10.3](https://github.com/expressjs/serve-static) - MIT 203 | * [sntp@1.0.9](https://github.com/hueniverse/sntp) - BSD 204 | * [source-map@0.1.43](https://github.com/mozilla/source-map) - BSD 205 | * [source-map@0.4.4](https://github.com/mozilla/source-map) - BSD-3-Clause 206 | * [source-map@0.5.6](https://github.com/mozilla/source-map) - BSD-3-Clause 207 | * [sshpk@1.10.1](https://github.com/arekinath/node-sshpk) - MIT 208 | * [statuses@1.2.1](https://github.com/jshttp/statuses) - MIT 209 | * [statuses@1.3.0](https://github.com/jshttp/statuses) - MIT 210 | * [string_decoder@0.10.31](https://github.com/rvagg/string_decoder) - MIT 211 | * [stringstream@0.0.5](https://github.com/mhart/StringStream) - MIT 212 | * [strip-ansi@3.0.1](https://github.com/chalk/strip-ansi) - MIT 213 | * [supports-color@2.0.0](https://github.com/chalk/supports-color) - MIT 214 | * [tough-cookie@2.2.2](https://github.com/SalesforceEng/tough-cookie) - BSD-3-Clause 215 | * [transformers@2.1.0](https://github.com/ForbesLindesay/transformers) - MIT 216 | * [tsscmp@1.0.5](https://github.com/suryagh/tsscmp) - MIT 217 | * [tunnel-agent@0.4.3](https://github.com/mikeal/tunnel-agent) - Apache-2.0 218 | * [tweetnacl@0.14.3](https://github.com/dchest/tweetnacl-js) - Public Domain 219 | * [type-is@1.6.13](https://github.com/jshttp/type-is) - MIT 220 | * [uglify-js@2.2.5](https://github.com/mishoo/UglifyJS2) - BSD 221 | * [uglify-js@2.7.3](https://github.com/mishoo/UglifyJS2) - BSD-2-Clause 222 | * [uglify-to-browserify@1.0.2](https://github.com/ForbesLindesay/uglify-to-browserify) - MIT 223 | * [uid-safe@2.1.1](https://github.com/crypto-utils/uid-safe) - MIT 224 | * [unpipe@1.0.0](https://github.com/stream-utils/unpipe) - MIT 225 | * [util-deprecate@1.0.2](https://github.com/TooTallNate/util-deprecate) - MIT 226 | * [utils-merge@1.0.0](https://github.com/jaredhanson/utils-merge) - MIT 227 | * [validator@5.7.0](https://github.com/chriso/validator.js) - MIT 228 | * [vary@1.0.1](https://github.com/jshttp/vary) - MIT 229 | * [verror@1.3.6](https://github.com/davepacheco/node-verror) - MIT* 230 | * [void-elements@2.0.1](https://github.com/hemanth/void-elements) - MIT 231 | * [window-size@0.1.0](https://github.com/jonschlinkert/window-size) - MIT 232 | * [with@4.0.3](https://github.com/ForbesLindesay/with) - MIT 233 | * [wordwrap@0.0.2](https://github.com/substack/node-wordwrap) - MIT/X11 234 | * [wordwrap@0.0.3](https://github.com/substack/node-wordwrap) - MIT 235 | * [x-xss-protection@1.0.0](https://github.com/helmetjs/x-xss-protection) - MIT 236 | * [xtend@4.0.1](https://github.com/Raynos/xtend) - MIT 237 | * [yargs@3.10.0](https://github.com/bcoe/yargs) - MIT 238 | --------------------------------------------------------------------------------