stripe-node with Deno
Tags: deno, typescript, stripe, tutorial
Published: 2023-09-06 19:32:32

---
title: stripe-node with Deno
published: true
description: It's not called stripe-deno, but Stripe Node works great with Deno.
tags: deno, typescript, stripe, tutorial
# cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y4uqcd27emv92uqtvf70.png
# Use a ratio of 100:42 for best results.
# published_at: 2023-09-06 18:36 +0000
---

This post shows an example of `stripe-node` usage in a Deno environment. Beyond this post, there's a [video tutorial](https://www.youtube.com/watch?v=epCHqHEdz8I), a [Deno example](https://github.com/stripe/stripe-node/blob/master/examples/webhook-signing/deno/main.ts) in the stripe-node repository itself, and there is also a [Stripe sample](https://github.com/stripe-samples/stripe-node-deno-samples/blob/main/checkout-integration/main.js) that you can read or clone from Github. If you just want to see code, wander over to Github. Stay here if you prefer your code examples with a side of dramatic commentary.

## Background
Earlier this year, in `stripe-node` [v11.15.0](https://github.com/stripe/stripe-node/releases/tag/v11.15.0), we added support to import stripe-node as an ES Module. This means it is much more convenient to use Stripe in non-NodeJS runtime environments, like Cloudflare Workers or Deno. Something funny: "stripe-node" is a bit misnomerous now. Node is not the only option anymore. Maybe `stripe-server-js` would be a more accurate name, but that doesn't quite roll off the tongue, does it? We'll stick with `stripe-node`, I'm sure you know what we mean.

## Prerequisites

* You'll need the [Stripe CLI](https://stripe.com/docs/stripe-cli) installed.
* Deno, version [1.28](https://github.com/denoland/deno/releases/tag/v1.28.0) or later.

## Start your project

If you're starting from scratch, then now it's time to run `deno init`. If you've already got a Deno project going, way to go! You're ahead of the game, but you might have to adapt the rest of the instructions.

## Importing Stripe

Deno init creates several files for you, among them `main.ts`, the entry point for your app. Open it with your code editor. No need to `npm install` with Deno. You can simply put

```typescript
import Stripe from "npm:stripe@^13.0.0"
```

at the top of your `main.ts`, and the package will be installed automatically. At the time of this writing, `13.x.x` is the latest major line of stripe-node, but if you're from the future you can `npm view stripe version` to see the latest version.

**Note:** you will need to import the *npm* versions of stripe-node. The versions at unpkg.com or deno.land aren't quite working due to how the import statements in our source code are written.

## Using Deno's import maps

If you're going to be importing `stripe` inside multiple files, you might not want to have to update the version in every import statement when it comes time to update the Stripe library. In this case, you will instead want to use a [bare specifier and Deno's import maps](https://deno.land/manual@v1.36.4/basics/import_maps). Change that import statement:

```diff
- import Stripe from "npm:stripe@^13.0.0"
+ import Stripe from "stripe"
```

and then edit `deno.jsonc` to add an entry for stripe in the "imports" map.

```diff
  {
    "tasks": {
      "dev": "deno run --watch main.ts"
-   }
+   },
+   "imports": {
+     "stripe": "npm:stripe@^13.0.0"
+   }
  }
```

Voila!

## Using Stripe

In Deno, you use Stripe just like you would in any other Typescript codebase. For example, suppose you want to make an API call to list customers. Initialize the Stripe client with your API key, and make the API call.

```typescript
const stripe = new Stripe("<YOUR (TEST MODE) API KEY GOES HERE>", {
  apiVersion: "2023-08-16",
})

if (import.meta.main) {
  const customers = await stripe.crustomers.list({limit: 5});
  console.log(customers.data.length);
  console.log(customers.data.map(c => c.email));
}
```

## Fetch vs. npm:https
In recent versions of Deno, this will Just Work, because Deno has added compatibility shims for the Node standard library. In versions of Deno before the Node compatibility mode (introduced in [v1.15](https://deno.com/blog/v1.15#improving-node-compatibility), stabilized in [1.28](https://deno.com/blog/v1.28)), you would have had to write

```diff
  const stripe = new Stripe("<YOUR API KEY GOES HERE>", {
    apiVersion: "2023-08-16",
+   httpClient: Stripe.createFetchHttpClient(),
  });
```

to tell stripe-node that you want to use the "fetch" API (native to the web, and to Deno), rather than attempting to use the "node:https" module from the Node standard library (which stripe-node tries to use by default). I recommend do this even if you are on a new version of Deno, though, just to skip the compatibility layer and keep things behind the scenes simpler.

## Typecheck it, run it, get permission

Before we run the code, let's type check it:

```shell
$ deno check main.ts
error: TS2551 [ERROR]: Property 'crustomers' does not exist on type 'Stripe'. Did you mean 'customers'?
  const customers = await stripe.crustomers.list();
```

Whoops! An embarassing typo, but at least we know the types are all set up and working. Fix the typo, run `deno check main.ts` and once we get a clean bill of health, let's go ahead and run our program!


```shell
$ deno run main.ts

┌ ⚠️  Deno requests net access to "api.stripe.com".
├ Requested by `fetch()` API.
├ Run again with --allow-net to bypass this prompt.
└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all net permissions) >
```

This is Deno's hallmark *permissions system* at play. In exchange for a bit of inconvenience, it offers you some peace of mind. If you see "api.stripe.com" as the domain name, nobody has compromised your supply chain with a malicious version of `stripe-node` that transmits your Stripe credentials to some other domain. Go ahead and press `y`, and the program will run.

```shell
✅ Granted net access to "api.stripe.com".
5
[ "richardm+1@example.com", "richardm+2@example.com", ...]
```

Now run it with the command-line flag to bypass the prompt.
```
$ deno run main.ts --allow-net=api.stripe.com
```

That's quite a mouthful. If you don't want to have to type all that every time, you should create a "task" in your `deno.jsonc`. There's already one in there called "dev" created by "deno init" that you can repurpose.

```diff
  {
    "tasks": {
-     "dev": "deno run --watch main.ts"
+     "dev": "deno run --watch --allow-net=api.strippe.com main.ts"
    }
  }
``` 

Now I'm a suspicious person by nature. So I've deliberately typoed the domain name as a test.

```shell
$ deno task dev
Task dev deno run --watch --allow-net=api.strippe.com main.ts
Watcher Process started.
┌ ⚠️  Deno requests net access to "api.stripe.com".
├ Requested by `fetch()` API.
├ Run again with --allow-net to bypass this prompt.
└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all net permissions) >
```

I press `n` and 

```shell
❌ Denied net access to "api.stripe.com".
error: Uncaught Error: An error occurred with our connection to Stripe. Request was retried 1 times.
    at file:///Users/richardm/Library/Caches/deno/npm/registry.npmjs.org/stripe/13.4.0/esm/RequestSender.js:319:37
    at eventLoopTick (ext:core/01_core.js:197:13)
```

Everything seems to be in order! I fix the typo, run `npm task dev` again, and everything is hunky-dory! Not only did I confirm the workings of Deno's permission system, but this helps me save face and pretend my previous embarassing typo "crustomers" was a deliberate choice.

## Webhooks

Now for completeness, let's listen for webhooks too.

First, run `stripe listen` to receive events from the Stripe API and direct them to localhost. Let's be cliché and pick port 3000.


```shell
$ stripe listen --latest --forward-to=localhost:3000
> Ready! You are using Stripe API Version [2023-08-16]. Your webhook signing secret is whsec_... (^C to quit)
```

Copy the webhook signing secret, and let's edit `main.ts` one last time and add the following snippet of code, lightly adapted from the [examples in github.com/stripe/stripe-node](https://github.com/stripe/stripe-node/blob/c384675a45f6ef62d9cfb061e7987925eceb767a/examples/webhook-signing/deno/main.ts#L16):

```typescript
if (import.meta.main) {
  ...
  const port = 3000
  const server = Deno.listen(
    {hostname: '127.0.0.1', port: 3000}
  );
  console.log(
    `Webhook endpoint available at http://127.0.0.1:${port}/
  `);
  
  async function handler(request: Request) {
    const signature = request.headers.get('Stripe-Signature');
  
    const body = await request.text();
    let event;
    try {
      event = await stripe.webhooks.constructEventAsync(
        body,
        signature,
        "<YOUR SIGNING SECRET GOES HERE>",
        undefined
      );
    } catch (err) {
      console.log(`❌ Error message: ${err.message}`);
      return new Response(err.message, {status: 400});
    }
  
    console.log('✅ Success:', event.id);
  
    if (event.type === 'payment_intent.succeeded') {
      const obj = event.data.object as Stripe.PaymentIntent;
      console.log(`💰 PaymentIntent status: ${obj.status}`);
    } else {
      console.warn(`❌Unhandled event type: ${event.type}`);
    }
    return new Response(
      JSON.stringify({received: true}), {status: 200}
    );
  }
  await serveListener(server, handler);
}

```

Now time to run it:

```typescript
$ deno task dev
Task dev deno run --watch --allow-net=api.stripe.com main.ts
...
┌ ⚠️  Deno requests net access to "127.0.0.1:3000".
├ Requested by `Deno.listen()` API.
├ Run again with --allow-net to bypass this prompt.
└ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all net permissions) > ^C
```

The permissions system is at it again! Let's update `deno.jsonc` again.

```diff
    "tasks": {
-     "dev": "deno run --watch --allow-net=api.stripe.com main.ts"
+     "dev": "deno run --watch --allow-net=api.stripe.com,127.0.0.1:3000 main.ts"
    }
```

Run `deno task dev`, and it will run in the background, listening for events. Make sure you've got `stripe listen --latest --forward-to=localhost:3000` running in another terminal, and then in a third terminal, run `stripe trigger payment_intent.succeeded` and you should see your Deno process log output like 

```shell
Webhook endpoint available at http://127.0.0.1:3000/
✅ Success: evt_3Nnabcdefghijklmnopqrstu
❌ Unhandled event type: payment_intent.created
✅ Success: evt_3Nnabcdefghijklmnopqrstu
❌ Unhandled event type: charge.succeeded
✅ Success: evt_3Nnabcdefghijklmnopqrstu
💰 PaymentIntent status: succeeded
✅ Success: evt_3Nnabcdefghijklmnopqrstu
❌ Unhandled event type: payment_intent.created
```

## What's next?

That wraps it up for the post, but some obvious next steps would be:
  - Use environment variables for your signing secret and API key.
  - Deploy to a server on the Internet instead of localhost, and create a webhook endpoint to send Stripe events to that server.
  - Write the Stripe-related logic you actually need for your app. Listing customers is just the start.

But I believe in you. You've got this. Don't let the haters get you down. I will leave you with these words of inspiration: if you have a suggestion, or something seems wrong with stripe-node's Deno support, you can always leave feedback in a [Github issue at github.com/stripe/stripe-node](https://github.com/stripe/stripe-node/issues). Good luck!