This post shows an example of stripe-node
usage in a Deno environment. Beyond this post, there's a video tutorial, a Deno example in the stripe-node repository itself, and there is also a Stripe sample 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, 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 installed.
- Deno, version 1.28 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
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. Change that import statement:
- 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.
{
"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.
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, stabilized in 1.28), you would have had to write
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:
$ 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!
$ 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.
✅ 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.
{
"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.
$ 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
❌ 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.
$ 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:
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:
$ 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.
"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
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. Good luck!
Top comments (0)