Skip to content

Stripe

Stripe allows you to accept credit card payments online.

There are a two parts to a payment flow:

  1. Link to Stripe – User clicks on a link in your site that takes them to a Stripe checkout page.

  2. Handle Fufillment – You can have a webhook that listens for succssful payment events from Stripe. You can use this webhook to update your database to authorize the user to view premium content or send a confirmation email to the user.

This guide covers the two simplest ways to link your users to Stripe:

Stripe Payment Links are the simplest way to link to Stripe.

Docs: Stripe Payment Links

Create a Stripe Payment Link

Stripe Payment links do not require a backend. Link to them from HTML:

<a href="https://buy.stripe.com/test_fZe6rigkxdR9aze289">Buy</a>

When your users click on the link, they will be taken to a Stripe checkout page where they can enter their credit card information.

Stripe Checkout

Stripe Checkout is a more customizable way to link to Stripe. You first create a Checkout Session on your server, then redirect the user to that session’s URL.

Docs: Stripe Checkout

We ported Stripe Checkout Quickstart to Val Town: https://val.town/v/std/stripeCheckoutQuickstart

// @ts-ignore
import { Stripe } from "npm:stripe";
const stripe = new Stripe(Deno.env.get("STRIPE_TEST_API_KEY"));
export default async function (req: Request): Promise<Response> {
const url = new URL(req.url);
if (url.pathname === "/") {
return new Response(
`<h1>Stripe Checkout Demo</h1>
<form action="/create-checkout-session" method="POST">
<input name="name" placeholder="t shirt">
<input type="number" name="price" placeholder="$5">
<button type="submit">Buy</button>
</form>`,
{ headers: { "content-type": "text/html" } }
);
}
if (url.pathname === "/create-checkout-session" && req.method === "POST") {
const form = await req.formData();
const name = form.get("name");
const price = form.get("price");
const session = await stripe.checkout.sessions.create({
line_items: [
{
price_data: {
currency: "usd",
product_data: {
name,
},
unit_amount: Number(price) * 100,
},
quantity: 1,
},
],
mode: "payment",
success_url: `https://${url.host}/confirmation`,
cancel_url: `https://${url.host}`,
automatic_tax: { enabled: true },
});
return new Response(null, {
status: 303,
headers: {
Location: session.url,
},
});
}
if (url.pathname === "/confirmation") {
return new Response(`<h1>You paid!</h1>`, {
headers: { "content-type": "text/html" },
});
}
}

2. Handle Fufillment

After a user pays, you may need to fufill their order:

  • If you are selling a digital product, this might mean sending them a download link.
  • If you are selling a physical product, this might mean shipping the product to them.
  • If you are selling a subscription, this might mean updating your database to authorize the user to view premium content.

You can listen for events from Stripe to know when a payment has been successful. You can do this by setting up a webhook. A webhook is a URL on your server that Stripe will send events to.

Docs: Stripe Webhooks

Val Town makes it very easy to create a webhook on an HTTP val. Take a look at the example below to get started.

Example Val Town Stripe Webhook

This webhook listens for new subscribers to Val Town Pro, and sends the Val Town team a Discord notifcation. The val itself (link below) explains how to set it up.

https://www.val.town/v/stevekrouse/newStripeSubscriber

import { discordWebhook } from "https://esm.town/v/stevekrouse/discordWebhook";
import Stripe from "npm:stripe";
const stripe = new Stripe(
Deno.env.get("stripe_sk_customer_readonly") as string,
{
apiVersion: "2020-08-27",
}
);
function getStripeCustomer(customerId: string) {
return stripe.customers.retrieve(customerId);
}
export let newStripeEvent = async (req: Request) => {
const url = new URL(req.url);
if (req.method !== "POST") {
return new Response("Method not allowed", { status: 405 });
}
const signature = req.headers.get("Stripe-Signature");
const body = await req.text();
const webhookSecret = Deno.env.get("STRIPE_WEBHOOK_SECRET");
let event;
try {
event = await stripe.webhooks.constructEventAsync(
body,
signature,
webhookSecret
);
} catch (err: any) {
return new Response(`Webhook Error: ${err.message}`, { status: 400 });
}
console.log(event);
const newSubscription =
event.data.previous_attributes.status === "incomplete" &&
event.data.object.status === "active";
if (!newSubscription) {
console.log("Not a new subscription");
console.log(
event.data.previous_attributes.status,
event.data.object.status
);
return Response.json("ok");
}
const customer = await getStripeCustomer(event.data.object.customer);
const customerEmail = customer.email;
console.log("New subscription", customerEmail);
discordWebhook({
url: Deno.env.get("discordUserEvents"),
content: `New Subscription 🥳 ${customerEmail}`,
});
return Response.json("ok");
};