How to Use PayPal Subscriptions API with Node.js

Our Google add-on store uses PayPal Subscriptions with Digital Goods to process recurring payments and the invoices are sent to customers through Document Studio.

There are two steps.

  1. Customers makes the payment and completes the order on our website.
  2. PayPal sends a BILLING.SUBSCRIPTION.ACTIVATED webhook to a serverless function.
  3. The function (running on Firebase, Google Cloud) verifies the subscription and checks if the status is active.
  4. It invokes the Apps Script API to complete the order.

The cloud function was previously using the official PayPal SDK for Node.js but it has been recently deprecated and no longer supports the new PayPal subscriptions API endpoints. Migrating from the PayPal Node SDK to your own solution is relatively simple and involves two steps:

1. Get the PayPal Access Token

const { default: axios } = require("axios");

const getPayPalAccessToken = async () => {
  const client_id = "PayPal Client ID goes here";
  const client_secret = "PayPal Client Secret goes here";
  const options = {
    url: "",
    method: "POST",
    headers: {
      Accept: "application/json",
      "Accept-Language": "en_US",
      "Content-Type": "application/x-www-form-urlencoded",
    auth: {
      username: client_id,
      password: client_secret,
    params: {
      grant_type: "client_credentials",
  const { status, data } = await axios(options);
  return data.access_token;

If you are planning to test your integration with your PayPal sandbox account instead of the production version, replace in the requests with and use the sandbox client secret credentials.

2. Verify the Subscription

A successful request returns the HTTP 200 OK status code and a JSON response body.

const { default: axios } = require("axios");

const verifyPayPalSubscription = async (subscription_id) => {
  const token = await getPayPalAccessToken();
  const options = {
    method: "GET",
    url: `${subscription_id}`,
    headers: {
      Authorization: `Bearer ${token}`,
      Accept: "application/json",
  const { status, data = {} } = await axios(options);
  if (status === 200) {
    const { subscriber: { email_address } = {}, status } = data;
    return status === "ACTIVE";
  return false;

Once the PayPal Subscription is found to be active, an HTTP request is made to the Google Apps Script API that sends the invoice and license to the customer. Learn more.

The Best Extensions for Google Chrome

Best Google Chrome extensions

Here’s a comprehensive list of the best extensions for Google Chrome that will help you do more and also enhance your web browsing experience.

The Best Google Chrome extensions

  • Vimium C — Browse the web, interact with web pages, navigate browser history using keyboard shortcuts. Inspired by Vim commands.
  • Fika - Read pages in a Kindle-style clean layout with beautiful typography while hiding the distracting parts.
  • PushBullet — Easily transfer web page links, text notes, or push photos and files from the computer to your phone and vice-versa.
  • OneTab — Save all your open tabs in a group and duplicate your session anytime later with a click.
  • Gmail Sender Icons - Quickly identify the sending domain of an email in Gmail without opening the message.
  • Bubble Cursor - The add-on makes it easy for you to click on links that are too small to select with your mouse or for typing inside input boxes.
  • Picture in Picture - Watch YouTube videos in an always-on-top floating window while you work on other tasks.
  • Zoho Annotator - A simple ad-free screenshot capture tool with a built-in editor for annotating images. Also integrates with Google Drive, OneDrive and Dropbox.
  • Loom - Record a screencast video with voice narration, include your webcam video and share instantly. Also see Screenity.
  • URL Render - Instead of clicking individual links on the Google search page, hover your mouse and instantly view the underlying page in a floating window.
  • Clipt - A universal clipboard for Android and desktop computers. Copy any text on your phone and instantly paste it on the desktop and vice-versa.
  • Hover Zoom - Hover your mouse over any thumbnail image on sites like Facebook and Amazon and the add-on will enlarge the image to its full size.
  • Quick Source Viewer - A better alternative to the native View Source option that displays both JS and CSS files in addition to HTML content.
  • Save To Drive - Right-click and save the current web page, or images on a page, to your Google Drive.
  • Google Input - Type text in any language of your choice using virtual keyboards, your handwriting and transliteration.
  • Google Dictionary - Use this add-on to view definitions of words and learn their correct pronunciation.
  • uBlacklist - Blocks specific sites and entire domains from appearing in your Google search results.
  • Gmail Notes - Attach sticky notes to your email message that are persistent and will show up the next time you open that same email inside Gmail.
  • Ugly Email - Some emails contain a tracking pixel that notifies the sender when their emails are read. This add-on blocks the tracking attempts.
  • Stylebot - Change the appearance of any web page with CSS selectors, permanently hide elements on a page and the changes persist across browser sessions.
  • Webtime Tracker — Keep track of how you spend your time on individual websites and visualize your Internet usage through graphs.
  • Skip Search - An address bar shortcut for the I'm lucky command that directly takes you to the most relevant website for your search query.
  • Single File - Download the entire web page into a single HTML file. The associated CSS files, fonts and images are also saved in the file.
  • Go Incognito - Open the current tab of your browser in a new incognito tab, useful for browsing news websites are behind paywalls.
  • Feeder - An excellent RSS feed reader that is accessible from the Chrome toolbar and supports notifications for your favorite feeds.
  • Scribe - Create step-by-step guides and tutorials by recording steps and publish them as PDF guides.
  • Visbug - A must-have design tool for web developers that brings powerful editing capabilities to the browser.
  • TabNotes - A minimalistic start page for Chrome that opens a simple notepad in each new tab.
  • Sitemod - Modify any website on the Internet through Chrome Dev Tools and create a permanent copy of the modified website with a unique, shareable URL.
  • iCloud Passwords - Access your Safari passwords that are saved inside iOS and Mac device from Chrome on Windows PCs.
  • Twitter Screenshots - Take beautiful, uncluttered screenshots of tweets with a click.
  • StayFocusd - It helps you limit the time you spend on distracting websites Facebook or Reddit by temporarily blocking the site when the allotted time has passed.
  • Black Menu - Create shortcuts to your favorite Google services and access your emails, calendar, videos and more in an easily accessible popup.

How to Use Notion with Gmail and Google Sheets using Apps Script

Notion, my absolute favorite tool for storing all sorts of things from web pages to code snippets to recipes, just got better. They’ve released a public API and thus it will be a lot easier for developers to read and write to their Notion workspace from external apps.

For instance, you can create a document in Google Docs and export it to Notion while staying inside Docs. Google Sheets users can pull pages from Notion database into their spreadsheet. Any new submissions in Google Forms can be directly saved to Notion and so on!

Save Gmail Messages in Notion

I have put together a Gmail add-on that makes it easy for you to save email messages, or any other text content, from Gmail to your Notion workspace with a click. Here’s how the app works.

Step 1: Connect Gmail to Notion


Step 2: Allow Access to Notion pages - if you have multiple databases in your Notion workspace, you have an option to grant access to select databases and the rest will be inaccessible to the external app.

Authorize Notion

Step 3: Choose Email - open any email message in Gmail and you’ll have an option to edit the content of the email subject and body before sending the content to your Notion page. Please note that the app only supports plain text format at this time.

Send Email to Notion

Step 4: Open Notion - As soon as you hit the Send to Notion button, the content of the currently selected email message is added to your Notion database. You can click the All updates link in your Notion sidebar to view to recently added page.

Notion page

If you would like to try this Gmail to Notion app, please get in touch.

How to Use Notion with Google Apps Script

If you would to integrate your own Google add-on with Notion API, here’s a brief outline of the steps involved.

  1. Go to and click the Create New Integration button. You’ll be provided with a Client ID and Client Secret that you’ll need in a later step.

  2. Include the OAuth2 library in your Apps Script project and invoke the getRedirectUri method to get the OAuth2 redirect URL for the previous step.

const getNotionService = () => {
  return OAuth2.createService("Notion")
      Authorization: `Basic ${Utilities.base64Encode(

const authCallback = (request) => {
  const isAuthorized = getNotionService().handleCallback(request);
  return HtmlService.createHtmlOutput(
    isAuthorized ? "Success!" : "Access Denied!"

const getRedirectUri = () => {
  1. Connect to Notion API - Make a Get HTTP request to the /vi/databases to fetch a list of all databases that the user has explicitly shared with authorized app.
function getDatabasesList() {
  var service = getNotionService();
  if (service.hasAccess()) {
    const url = "";
    const response = UrlFetchApp.fetch(url, {
      headers: {
        Authorization: `Bearer ${service.getAccessToken()}`,
        "Notion-Version": "2021-05-13",
    const { results = [] } = JSON.parse(response.getContentText());
    const databases = results
      .filter(({ object }) => object === "database")
      .map(({ id, title: [{ plain_text: title }] }) => ({ id, title }));
    console.log({ databases });
  } else {
    console.log("Please authorize access to Notion");

Gmail to Notion - Try the App

The Gmail to Notion app is in private beta. If you would like to use it with your Gmail or Google Workspace account, please get in touch for an invite.

Create React App with Multiple Entry Points

The Create React App frameworks lets you easily build single page applications but it doesn’t support multiple entry points. To give you an example, if a website outputs separate home pages for mobile and desktop clients, the pages could be sharing some common React components between them, and it may thus not be practical to build two completely separate React applications.

Also see: Bundle React App with Gulp

CRA doesn’t support multiple entry points but there are couple of ways to solve this problem.

Option 1 Eject from the Create React App using the npm run eject command and update the entry inside webpack.config.js file to include multiple entry points.

Option 2 Use an alternate build tool like Vite.js that includes support for multiple entry points out of the box.

Option 3 Use the rewired app - it lets you easily make changes and small tweaks to the default Webpack configuration without ejecting the app.

Option 4 Use REACT_APP environment variables to specify the target component and then use ES5 dynamic imports to load the corresponding app component as shown in this example.

React Multiple Entry Points

Multiple Entry Points for Create React App

If you intend to use the Create React App configuration without ejecting it, here’s a simple workaround that will help you define multiple entry points and the output will be bundle in separate folders.

Inside the src folder, create two components.

// ./src/Desktop.js
import React from "react";

const Desktop = () => {
  return <h1>For Desktop</h1>;

export default Desktop;
// ./src/Mobile.js
import React from "react";

const Mobile = () => {
  return <h1>For Mobile</h1>;

export default Mobile;

The default entry file index.js looks something like this:

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

    <App />

Next, edit your package.json file and add commands, one per build target.

"scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "build:mobile": "cp src/Mobile.js src/App.js && npm run build && mv build build-mobile",
    "build:desktop": "cp src/Desktop.js src/App.js && npm run build && mv build build-desktop"

Run npm run build:mobile when the build target is mobile or npm run build:desktop for the desktop entry point.

Google Sheets – Find Values in One Column that are Missing in Another Column

A small business maintains their staff roster in a simple Google Sheet - the column A of the sheet contains a list of all employee names and column B contains a list of employees who have been assigned to a project.

Items in column A that are not in column B

The immediate task is to identify staff members who are part of the organization but have not been assigned any project yet. In other words, the manager needs to figure out all employee names from column A who are not preset in column B.

There are two ways to solve this problem - visually and through formulas.

Using Visual Formatting

The first option would be to highlight cells in column A that are missing in column B.

Inside the Google Sheet, go to the Format menu and choose conditional formatting. Here select A2:A for the range field, choose Custom Formula from the Format Cells If dropdown and paste the formula:

=COUNTIF(B$2:B, A2)=0

The COUNTIF function will essentially count the occurrence of each item in Column A against the range of cells in Column B. If the count for an item in Column A is 0, it means that the cell value is not present in column B and the cell is highlighted with a different background color.

Visual Formatting - Missing Column Values

Find Missing Items in Another Column

The next approach uses Google Sheet formulas to create a list of items that are in Column A but missing in Column B.

We’ll make use of the FILTER function that, as the name suggests, returns only a filtered version of a range that meets a specific criteria. In our case, the criteria is similar to the one that we used in the visual formatting section.

Go to column C (or any blank column) and enter this formula in the first empty cell.


Google Sheets MATCH function

The MATCH function returns the position of items in Column A in the range associated with Column B and it returns #N/A if the values is not found. When the result is used with ISNA, it returns true only when the match is not found.

Using Google Query Language

SQL geeks may also use the Google Query Language, we are used it with D3.js visualization, to print the list of names that are in Column B but not in Column B.

   "SELECT A WHERE A <> ''
    ORDER BY A")

missing values - Google Query

The matches operator in the where clause does a regex comparison and the order by clause in the query will automatically sort the output alphabetically.

How to Email Spreadsheets Automatically on a Recurring Schedule

The Email Spreadsheets add-on for Google Sheets will help you automate the reporting of spreadsheet data and dashboards by email. If you are an office worker who has been emailing spreadsheets to colleagues manually, this add-on will save you a ton of time. And because it runs on the Google Cloud, your spreadsheet reports will be delivered even while you are offline or on vacation.

With Email Spreadsheets, you can schedule reports and it will automatically send them by email on a recurring schedule. You can email entire workbooks, specific sheets inside a workbook or even range of cells. Watch the video to get started.

Email Google Sheets Automatically

Go to the Google add-on store and install Email Google Sheets. Next, open any Google Spreadsheet in your Google Drive, go to the Add-ons menu inside the sheet, choose Email Spreadsheets from the dropdown and then choose Rules to create your first scheduled email report.

You are presented with a 3-step wizard to help the email schedule of your spreadsheet report.

Step 1: Select Sheet Export Options

Google Sheet - Export Options

  1. Expand the “Select Sheets” dropdown and select one or more sheets that you would like to include in the email. Each sheet is attached as a separate file in the email but you can choose the “Merge all sheets” option to create a single file from all sheets in the workbook.
  2. Select the export format. You can choose between PDF, Excel (xlsx), OpenDocument or CSV formats. The “Email without Attachment” option can be used to embed a specific range of cells in the email body without including any sheet as an attachment.
  3. If you have selected PDF as the export format in step 2, you are presented with a few more options. For instance, you can change the paper orientation (Portrait or Landscape), the paper size or alter the print margins to fit more content on a page. You can choose to show gridlines, notes, sheet names and page numbers in the exported file.
  4. (optional) The Cell Range option lets you specify a range in A1 notation and only that range will be exported in the PDF file.

Tip: Use the Preview button to test how your exported files would be like with the various export options.

Step 2: Write the Email Template

Email Template with Dynamic Sheet Cell Values

Next, we create an email template that will be sent with your reports. You can specify one or email recipients in the TO, CC, or BCC fields. Multiple email addresses should be separated by a comma.

You can also specify dynamic email recipients based on cell values in the spreadsheet. For instance, if the email address of the recipient is specified in cell B2 of a sheet titled “Employee Shifts”, you can put {{Employee Shifts!B2}} in the To field, and the add-on will pull the dynamic value from the cell at the time of sending the email report.

These dynamic cell values enclosed inside double curly braces can be used inside any of the email fields including subject, email body, and the sender’s name.

The email body can include dynamic cell values as well as ranges that make it easy of you to send portions of the spreadsheet without sharing the full workbook. For instance, you can write {{Employee Wages!B2:F9}} to include only the specific range (B2:F9) from the Wages sheet. Internally, the add-on converts the range to an HTML table, retaining all the display formatting with CSS, and embed it into the email.

You can also include standard HTML tags like H1, IMG, A, B, EM and more to include images and rich formatting in your emails.

Tip: Use the Test Email button to send an email with the exported files before setting up the schedule.

C: Create the Email Schedule

Email Sheets Daily, Hourly, Weekly or Monthly

The Google Sheets add-on includes a scheduler to help you set up recurring schedules visually. You can send email hourly, daily, weekly, monthly or even on a yearly recurring basis.

It is also possible to setup advanced schedules like:

  • Send a recurring email on the last working day of the month.
  • Send email reports every alternate day and end the reporting after 15 days.
  • Set up a quarterly schedule and send email reports on the first Monday of the quarter.

That’s it. After specifying the schedule, hit the Save button and your email report will be scheduled.

If you would like to edit your current email report or schedule a new report, go the add ons menu again, choose Email Spreadsheets and Rules.

Download Email Sheets

Email Google Sheets - How it works?

The add-on is written in Google Apps Script. It uses the Google Sheets API to convert sheets to PDF files and uses the Gmail API for sending the converted files as attachments.

Get Email Alerts When COVID-19 Vaccines Become Available Near You

India is currently in the midst of a second wave of the Coronavirus and this one is far more devastating than what we have seen last year. The country is reporting close to 400,000+ new cases every day but the actual count of daily infections could be much higher.

The COVID-19 vaccination program in India was earlier available to people above 45 years of age but starting today (May 1), anyone above the age of 18 years is eligible for Covid-19 vaccination.

Vaccine Availability

COVID-19 Vaccines Near Me

The government’s official website - - has a useful search section that allows you see the nearby vaccination centers in your city or zip code. You’ll also know how many vaccine doses are available at a specific center and the earliest date when the next batch of vaccine stocks are due.

Based on this public data, I have developed an open-source vaccine tracker that will monitor the vaccine availability near you and will send email alerts as stocks become available. The source code of the project is available on Github.

Build your own Covid-19 Vaccine Tracker

Step 1: To get started, click here to make a copy of the Vaccine Tracker Google Sheet in your Google Drive.

Step 2: Click the Vaccine Tracker menu (near the Help menu) and choose Enable as shown in the screenshot.

Vaccine Tracker Google Sheet

Step 3: You may see an authorization window. If you get an “unverified app” message, click the Advanced link and choose “Go to Vaccine Alerts”. The app is 100% safe and open-source.

Step 4: Go to Step 2 now and choose the Enable menu again to launch the tracker. Enter one more pin codes, the email address where you wish to receive the alerts and the age group for which you need to monitor vaccine availability.

Click the Create Email Alert button and your system is up and running. Google Sheets will run this monitor every day and send an email at 8 am indicating the availability of vaccines in your specified areas.

Here’s a copy of the email sent by the vaccine tracker.

Email Alert - Vaccine Tracker

And if you ever wish to stop Google Sheets from tracking vaccine availability, go to the same sheet and choose Disable from the menu.

How to Print the Function Call Flow with Stack Trace in JavaScript

The printStackTrace method of Java is useful for handling exceptions and errors during development. It tells you the exact line number in your source code and the file name where the problem occurred.

If you are working in the JavaScript / Google Apps Script world, you can use the console.trace() method to output the complete stack inside the web console ( or StackDriver logs for Google Scripts).

A better alternative is that you parse the stack property of the Error object. This contains the entire stack trace along with line numbers, column position and the function names.

function printStackTrace() {
  const error = new Error();
  const stack = error.stack
    .map((line) => line.replace(/\s+at\s+/, ""))

function three() {
  console.log("Function Three!");

function two() {
  console.log("Function Two!");

function one() {
  console.log("Function One!");


The output of the printStackTrace method looks something like this. The first few lines are the program output and as you move downwards, you’ll see a list of methods which invoked the previous method.

Function One!
index.js:16 Function Two!
index.js:11 Function Three!
index.js:7 three (index.js:12:3)
two (index.js:17:3)
one (index.js:22:3)

You can use the stack trace to know the exact location of the problematic code in your JavaScript app or if you simply want to print the function calling flow of your JavaScript program without even throwing an exception.

How to Perform IP Address Lookup with Google Sheets

Websites can determine the visitor’s geographic location using their IP address and serve more relevant content. For example, a weather website may use your IP address to estimate your approximate location and provide weather forecast for your current city automatically. A currency exchange website can determine your default currency based on your country which is detected from your IP address.

Google Sheets - IP 2 Location

There are free web IP lookup services, for example, that will reveal the country of your client’s IP address with a simple HTTP request. We internally use that service at Digital Inspiration to determine the payment service provider on the checkout page.

Bulk IP Lookup with Google Sheets

IP2Location is another good alternative that retrieves more detailed geolocation information for any IP address. The IP location lookup service can retrieve the client’s country, city name, region, the ISP name and more.

If you have a bulk list of IP addresses, you can use Google Sheets to estimate the corresponding geographic details for each of the addresses in few easy steps:

  1. Click here to make a copy of the Google Sheet for performing IP lookups in bulk.

  2. Paste the list of IP addresses in column A, one per row. The lookup service works for both IPv4 and IPv6 addresses.

  3. Enter your key in cell E1. If you have a small list of IP address, use demo as the key or get your own API key from

  4. Click the Run button, authorize the script and watch as the geographic details and ISP names are populated in the sheet.

IP2Location Web Service Demo

How IP2Location Script Works

Internally, the Google Sheet uses the IP2location web service with Google Apps Script to transform IP addresses into geographic region.

It uses the UrlFetchApp service to perform multiple HTTP requests in a single batch for improved performance. Here’s the full source code:

const ip2location = () => {
  // Get all the input data from Google Sheet
  const ss = SpreadsheetApp.getActiveSheet();
  const data = ss.getDataRange().getDisplayValues();

  // Use your own API key or use demo key
  const apiKey = data[0][4] || "demo";

  // Generate API URL for IP address
  const getUri_ = (ipAddress) => {
    const API_URL = "";
    return `${API_URL}/?ip=${ipAddress}&key=${apiKey}&package=ws4`;

  const requests = [];

  for (let r = 2; r < data.length; r++) {
    const [ipAddress, countryName] = data[r];
    // Only process rows where the country is blank
    if (ipAddress && !countryName) {
      requests.push({ url: getUri_(ipAddress), rowNumber: r + 1 });

  // Make API calls in bulk using the UrlFetchApp service
  UrlFetchApp.fetchAll(requests).forEach((content, i) => {
    // Parse the JSON response
    const { city_name, country_name, isp, response } = JSON.parse(content);

    // If the response is populated, the API call failed
    if (response) throw new Error(response);

    // Write the response data to Google Sheet
    const values = [[country_name, region_name, city_name, isp]];
    ss.getRange(requests[i].rowNumber, 2, 1, 4).setValues(values);

  // Flush all changes