You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
242 lines
7.0 KiB
242 lines
7.0 KiB
#!/usr/bin/env node
|
|
|
|
// Packages
|
|
const ansiEscapes = require('ansi-escapes')
|
|
const chalk = require('chalk')
|
|
const ccValidator = require('credit-card')
|
|
|
|
// Ours
|
|
const textInput = require('../lib/utils/input/text')
|
|
const countries = require('../lib/utils/billing/country-list')
|
|
const cardBrands = require('../lib/utils/billing/card-brands')
|
|
const geocode = require('../lib/utils/billing/geocode')
|
|
const success = require('../lib/utils/output/success')
|
|
const wait = require('../lib/utils/output/wait')
|
|
const { tick } = require('../lib/utils/output/chars')
|
|
const rightPad = require('../lib/utils/output/right-pad')
|
|
|
|
function expDateMiddleware(data) {
|
|
return data
|
|
}
|
|
|
|
module.exports = function({ creditCards, currentTeam, user }) {
|
|
const state = {
|
|
error: undefined,
|
|
cardGroupLabel: `> ${chalk.bold(
|
|
`Enter your card details for ${chalk.bold(
|
|
(currentTeam && currentTeam.slug) || user.username || user.email
|
|
)}`
|
|
)}`,
|
|
|
|
name: {
|
|
label: rightPad('Full Name', 12),
|
|
placeholder: 'John Appleseed',
|
|
validateValue: data => data.trim().length > 0
|
|
},
|
|
|
|
cardNumber: {
|
|
label: rightPad('Number', 12),
|
|
mask: 'cc',
|
|
placeholder: '#### #### #### ####',
|
|
validateKeypress: (data, value) => /\d/.test(data) && value.length < 19,
|
|
validateValue: data => {
|
|
data = data.replace(/ /g, '')
|
|
const type = ccValidator.determineCardType(data)
|
|
if (!type) {
|
|
return false
|
|
}
|
|
return ccValidator.isValidCardNumber(data, type)
|
|
}
|
|
},
|
|
|
|
ccv: {
|
|
label: rightPad('CCV', 12),
|
|
mask: 'ccv',
|
|
placeholder: '###',
|
|
validateValue: data => {
|
|
const brand = state.cardNumber.brand.toLowerCase()
|
|
return ccValidator.doesCvvMatchType(data, brand)
|
|
}
|
|
},
|
|
|
|
expDate: {
|
|
label: rightPad('Exp. Date', 12),
|
|
mask: 'expDate',
|
|
placeholder: 'mm / yyyy',
|
|
middleware: expDateMiddleware,
|
|
validateValue: data => !ccValidator.isExpired(...data.split(' / '))
|
|
},
|
|
|
|
addressGroupLabel: `\n> ${chalk.bold('Enter your billing address')}`,
|
|
|
|
country: {
|
|
label: rightPad('Country', 12),
|
|
async autoComplete(value) {
|
|
for (const country in countries) {
|
|
if (!Object.hasOwnProperty.call(countries, country)) {
|
|
continue
|
|
}
|
|
|
|
if (country.startsWith(value)) {
|
|
return country.substr(value.length)
|
|
}
|
|
|
|
const lowercaseCountry = country.toLowerCase()
|
|
const lowercaseValue = value.toLowerCase()
|
|
|
|
if (lowercaseCountry.startsWith(lowercaseValue)) {
|
|
return lowercaseCountry.substr(value.length)
|
|
}
|
|
}
|
|
|
|
return false
|
|
},
|
|
validateValue: value => {
|
|
for (const country in countries) {
|
|
if (!Object.hasOwnProperty.call(countries, country)) {
|
|
continue
|
|
}
|
|
|
|
if (country.toLowerCase() === value.toLowerCase()) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
},
|
|
|
|
zipCode: {
|
|
label: rightPad('ZIP', 12),
|
|
validadeKeypress: data => data.trim().length > 0,
|
|
validateValue: data => data.trim().length > 0
|
|
},
|
|
|
|
state: {
|
|
label: rightPad('State', 12),
|
|
validateValue: data => data.trim().length > 0
|
|
},
|
|
|
|
city: {
|
|
label: rightPad('City', 12),
|
|
validateValue: data => data.trim().length > 0
|
|
},
|
|
|
|
address1: {
|
|
label: rightPad('Address', 12),
|
|
validateValue: data => data.trim().length > 0
|
|
}
|
|
}
|
|
|
|
async function render() {
|
|
for (const key in state) {
|
|
if (!Object.hasOwnProperty.call(state, key)) {
|
|
continue
|
|
}
|
|
const piece = state[key]
|
|
if (typeof piece === 'string') {
|
|
console.log(piece)
|
|
} else if (typeof piece === 'object') {
|
|
let result
|
|
try {
|
|
/* eslint-disable no-await-in-loop */
|
|
result = await textInput({
|
|
label: '- ' + piece.label,
|
|
initialValue: piece.initialValue || piece.value,
|
|
placeholder: piece.placeholder,
|
|
mask: piece.mask,
|
|
validateKeypress: piece.validateKeypress,
|
|
validateValue: piece.validateValue,
|
|
autoComplete: piece.autoComplete
|
|
})
|
|
|
|
piece.value = result
|
|
|
|
if (key === 'cardNumber') {
|
|
let brand = cardBrands[ccValidator.determineCardType(result)]
|
|
piece.brand = brand
|
|
|
|
if (brand === 'American Express') {
|
|
state.ccv.placeholder = '#'.repeat(4)
|
|
} else {
|
|
state.ccv.placeholder = '#'.repeat(3)
|
|
}
|
|
|
|
brand = chalk.cyan(`[${brand}]`)
|
|
const masked = chalk.gray('#### '.repeat(3)) + result.split(' ')[3]
|
|
process.stdout.write(
|
|
`${chalk.cyan(tick)} ${piece.label}${masked} ${brand}\n`
|
|
)
|
|
} else if (key === 'ccv') {
|
|
process.stdout.write(
|
|
`${chalk.cyan(tick)} ${piece.label}${'*'.repeat(result.length)}\n`
|
|
)
|
|
} else if (key === 'expDate') {
|
|
let text = result.split(' / ')
|
|
text = text[0] + chalk.gray(' / ') + text[1]
|
|
process.stdout.write(`${chalk.cyan(tick)} ${piece.label}${text}\n`)
|
|
} else if (key === 'zipCode') {
|
|
const stopSpinner = wait(piece.label + result)
|
|
const addressInfo = await geocode({
|
|
country: state.country.value,
|
|
zipCode: result
|
|
})
|
|
if (addressInfo.state) {
|
|
state.state.initialValue = addressInfo.state
|
|
}
|
|
if (addressInfo.city) {
|
|
state.city.initialValue = addressInfo.city
|
|
}
|
|
stopSpinner()
|
|
process.stdout.write(
|
|
`${chalk.cyan(tick)} ${piece.label}${result}\n`
|
|
)
|
|
} else {
|
|
process.stdout.write(
|
|
`${chalk.cyan(tick)} ${piece.label}${result}\n`
|
|
)
|
|
}
|
|
} catch (err) {
|
|
if (err.message === 'USER_ABORT') {
|
|
process.exit(1)
|
|
} else {
|
|
console.error(err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
console.log('') // New line
|
|
const stopSpinner = wait('Saving card')
|
|
|
|
try {
|
|
const res = await creditCards.add({
|
|
name: state.name.value,
|
|
cardNumber: state.cardNumber.value,
|
|
ccv: state.ccv.value,
|
|
expDate: state.expDate.value,
|
|
country: state.country.value,
|
|
zipCode: state.zipCode.value,
|
|
state: state.state.value,
|
|
city: state.city.value,
|
|
address1: state.address1.value
|
|
})
|
|
stopSpinner()
|
|
success(
|
|
`${state.cardNumber
|
|
.brand} ending in ${res.last4} was added to ${chalk.bold(
|
|
(currentTeam && currentTeam.slug) || user.username || user.email
|
|
)}`
|
|
)
|
|
} catch (err) {
|
|
stopSpinner()
|
|
const linesToClear = state.error ? 13 : 12
|
|
process.stdout.write(ansiEscapes.eraseLines(linesToClear))
|
|
state.error = `${chalk.red(
|
|
'> Error!'
|
|
)} ${err.message} Please make sure the info is correct`
|
|
await render()
|
|
}
|
|
}
|
|
|
|
render().catch(console.error)
|
|
}
|
|
|