Tuesday, 28 September 2021

Find Product Prices in Google Sheets with Vlookup and Match Functions

You run a coffee shop and you are looking for a spreadsheet formula to quickly look up prices of the product that your customer has ordered. You have the price matrix stored in a Google Sheet with the names of beverages in one column and the quantity-wise prices in the adjacent columns.

When a customer selects their favorite beverage and the cup size, you can use the MATCH function to find the relative position of the column and row in the price table that matches the selected beverage and quantity. Next, use the INDEX function to find the actual price of the beverage in the selected quantity.

MATCH function in Google Sheets Price Table

In our Starbuck Coffee example, the coffee prices are stored in the range B2:B11. The customer’s beverage name (Caffè Mocha in this example) is stored in the cell G3. The following MATCH function will return the relative position of the selected beverage from the list of beverages.

=MATCH(G3, $B$2:$B$11, 0)

The third parameter of the MATCH function is set to 0 since we want the exact match and our price list is not sorted.

Similarly, the next MATCH function will return the relative position of the column that contains the price of the beverage based on the selected quantity. The cup sizes are stored in the range C2:E2. The selected cup size is stored in the cell H3.

=MATCH(H3, $B$2:$E$2, 0)

Now that we know the relative row and column position of the price value we are looking for, we can use the INDEX function to find the actual price from the table.

=INDEX($B$2:$E$11, H5, H7)

Use Vlookup with ArrayFormula and Match

For the next example, we have a customer order that contains multiple beverages, one per row. We want to find the price of each beverage and the total price of the order. Array Formulas will be a perfect fit here since we want to extend the same formula to all rows of the spreadsheet.

However, we’ll have to revisit our approach since the INDEX function used in the previous example cannot be used with Array Formulas as it cannot return multiple values. We’ll replace INDEX with a similar VLOOKUP function and combine it with the MATCH function to perform a two-way lookup (find the beverage by name and then look for the specific cup size).

The VLOOKUP function syntax, in simple English, is:

=VLOOKUP(
  What you want to look for (beverage name),
  Where you want to look for it (price table range),
  The column number containing the matching value (chosen cup size),
  Return an approximate or exact match (True or False)
)

The function will look for the beverage name in the specified price range (B2:E11) and, from the matching row, return the value of the cell in the column that corresponds to selected cup size.

The price range is not sorted so we will put FALSE for the fourth parameter.

The MATCH function will return the relative position of the column that contains the price of the selected quantity of the matching beverage:

=MATCH(
  What are you looking for (cup size),
  Where are you looking for it (cup size header range),
  0 if you want to find the exact value (default is 1)
)

If a row doesn’t contain the beverage name, the formula will return #N/A and thus we wrap the value in IFNA to prevent the formula from returning any errors.

Our final formula will thus look like:

=ARRAYFORMULA(IFNA(VLOOKUP(B14:B, $B$2:$E$11, MATCH(C14:C, $B$2:$E$2, 0), FALSE)))

VLOOKUP MATCH function

Download the Excel file - Price Lookup Sheet



from Digital Inspiration https://ift.tt/2WhPNpN

Tuesday, 14 September 2021

Improve Performance of Google Apps Script with Memoization

A folder in Google Drive contains a bunch of CSV files and you are required to write a Google Script to find a particular value in the CSV files. The solution is simple:

  1. Use the Drive API to get a list of CSV files in the specified folder.
  2. Parse the CSV files one by one using the Utilities.parseCsv() function.
  3. Read the CSV file, line by line, until the value is found and return the line number.
const findContentInCSVFiles = (folderId, searchString) => {
  const folder = DriveApp.getFolderById(folderId);
  const files = folder.getFilesByType("text/csv");

  while (files.hasNext()) {
    const file = files.next();
    const fileContent = file.getBlob().getDataAsString();
    const linesOfData = Utilities.parseCsv(fileContent, ",");

    let found = false;
    let lineNumber = 0;

    for (; lineNumber < linesOfData.length && !found; lineNumber += 1) {
      const line = linesOfData[lineNumber];
      found = line.find((element) => element === searchString);
    }

    if (found) {
      return `${searchString} found in line #${
        lineNumber + 1
      } of file ${file.getName()}`;
    }
  }
  return "String not found :(";
};

Optimize Google Script Performance

