text: import Stripe from 'stripe';
  import debug from 'debug';
  import crypto from 'crypto';
  import { isEmail, isNumeric } from 'validator';
  
  import {
    getAsyncPaypalToken,
    verifyWebHook,
    updateUser,
    verifyWebHookType
  } from '../utils/donation';
  import {
    durationKeysConfig,
    donationOneTimeConfig,
    donationSubscriptionConfig
  } from '../../../config/donation-settings';
  import keys from '../../../config/secrets';
  
  const log = debug('fcc:boot:donate');
  
  export default function donateBoot(app, done) {
    let stripe = false;
    const api = app.loopback.Router();
    const hooks = app.loopback.Router();
    const donateRouter = app.loopback.Router();
  
    const subscriptionPlans = Object.keys(
      donationSubscriptionConfig.plans
    ).reduce(
      (prevDuration, duration) =>
        prevDuration.concat(
          donationSubscriptionConfig.plans[duration].reduce(
            (prevAmount, amount) =>
              prevAmount.concat({
                amount: amount,
                interval: duration,
                product: {
                  name: `${
                    donationSubscriptionConfig.duration[duration]
                  } Donation to freeCodeCamp.org - Thank you ($${amount / 100})`,
                  metadata: {
                    /* eslint-disable camelcase */
                    sb_service: `freeCodeCamp.org`,
                    sb_tier: `${
                      donationSubscriptionConfig.duration[duration]
                    } $${amount / 100} Donation`
                    /* eslint-enable camelcase */
                  }
                },
                currency: 'usd',
                id: `${donationSubscriptionConfig.duration[
                  duration
                ].toLowerCase()}-donation-${amount}`
              }),
            []
          )
        ),
      []
    );
  
    function validStripeForm(amount, duration, email) {
      return isEmail('' + email) &&
        isNumeric('' + amount) &&
        durationKeysConfig.includes(duration) &&
        duration === 'onetime'
        ? donationOneTimeConfig.includes(amount)
        : donationSubscriptionConfig.plans[duration];
    }
  
    function connectToStripe() {
      return new Promise(function(resolve) {
        // connect to stripe API
        stripe = Stripe(keys.stripe.secret);
        // parse stripe plans
        stripe.plans.list({}, function(err, stripePlans) {
          if (err) {
            throw err;
          }
          const requiredPlans = subscriptionPlans.map(plan => plan.id);
          const availablePlans = stripePlans.data.map(plan => plan.id);
          if (process.env.STRIPE_CREATE_PLANS === 'true') {
            requiredPlans.forEach(requiredPlan => {
              if (!availablePlans.includes(requiredPlan)) {
                createStripePlan(
                  subscriptionPlans.find(plan => plan.id === requiredPlan)
                );
              }
            });
          } else {
            log(`Skipping plan creation`);
          }
        });
        resolve();
      });
    }
  
    function createStripePlan(plan) {
      log(`Creating subscription plan: ${plan.product.name}`);
      stripe.plans.create(plan, function(err) {
        if (err) {
          log(err);
        }
        log(`Created plan with plan id: ${plan.id}`);
        return;
      });
    }
  
    function createStripeDonation(req, res) {
      const { user, body } = req;
  
      const {
        amount,
        duration,
        token: { email, id }
      } = body;
  
      if (!validStripeForm(amount, duration, email)) {
        return res.status(500).send({
          error: 'The donation form had invalid values for this submission.'
        });
      }
  
      let donatingUser = {};
      let donation = {
        email,
        amount,
        duration,
        provider: 'stripe',
        startDate: new Date(Date.now()).toISOString()
      };
  
      const createCustomer = user => {
        donatingUser = user;
        return stripe.customers.create({
          email,
          card: id
        });
      };
  
      const createSubscription = customer => {
        donation.customerId = customer.id;
        return stripe.subscriptions.create({
          customer: customer.id,
          items: [
            {
              plan: `${donationSubscriptionConfig.duration[
                duration
              ].toLowerCase()}-donation-${amount}`
            }
          ]
        });
      };
  
      const createOneTimeCharge = customer => {
        donation.customerId = customer.id;
        return stripe.charges.create({
          amount: amount,
          currency: 'usd',
          customer: customer.id
        });
      };
  
      const createAsyncUserDonation = () => {
        donatingUser
          .createDonation(donation)
          .toPromise()
          .catch(err => {
            throw new Error(err);
          });
      };
  
      return Promise.resolve(user)
        .then(nonDonatingUser => {
          const { isDonating } = nonDonatingUser;
          if (isDonating) {
            throw {
              message: `User already has active donation(s).`,
              type: 'AlreadyDonatingError'
            };
          }
          return nonDonatingUser;
        })
        .then(createCustomer)
        .then(customer => {
          return duration === 'onetime'
            ? createOneTimeCharge(customer).then(charge => {
                donation.subscriptionId = 'one-time-charge-prefix-' + charge.id;
                return res.send(charge);
              })
            : createSubscription(customer).then(subscription => {
                donation.subscriptionId = subscription.id;
                return res.send(subscription);
              });
        })
        .then(createAsyncUserDonation)
        .catch(err => {
          if (
            err.type === 'StripeCardError' ||
            err.type === 'AlreadyDonatingError'
          ) {
            return res.status(402).send({ error: err.message });
          }
          return res
            .status(500)
            .send({ error: 'Donation failed due to a server error.' });
        });
    }
  
    function createHmacHash(req, res) {
      const { user, body } = req;
  
      if (!user || !body) {
        return res
          .status(500)
          .send({ error: 'User must be signed in for this request.' });
      }
  
      const { email } = body;
  
      if (!isEmail('' + email)) {
        return res
          .status(500)
          .send({ error: 'The email is invalid for this request.' });
      }
  
      if (!user.donationEmails.includes(email)) {
        return res.status(500).send({
          error: `User does not have the email: ${email} associated with their donations.`
        });
      }
  
      log(`creating HMAC hash for ${email}`);
      return Promise.resolve(email)
        .then(email =>
          crypto
            .createHmac('sha256', keys.servicebot.hmacKey)
            .update(email)
            .digest('hex')
        )
        .then(hash => res.status(200).json({ hash }))
        .catch(() =>
          res
            .status(500)
            .send({ error: 'Donation failed due to a server error.' })
        );
    }
  
    function addDonation(req, res) {
      const { user, body } = req;
  
      if (!user || !body) {
        return res
          .status(500)
          .send({ error: 'User must be signed in for this request.' });
      }
      return Promise.resolve(req)
        .then(
          user.updateAttributes({
            isDonating: true
          })
        )
        .then(() => res.status(200).json({ isDonating: true }))
        .catch(err => {
          log(err.message);
          return res.status(500).send({
            type: 'danger',
            message: 'Something went wrong.'
          });
        });
    }
  
    function updatePaypal(req, res) {
      const { headers, body } = req;
      return Promise.resolve(req)
        .then(verifyWebHookType)
        .then(getAsyncPaypalToken)
        .then(token => verifyWebHook(headers, body, token, keys.paypal.webhookId))
        .then(hookBody => updateUser(hookBody, app))
        .catch(err => {
          // Todo: This probably need to be thrown and caught in error handler
          log(err.message);
        })
        .finally(() => res.status(200).json({ message: 'received paypal hook' }));
    }
  
    const stripeKey = keys.stripe.public;
    const secKey = keys.stripe.secret;
    const paypalKey = keys.paypal.client;
    const paypalSec = keys.paypal.secret;
    const hmacKey = keys.servicebot.hmacKey;
    const stripeSecretInvalid = !secKey || secKey === 'sk_from_stripe_dashboard';
    const stripPublicInvalid =
      !stripeKey || stripeKey === 'pk_from_stripe_dashboard';
  
    const paypalSecretInvalid =
      !paypalKey || paypalKey === 'id_from_paypal_dashboard';
    const paypalPublicInvalid =
      !paypalSec || paypalSec === 'secret_from_paypal_dashboard';
    const hmacKeyInvalid =
      !hmacKey || hmacKey === 'secret_key_from_servicebot_dashboard';
    const paypalInvalid = paypalPublicInvalid || paypalSecretInvalid;
    const stripeInvalid = stripeSecretInvalid || stripPublicInvalid;
  
    if (stripeInvalid || paypalInvalid || hmacKeyInvalid) {
      if (process.env.FREECODECAMP_NODE_ENV === 'production') {
        throw new Error('Donation API keys are required to boot the server!');
      }
      log('Donation disabled in development unless ALL test keys are provided');
      done();
    } else {
      api.post('/charge-stripe', createStripeDonation);
      api.post('/create-hmac-hash', createHmacHash);
      api.post('/add-donation', addDonation);
      hooks.post('/update-paypal', updatePaypal);
      donateRouter.use('/donate', api);
      donateRouter.use('/hooks', hooks);
      app.use(donateRouter);
      connectToStripe().then(done);
    }
  }
  