Stripe allows you to accept credit card payments online.
There are a two parts to a payment flow:
-
Link to Stripe – User clicks on a link in your site that takes them to a Stripe checkout page.
-
Handle Fulfillment – You can have a webhook that listens for successful 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.
1. Link to Stripe
Section titled “1. Link to Stripe”This guide covers the two simplest ways to link your users to Stripe:
- Stripe Payment Link – the simplest way to link to Stripe
- Stripe Checkout – a more customizable way to link to Stripe
Stripe Payment Link
Section titled “Stripe Payment Link”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
Section titled “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-ignoreimport { 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 Fulfillment
Section titled “2. Handle Fulfillment”After a user pays, you may need to fulfill 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 using an HTTP trigger. Take a look at the example below to get started.
Example Val Town Stripe Webhook
Section titled “Example Val Town Stripe Webhook”This webhook listens for new subscribers to Val Town Pro, and sends the Val Town team a Discord notification. 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");};