The code to read CSV files and find the required value is simple but not efficient. You’ve to perform the same expensive operation for every value that you have to search in the folder of CSV files.

Memoization is a simple optimization technique that can be used to improve the performance of your Google Apps Script code. The basic idea is that you cache the results of an expensive function call using closures. If the function is called again with the same arguments, the cached result is returned instead of calling and executing the function all over again.

const memoize = (func) => {
  // Cache for storing the previously computed results
  const cache = {};
  return (...args) => {
    // Serializer to convert N arguments to a string
    const key = JSON.stringify(args);
    if (typeof cache[key] === "undefined") {
      cache[key] = func(...args);
    }
    return cache[key];
  };
};

const memoizedFindFunction = memoize(findContentInCSVFiles);

const findContentInFiles = () => {
  const FOLDER_ID = "<<folder id>>";
  const SEARCH_STRING = "hello world!";
  const response = memoizedFindFunction(FOLDER_ID, SEARCH_STRING);
  Logger.log(resonse);
};

The memoization function is called with the arguments of the original function. The result of the function is stored in a cache and returned when the same arguments are passed again.



from Digital Inspiration https://ift.tt/3941S4L

How to Request Payments with Stripe Checkout and Google Sheets

Stripe Payment Links

Stripe payment links make it easy for you to accept credit card payments from customers anywhere in the world without even having a website. You can use the Stripe dashboard to generate payment links and then send the links over email, WhatsApp, SMS, or share them on your social media pages.

A limitation of Stripe Payment links is that you can only generate them manually. Stripe has a feature-rich API but it doesn’t allow you to generate payment links automatically.

If you are looking to generate custom payment links for Stripe in bulk and send them to your customers, you can consider using Stripe Checkout. These are payment forms hosted on the Stripe website and allow you to collect only payments your customers.

It is important to note that Stripe Checkout sessions will automatically expire after 24 hours. As an alternative, you can use the Stripe API to generate invoices and email the invoice link to your customers.

Stripe API Key

To get started, open your Stripe dashboard, go to Developers > API Keys > Created restricted API key.

Give your key a descriptive name, choose the Write permission under Checkout Sessions and click Create key.

Next, make a copy of the Stripe Google Sheet in your Google Drive. Go to Tools > Script Editor and replace the Stripe API Key with the key generated in the previous step. Then, click on the Run menu once to authorize the script with your Google Account.

Switch to the Google Sheet and you can now use the custom Google Sheets function STRIPE() to generate Stripe Checkout sessions for accepting online payments.

If you would like to generate payment links for multiple rows in the Google Sheet, just write the formula in the first row and drag the crosshairs to the other rows as show in the demo below. Array Formulas are not supported yet.

Stripe Payment Links

How Stripe Checkout Works with Google Sheets

If you are curious to know how integration of Google Sheets and Stripe works, the answer is Google Apps Script. The underlying code invokes the Stripe API with your secret API key and writes the generated checkout session links in the Google Sheet.

The custom Google Sheets function uses the built-in caching service of Apps Script to reduce latency and improve performance. The code can be extended to accept recurring payments for subscriptions.

/**
 *
 *  Author  Amit Agarwal
 *  Email   amit@labnol.org
 *  Web     https://digitalinspiration.com/
 *
 **/

const STRIPE_API_KEY = "<< Stripe API Key >>";
const STRIPE_SUCCESS_URL = "https://digitalinspiration.com";
const STRIPE_CANCEL_URL = "https://digitalinspiration.com";

/**
 * Generate Stripe payment links in Google Sheets
 *
 * @param {number} amount The amount to be paid using Stripe
 * @param {string} currency The 3-letter currency code (optional)
 * @param {string} description A short description of the item name (optional)
 * @return Stripe checkout session link
 * @customfunction
 */

