makeomatic/ms-payments

View on GitHub
src/actions/sale/execute.js

Summary

Maintainability
A
2 hrs
Test Coverage
const { ActionTransport } = require('@microfleet/core');
const { HttpStatusError } = require('common-errors');
const Promise = require('bluebird');
const render = require('ms-mailer-templates');
const pick = require('lodash/pick');

// helpers
const key = require('../../redis-key');
const { serialize } = require('../../utils/redis');
const { SALES_DATA_PREFIX, TRANSACTION_TYPE_SALE, TRANSACTION_TYPE_3D } = require('../../constants');
const { saveCommon, parseSale, getOwner } = require('../../utils/transactions');
const { payment: { execute: executePayment }, handleError } = require('../../utils/paypal');

// parse json
function parseInput(data, fallback) {
  return data ? JSON.parse(data) : fallback;
}

// send paypal request
// eslint-disable-next-line camelcase
function sendRequest({ payer_id, payment_id }) {
  const { config } = this;
  return executePayment(payment_id, { payer_id }, config.paypal)
    .catch(handleError);
}

// save data to redis
function updateRedis(sale) {
  const { redis } = this;
  const { state } = sale;

  if (state !== 'approved') {
    throw new HttpStatusError(412, `paypal returned "${sale.state}" on the sale`);
  }

  const { id } = sale;
  const saleKey = key(SALES_DATA_PREFIX, id);
  const payer = sale.payer.payer_info.email && sale.payer.payer_info.email;
  const owner = getOwner(sale);

  const updateData = {
    sale,
    create_time: new Date(sale.create_time).getTime(),
    update_time: new Date(sale.update_time).getTime(),
  };

  if (payer) {
    updateData.payer = payer;
  }

  if (owner) {
    updateData.owner = owner;
  }

  const parsedSale = parseSale(sale, owner);

  const updateTransaction = redis
    .pipeline()
    .hmget(saleKey, 'owner', 'cart')
    .hmset(saleKey, serialize(updateData))
    .exec()
    .spread((resp) => {
      const [err, recordedData] = resp;
      if (err) {
        throw err;
      }

      return {
        sale,
        username: parseInput(recordedData[0], owner),
        cart: parseInput(recordedData[1], null),
      };
    });

  const updateCommon = Promise
    .bind(this, parsedSale)
    .then(saveCommon);

  return Promise.props({
    parsedSale,
    sale: updateTransaction,
    updateCommon,
  });
}

// update user's metadata during model's sale
function updateMetadata({ sale: { sale, username, cart }, parsedSale }) {
  if (parsedSale.type !== TRANSACTION_TYPE_SALE) {
    return { sale, cart, parsedSale };
  }

  const { amqp, config } = this;
  const { users: { prefix, postfix, audience } } = config;
  const models = sale.transactions[0].item_list.items[0].quantity;
  const path = `${prefix}.${postfix.updateMetadata}`;

  const updateRequest = {
    username,
    audience,
    metadata: {
      $incr: {
        models,
      },
    },
  };

  return amqp
    .publishAndWait(path, updateRequest)
    .return({ sale, cart, parsedSale });
}

// send email
function sendCartEmail({ sale, cart, parsedSale }) {
  if (parsedSale.type !== TRANSACTION_TYPE_3D || !cart) {
    return sale;
  }

  const { mailer, config } = this;
  const cartConfig = config.cart;
  const emailData = pick(cartConfig, ['from', 'to', 'subject']);
  return render(cartConfig.template, cart)
    .then((html) => {
      const email = { ...emailData, html };
      return mailer
        .send(cartConfig.emailAccount, email)
        .return(sale);
    });
}

/**
 * @api {amqp} <prefix>.sale.execute Execute sale
 * @apiVersion 1.0.0
 * @apiName saleExecute
 * @apiGroup Sale
 *
 * @apiDescription Executes sale
 *
 * @apiSchema {jsonschema=sale/create.json} apiRequest
 * @apiSchema {jsonschema=response/sale/create.json} apiResponse
 */
function saleExecute({ params: message }) {
  return Promise
    .bind(this, message)
    .then(sendRequest)
    .then(updateRedis)
    .then(updateMetadata)
    .then(sendCartEmail);
}

saleExecute.transports = [ActionTransport.amqp];

module.exports = saleExecute;