makeomatic/ms-payments

View on GitHub
src/actions/plan/create.js

Summary

Maintainability
B
6 hrs
Test Coverage
const { ActionTransport } = require('@microfleet/core');
const Promise = require('bluebird');
const Errors = require('common-errors');
const merge = require('lodash/merge');
const cloneDeep = require('lodash/cloneDeep');
const reduce = require('lodash/reduce');
const find = require('lodash/find');

// helpers
const key = require('../../redis-key');
const { cleanupCache } = require('../../list-utils');
const { PLANS_DATA, PLANS_INDEX, FREE_PLAN_ID } = require('../../constants');
const {
  plan: { create: billingPlanCreate }, handleError, is, states: { active },
} = require('../../utils/paypal');
const { serialize } = require('../../utils/redis');
const { createJoinPlans } = require('../../utils/plans');

function sendRequest(config, message) {
  const defaultMerchantPreferences = {
    return_url: config.urls.plan_return,
    cancel_url: config.urls.plan_cancel,
    auto_bill_amount: 'YES',
    initial_fail_amount_action: 'CANCEL',
  };

  // setup default merchant preferences
  const { plan } = message;
  const { merchant_preferences: merchatPref, payment_definitions: paymentDefinitions } = plan;
  plan.merchant_preferences = merge(defaultMerchantPreferences, merchatPref || {});

  // divide single plan definition into as many as payment_definitions present
  const plans = Promise.map(paymentDefinitions, (definition) => {
    const partialPlan = {
      ...cloneDeep(plan),
      name: `${plan.name}-${definition.frequency.toLowerCase()}`,
      payment_definitions: [definition],
    };

    return billingPlanCreate(partialPlan, config.paypal).catch(handleError);
  });

  if (is.active(message.plan) === false) {
    return plans;
  }

  // activate the plan if we requested it
  return plans.map(async (planData) => {
    const { id } = planData;
    planData.state = active;
    await this.dispatch('plan.state', { params: { id, state: active } });
    return planData;
  });
}

function createSaveToRedis(redis, message) {
  return function saveToRedis(data) {
    const { plan, plans } = data;
    const { meta, level, title } = message;
    const aliasedId = message.alias || plan.id;
    const hidden = message.hidden || false;

    const pipeline = redis.pipeline();
    const planKey = key(PLANS_DATA, aliasedId);
    const plansData = reduce(plans, (a, p) => {
      const { frequency } = p.payment_definitions[0];
      a[frequency.toLowerCase()] = p.id;
      return a;
    }, {});

    pipeline.sadd(PLANS_INDEX, aliasedId);

    const subscriptions = message.subscriptions.map((subscription) => {
      subscription.definition = find(plan.payment_definitions, (item) => (
        item.frequency.toLowerCase() === subscription.name
      ));
      return subscription;
    });

    const saveDataFull = {
      plan: {
        ...plan,
        hidden,
      },
      id: plan.id,
      subs: subscriptions,
      type: plan.type,
      state: plan.state,
      name: plan.name,
      hidden,
      ...plansData,
      title,
      meta,
      level,
    };

    if (message.alias !== null && message.alias !== undefined) {
      saveDataFull.alias = message.alias;
    }

    const serializedData = serialize(saveDataFull);
    pipeline.hmset(planKey, serializedData);

    if (plan.id !== aliasedId) {
      pipeline.hmset(key(PLANS_DATA, plan.id), serializedData);
    }

    plans.forEach((planData) => {
      const saveData = {
        plan: {
          ...planData,
          hidden,
        },
        id: planData.id,
        subs: [find(subscriptions, { name: planData.payment_definitions[0].frequency.toLowerCase() })],
        type: planData.type,
        state: planData.state,
        name: planData.name,
        hidden,
        title,
        meta,
        level,
      };

      if (message.alias) {
        saveData.alias = message.alias;
      }

      pipeline.hmset(key(PLANS_DATA, planData.id), serialize(saveData));
    });

    return pipeline.exec().return(saveDataFull);
  };
}

/**
 * @api {amqp} <prefix>.plan.create Create plan
 * @apiVersion 1.0.0
 * @apiName planCreate
 * @apiGroup Plan
 *
 * @apiDescription Creates new plan
 *
 * @apiSchema {jsonschema=plan/create.json} apiRequest
 * @apiSchema {jsonschema=response/plan/create.json} apiResponse
 */
/**
 * Creates paypal plan with a special case for a free plan
 * @param  {Object} message
 * @return {Promise}
 */
function planCreate({ params: message }) {
  const { config, redis } = this;
  const { alias } = message;
  const saveToRedis = createSaveToRedis(redis, message);
  let promise = Promise.bind(this);

  if (alias && alias !== FREE_PLAN_ID) {
    promise = promise.then(() => {
      return redis.sismember(PLANS_INDEX, alias).then((isMember) => {
        if (isMember === 1) {
          throw new Errors.HttpStatusError(409, `plan ${alias} already exists`);
        }

        return null;
      });
    });
  }

  // this is a free plan, don't put it on paypal
  if (alias === FREE_PLAN_ID) {
    message.plan.id = alias;
    promise = promise.return({ plan: message.plan, plans: [] });
  } else {
    promise = promise.return([config, message]).spread(sendRequest).then(createJoinPlans(message));
  }

  return promise
    .then(saveToRedis)
    .tap(() => cleanupCache.call(this, PLANS_INDEX));
}

planCreate.transports = [ActionTransport.amqp];

module.exports = planCreate;