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 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.
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
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-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 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");};