const STRIPE = (amount, currency, description) => {
  const input = {
    "line_items[0][price_data][currency]": currency || "USD",
    "line_items[0][price_data][product_data][name]": description || "Name",
    "line_items[0][price_data][unit_amount]": Math.ceil(amount * 100),
    "line_items[0][quantity]": 1,
  };

  const cacheKey = JSON.stringify(input);

  const cachedLink = CacheService.getScriptCache().get(cacheKey);

  if (cachedLink) return cachedLink;

  const params = {
    cancel_url: STRIPE_CANCEL_URL,
    success_url: STRIPE_SUCCESS_URL,
    mode: "payment",
    billing_address_collection: "required",
    "payment_method_types[]": "card",
    ...input,
  };

  const payload = Object.entries(params)
    .map(([key, value]) =>
      [encodeURIComponent(key), encodeURIComponent(value)].join("=")
    )
    .join("&");

  const response = UrlFetchApp.fetch(
    "https://api.stripe.com/v1/checkout/sessions",
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${STRIPE_API_KEY}`,
        "Content-Type": "application/x-www-form-urlencoded",
      },
      payload,
      muteHttpExceptions: true,
    }
  );

  const { url, error } = JSON.parse(response);

  if (url) {
    CacheService.getScriptCache().put(cacheKey, url, 21600);
  }

  return error ? error.message : url;
};

You can use Mail Merge with Gmail to request online payments from your customers over email. You may also use Document Studio to create PDF invoices and embed the payment links directly in the customer’s invoice.



from Digital Inspiration https://ift.tt/3tCJfyt

Monday, 13 September 2021

How to Request Payments with Stripe Checkout and Google Sheets

Stripe Payment Links

Stripe payment links make it easy for you to accept credit card payments from customers anywhere in the world without even having a website. You can use the Stripe dashboard to generate payment links and then send the links over email, WhatsApp, SMS, or share them on your social media pages.

A limitation of Stripe Payment links is that you can only generate them manually. Stripe has a feature-rich API but it doesn’t allow you to generate payment links automatically.

If you are looking to generate custom payment links for Stripe in bulk and send them to your customers, you can consider using Stripe Checkout. These are payment forms hosted on the Stripe website and allow you to collect only payments your customers.

It is important to note that Stripe Checkout sessions will automatically expire after 24 hours. As an alternative, you can use the Stripe API to generate invoices and email the invoice link to your customers.

Stripe API Key

To get started, open your Stripe dashboard, go to Developers > API Keys > Created restricted API key.

Give your key a descriptive name, choose the Write permission under Checkout Sessions and click Create key.

Next, make a copy of the Stripe Google Sheet in your Google Drive. Go to Tools > Script Editor and replace the Stripe API Key with the key generated in the previous step. Then, click on the Run menu once to authorize the script with your Google Account.

Switch to the Google Sheet and you can now use the custom Google Sheets function STRIPE() to generate Stripe Checkout sessions for accepting online payments.

If you would like to generate payment links for multiple rows in the Google Sheet, just write the formula in the first row and drag the crosshairs to the other rows as show in the demo below. Array Formulas are not supported yet.

Stripe Payment Links

How Stripe Checkout Works with Google Sheets

If you are curious to know how integration of Google Sheets and Stripe works, the answer is Google Apps Script. The underlying code invokes the Stripe API with your secret API key and writes the generated checkout session links in the Google Sheet.

The custom Google Sheets function uses the built-in caching service of Apps Script to reduce latency and improve performance. The code can be extended to accept recurring payments for subscriptions.

/**
 *
 *  Author  Amit Agarwal
 *  Email   amit@labnol.org
 *  Web     https://digitalinspiration.com/
 *
 **/

const STRIPE_API_KEY = "<< Stripe API Key >>";
const STRIPE_SUCCESS_URL = "https://digitalinspiration.com";
const STRIPE_CANCEL_URL = "https://digitalinspiration.com";

/**
 * Generate Stripe payment links in Google Sheets
 *
 * @param {number} amount The amount to be paid using Stripe
 * @param {string} currency The 3-letter currency code (optional)
 * @param {string} description A short description of the item name (optional)
 * @return Stripe checkout session link
 * @customfunction
 */

const STRIPE = (amount, currency, description) => {
  const input = {
    "line_items[0][price_data][currency]": currency || "USD",
    "line_items[0][price_data][product_data][name]": description || "Name",
    "line_items[0][price_data][unit_amount]": Math.ceil(amount * 100),
    "line_items[0][quantity]": 1,
  };

  const cacheKey = JSON.stringify(input);

  const cachedLink = CacheService.getScriptCache().get(cacheKey);

  if (cachedLink) return cachedLink;

  const params = {
    cancel_url: STRIPE_CANCEL_URL,
    success_url: STRIPE_SUCCESS_URL,
    mode: "payment",
    billing_address_collection: "required",
    "payment_method_types[]": "card",
    ...input,
  };

  const payload = Object.entries(params)
    .map(([key, value]) =>
      [encodeURIComponent(key), encodeURIComponent(value)].join("=")
    )
    .join("&");

  const response = UrlFetchApp.fetch(
    "https://api.stripe.com/v1/checkout/sessions",
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${STRIPE_API_KEY}`,
        "Content-Type": "application/x-www-form-urlencoded",
      },
      payload,
      muteHttpExceptions: true,
    }
  );

  const { url, error } = JSON.parse(response);

  if (url) {
    CacheService.getScriptCache().put(cacheKey, url, 21600);
  }

  return error ? error.message : url;
};

