From ca1be7b5018ede6c4a6901923e51ff2e94e45bba Mon Sep 17 00:00:00 2001 From: Matheus Fernandes Date: Fri, 25 Aug 2017 06:16:30 -0300 Subject: [PATCH] Add support for coupon codes (#747) * [BREAKING] `now domains`: remove `--config` flag Needed so we can introduce `--coupon` * Remove test code * Fix the amount of erased lines upon error * Make it async * Add option to clear the output after adding the card * Tweak success message * Add support for coupon codes --- bin/domains/buy.js | 55 ++++++++++++++++++++++++---- bin/now-billing-add.js | 19 ++++++++-- bin/now-domains.js | 14 ++----- lib/domains.js | 39 ++++++++++++++++---- lib/utils/domains/treat-buy-error.js | 4 ++ 5 files changed, 102 insertions(+), 29 deletions(-) diff --git a/bin/domains/buy.js b/bin/domains/buy.js index 40fd247..c3082d2 100644 --- a/bin/domains/buy.js +++ b/bin/domains/buy.js @@ -5,14 +5,14 @@ const wait = require('../../lib/utils/output/wait') const cmd = require('../../lib/utils/output/cmd') const param = require('../../lib/utils/output/param') const info = require('../../lib/utils/output/info') -const uid = require('../../lib/utils/output/uid') const success = require('../../lib/utils/output/success') const stamp = require('../../lib/utils/output/stamp') const promptBool = require('../../lib/utils/input/prompt-bool') const eraseLines = require('../../lib/utils/output/erase-lines') const treatBuyError = require('../../lib/utils/domains/treat-buy-error') +const NowCreditCards = require('../../lib/credit-cards') -module.exports = async function({ domains, args, currentTeam, user }) { +module.exports = async function({ domains, args, currentTeam, user, coupon }) { const name = args[0] let elapsed @@ -21,14 +21,54 @@ module.exports = async function({ domains, args, currentTeam, user }) { } const nameParam = param(name) - elapsed = stamp() - let stopSpinner = wait(`Checking availability for ${nameParam}`) + let stopSpinner let price let period + let validCoupon try { + if (coupon) { + stopSpinner = wait(`Validating coupon ${param(coupon)}`) + const creditCards = new NowCreditCards({ + apiUrl: domains._agent._url, + token: domains._token, + debug: domains._debug, + currentTeam + }) + const [couponInfo, { cards }] = await Promise.all([ + domains.coupon(coupon), + creditCards.ls() + ]) + stopSpinner() + + if (!couponInfo.isValid) { + return error(`The coupon ${param(coupon)} is invalid`) + } + + if (!couponInfo.canBeUsed) { + return error(`The coupon ${param(coupon)} has already been used`) + } + + validCoupon = true + + if (cards.length === 0) { + info( + 'You have no credit cards on file. Please add one in order to claim your free domain' + ) + info(`Your card will ${bold('not')} be charged`) + + await require('../now-billing-add')({ + creditCards, + currentTeam, + user, + clear: true + }) + } + } + elapsed = stamp() + stopSpinner = wait(`Checking availability for ${nameParam}`) const json = await domains.price(name) - price = json.price + price = validCoupon ? 0 : json.price period = json.period } catch (err) { stopSpinner() @@ -61,9 +101,8 @@ module.exports = async function({ domains, args, currentTeam, user }) { stopSpinner = wait('Purchasing') elapsed = stamp() - let domain try { - domain = await domains.buy(name) + await domains.buy({ name, coupon }) } catch (err) { stopSpinner() return treatBuyError(err) @@ -71,7 +110,7 @@ module.exports = async function({ domains, args, currentTeam, user }) { stopSpinner() - success(`Domain purchased and created ${uid(domain.uid)} ${elapsed()}`) + success(`Domain ${nameParam} purchased ${elapsed()}`) info( `You may now use your domain as an alias to your deployments. Run ${cmd( 'now alias --help' diff --git a/bin/now-billing-add.js b/bin/now-billing-add.js index 537663f..c1068dd 100644 --- a/bin/now-billing-add.js +++ b/bin/now-billing-add.js @@ -19,7 +19,12 @@ function expDateMiddleware(data) { return data } -module.exports = function({ creditCards, currentTeam, user }) { +module.exports = async function({ + creditCards, + currentTeam, + user, + clear = false +}) { const state = { error: undefined, cardGroupLabel: `> ${chalk.bold( @@ -221,6 +226,10 @@ module.exports = function({ creditCards, currentTeam, user }) { address1: state.address1.value }) stopSpinner() + if (clear) { + const linesToClear = state.error ? 15 : 14 + process.stdout.write(ansiEscapes.eraseLines(linesToClear)) + } success( `${state.cardNumber .brand} ending in ${res.last4} was added to ${chalk.bold( @@ -229,7 +238,7 @@ module.exports = function({ creditCards, currentTeam, user }) { ) } catch (err) { stopSpinner() - const linesToClear = state.error ? 13 : 12 + const linesToClear = state.error ? 15 : 14 process.stdout.write(ansiEscapes.eraseLines(linesToClear)) state.error = `${chalk.red( '> Error!' @@ -238,5 +247,9 @@ module.exports = function({ creditCards, currentTeam, user }) { } } - render().catch(console.error) + try { + await render() + } catch (err) { + console.erorr(err) + } } diff --git a/bin/now-domains.js b/bin/now-domains.js index ed52383..9022b73 100755 --- a/bin/now-domains.js +++ b/bin/now-domains.js @@ -22,11 +22,11 @@ const toHost = require('../lib/to-host') const { handleError, error } = require('../lib/error') const argv = minimist(process.argv.slice(2), { - string: ['config', 'token'], + string: ['coupon', 'token'], boolean: ['help', 'debug', 'external', 'force'], alias: { help: 'h', - config: 'c', + coupon: 'c', debug: 'd', external: 'e', force: 'f', @@ -44,9 +44,6 @@ const help = () => { ${chalk.dim('Options:')} -h, --help Output usage information - -c ${chalk.bold.underline('FILE')}, --config=${chalk.bold.underline( - 'FILE' - )} Config file -d, --debug Debug mode [off] -e, --external Use external DNS server -f, --force Skip DNS verification @@ -142,10 +139,6 @@ const help = () => { const debug = argv.debug const apiUrl = argv.url || 'https://api.zeit.co' -if (argv.config) { - cfg.setConfigFile(argv.config) -} - if (argv.help || !subcommand) { help() exit(0) @@ -345,7 +338,8 @@ async function run({ token, config: { currentTeam, user } }) { domains: domain, args, currentTeam, - user + user, + coupon: argv.coupon }) break } diff --git a/lib/domains.js b/lib/domains.js index 246a644..4aff710 100644 --- a/lib/domains.js +++ b/lib/domains.js @@ -197,18 +197,12 @@ module.exports = class Domains extends Now { }) } - async buy(name) { + async buy({ name, coupon }) { if (!name) { throw new Error('`name` is not defined') } - const rawBody = { name } - - if (name.startsWith('test')) { - rawBody.dev = true - } - - const body = JSON.stringify(rawBody) + const body = JSON.stringify({ name, coupon }) return this.retry(async (bail, attempt) => { if (this._debug) { @@ -238,4 +232,33 @@ module.exports = class Domains extends Now { return json }) } + + async coupon(coupon) { + if (!coupon) { + throw new Error('`coupon` is not defined') + } + + const query = encodeQuery({ coupon }) + + return this.retry(async (bail, attempt) => { + if (this._debug) { + console.time(`> [debug] #${attempt} GET /domains/buy?${query}`) + } + + const res = await this._fetch(`/domains/buy?${query}`) + const json = await res.json() + + if (res.status !== 200) { + const e = new Error(json.error.message) + e.code = json.error.code + return bail(e) + } + + if (this._debug) { + console.timeEnd(`> [debug] #${attempt} GET /domains/buy?${query}`) + } + + return json + }) + } } diff --git a/lib/utils/domains/treat-buy-error.js b/lib/utils/domains/treat-buy-error.js index fbabfe0..2b9ff53 100644 --- a/lib/utils/domains/treat-buy-error.js +++ b/lib/utils/domains/treat-buy-error.js @@ -18,6 +18,10 @@ module.exports = function(err) { error('Purchase failed – Unexpected error') break } + case 'forbidden_premium': { + error('A coupon cannot be used to register a premium domain') + break + } default: { error(err.message) }