Source: shopify/charge/index.js

import callerId from 'caller-id';
import { ShopIdService } from 'cpb-api';
import { isArray } from 'cpb-common';
import Datastore from 'cpb-datastore';
import Shopify from 'cpb-shopify';

export const KIND_SHOPIFY_RECURRING_APPLICATION_CHARGE = 'shopify_recurring_application_charges';
export const KIND_SHOPIFY_CURRENT_APPLICATION_CHARGE = 'shopify_current_application_charge';
/**
 * ### Recurring Application Charges
 * @module cpb-shopify/charge
 * @type {{settings: {kind: string}, getActiveChargesMonthlyTotal({test: boolean}=): Promise<number>, get(*=): Promise<Object[]>, save(*): Promise<void>,
 *   list({shopName: string, shopID: number}): Promise<{expired: Array(), active: undefined, shopName: string, shopID: number}>}}
 */
export const Charge = {
  settings: {
    kind: KIND_SHOPIFY_RECURRING_APPLICATION_CHARGE,
    kindActiveCharge: 'shopify_current_application_charge',
    kindTestCharge: 'shopify_current_test_application_charge',
  },
  /**
   * ### Get All Recurring Application Charges from Shopify
   * @param {string} shopName
   * @param {number} [shopID] - if supplied no shopID lookup would be performed
   * @param fetch
   * @return {Promise<{shopName: string, shopID: number}>} charges - charge information
   */
  async list({ shopName, shopID } = {}, fetch) {
    let client,
      id = shopID,
      name = shopName;
    //
    if (!id || !name) {
      const o = await ShopIdService.getIdName(id || name);
      id = o.id;
      name = o.name;
    }
    if (!id) {
      console.error(`[shopify/charge][${shopName}] ERROR_GET_SHOPID`, callerId.getDetailedString());
      //      return;
    }

    const result = {
        shopID: id,
        shopName: name,
      },
      saveResult = [];

    let charges;
    try {
      if (fetch) {
        client = await Shopify.connect({ shopName: name });
        charges = await client.recurringApplicationCharge.list();
      }
    } catch (error) {
      console.error(`[shopify/charge][${id},${name}][${callerId.getDetailedString()}] ERROR_SHOPIFY_CONNECT`, { error });
      result.error = error;
      charges = [];
    }

    if (!charges || !charges.length) charges = await Datastore.query({ kind: this.settings.kind, filter: { shopName } });
    if (!charges) {
      console.warn(`[shopify/charge/list][${shopID}, ${shopName}][${callerId.getDetailedString()}] ERROR_NO_CHARGES`);
      charges = [];
    }
    console.info(`[shopify/charge/list][${id},${name}] length:${charges.length}`);

    for (const charge of charges) {
      charge.shopName = name;
      charge.shopID = id;
      result[charge.status] = result[charge.status] || [];
      result[charge.status].push(charge);
      if (fetch) this.save(charge); //saveResult.push();
    }
    //    Promise.all(saveResult).catch(console.error);
    return result;
  },
  /***
   * ### Save The Charge Information into the Datastore into following kinds:
   *  *  `shopify_recurring_application_charges` - all charges for all shops. Keyed by **shopName+chargeID**
   *  * `shopify_current_application_charge` - current production charges. Keyed by **shopName**
   *  * `shopify_current_test_application_charge` - current test charges. Keyed by **shopName**
   * @param charge
   * @return {Promise<void>}
   */
  //  async
  save(charge) {
    if (!charge) throw new TypeError('!charge');
    const charges = isArray(charge) ? charge : [charge],
      kindAllCharges = this.settings.kind,
      kindActiveCharge = this.settings.kindActiveCharge, //'shopify_current_application_charge',
      kindTestCharge = this.settings.kindTestCharge; // 'shopify_current_test_application_charge';

    for (const data of charges) {
      console.log(`[shopify/charge/index][${data.shopName}] saveCharge::${data.status} - ${data.id}`);
      if (!data.shopID || !data.shopName) throw new TypeError('MISSING_SHOP_ID_OR_NAME');

      // only active charges
      if (data.status === 'active')
        Datastore.save({
          kind: data.test === true ? kindTestCharge : kindActiveCharge,
          data,
          key: Datastore.datastore.key([data.test === true ? kindTestCharge : kindActiveCharge, [charge.shopName, charge.id].join()]),
          useEmulator: process.env.USE_EMULATOR_DATASTORE,
        }).catch(console.error);

      // all charge info
      Datastore.save({
        kind: kindAllCharges,
        data,
        key: Datastore.datastore.key([kindAllCharges, [charge.shopName, charge.id].join()]),
        useEmulator: process.env.USE_EMULATOR_DATASTORE,
      }).catch(console.error);
    }
  },

  /**
   * ### Get Monthly Total For Active Charges
   * @param {?boolean} [test=undefined] - calculate for test installs
   * @returns {Promise<number>} total - monthly active charges total
   */
  async getActiveChargesMonthlyTotal({ test } = {}) {
    const kind = test ? this.settings.kindTestCharge : this.settings.kindActiveCharge,
      querySettings = { kind, selector: 'capped_amount' },
      data = await Datastore.query(querySettings);
    //    console.debug(data);
    const total = data.map(i => +i.capped_amount).reduce((previous, current) => previous + current);
    //    for (const charge of data) total += +charge.capped_amount;
    return Math.round(total * 100) / 100;
  },

  async get(filter, fetch) {
    console.debug(`[cpb-shopify/charge/get]`, { filter, fetch });
    return await this.list(filter, fetch).catch(console.error);
  },

  async getActiveCharge(idOrName, fetch) {
    if (!idOrName) throw new TypeError('!idOrName');
    const { name: shopName } = await ShopIdService.getIdName(idOrName),
      querySettings = {
        kind: this.settings.kind,
        useEmulator: process.env.USE_EMULATOR_DATASTORE,
        filter: { shopName, status: 'active' },
      };
    console.debug(`[cpb-shopify/charge.getActiveCharge]`, querySettings);

    const charges = await this.get(querySettings.filter, fetch);
    console.debug(`[shopify/charge/getActiveCharge]`, charges.active);

    return (charges.active || [])[0];
  },
};

export default Charge;