You can use Mail Merge with Gmail to request online payments from your customers over email. You may also use Document Studio to create PDF invoices and embed the payment links directly in the customer’s invoice.



from Digital Inspiration https://ift.tt/3z6xC3G

Wednesday, 8 September 2021

How to Delete Blank Rows from Tables in your Google Documents

The Document Studio add-on helps you generate Google Documents from data in Google Sheets and Google Form responses. You can create a template in Google Docs and the add-on will replace the placeholders with answers submitted in the Google Form response.

This approach may however create a lot of blank rows in the table for answers that have no response in Google Forms. To give you an example, if the user has not answered the Age question, the generated document will have a row for the question but with a blank value.

Google Docs Remove Table

Remove Blank Rows in Google Docs

With the help of Google Apps Script, we can easily pull all tables that are contained in the body of a Google Document, iterate through each row in the table and, if there’s no value in the row, we can safely remove the row from the table.

Inside your Google Document, go to the Tools menu, choose Script Editor and paste the following code. Go to the Run menu and choose RemoveBlankRows from the dropdown to run the script.

const removeBlankRows = () => {
  // Replace all whitespaces and check if the cell is blank
  const isBlankCell = (text = "") => !text.replace(/\s/g, "");

  // Does the row have any data other than in column 1 (header)
  const rowContainsData = (row) => {
    const columnCount = row.getNumCells();
    let rowHasFilledCell = false;
    for (
      let columnIndex = 1;
      columnIndex < columnCount && !rowHasFilledCell;
      columnIndex += 1
    ) {
      const cellValue = row.getCell(columnIndex).getText();
      if (!isBlankCell(cellValue)) {
        rowHasFilledCell = true;
      }
    }
    return rowHasFilledCell;
  };

  // Get the current document
  const document = DocumentApp.getActiveDocument();

  document
    .getBody()
    .getTables()
    .forEach((table) => {
      const rowCount = table.getNumRows();
      for (let rowIndex = rowCount - 1; rowIndex >= 0; rowIndex -= 1) {
        const row = table.getRow(rowIndex);
        if (isBlankCell(row.getText()) || !rowContainsData(row)) {
          // Remove the row from the Google Docs table
          table.removeRow(rowIndex);
        }
      }
    });

  // Flush and apply the changes
  document.saveAndClose();
};

Delete Blank Table Rows in Google Slides

You can use the same technique to remove blank rows from tables that are contained in your Google Slide presentation.

If your Google Slides table uses merged cells, you may want to check merge status of a cell with the SlidesApp.CellMergeState.MERGED enum.

const removeBlankRows = () => {
  // Get the current document
  const presentation = SlidesApp.getActivePresentation();

  presentation.getSlides().forEach((slide) => {
    slide.getTables().forEach((table) => {
      const rowCount = table.getNumRows();
      for (let rowIndex = rowCount - 1; rowIndex >= 0; rowIndex -= 1) {
        const row = table.getRow(rowIndex);
        const cellCount = row.getNumCells();
        let rowHasFilledCell = false;
        for (
          let cellIndex = 1;
          cellIndex < cellCount && !rowHasFilledCell;
          cellIndex += 1
        ) {
          const cellValue = row.getCell(cellIndex).getText().asString();
          if (cellValue.trim() !== "") {
            rowHasFilledCell = true;
          }
        }

        if (!rowHasFilledCell) {
          row.remove();
        }
      }
    });
  });

  // Flush and apply the changes
  presentation.saveAndClose();
};


from Digital Inspiration https://ift.tt/38PQrha