Skip to content

Stripe

You can accept payments in Val Town with Stripe. This guide teaches you the simplest method: Stripe Payment Links. Here’s how it works:

  1. When users click to pay on your site, you redirect them to your Stripe Payment Link.
  2. They pay with Stripe.
  3. Stripe redirects them back to your site, where you can now store their payment information in a database, and thank them for their payment.

Try it out

Head over to Payment Links in the Stripe dashboard and click the “create payment link” button.

On the Payment page tab, we’ll need to create a new product for our 1.00 tip.

stripe-config1.png

On the After payment tab, we need to select Don't show confirmation page and fill out the input with our val’s /success endpoint so we can redirect users there after a successful transaction.

stripe-config2.png

3. Repeat for each tip amount

Repeat steps 1 - 2 for each tip amount that will be used in the app. Here’s how we handle all the payment links in the app:

Handle Payment LinksRun in Val Town ↗
const [tipAmount, setTipAmount] = useState(5);
const paymentLinks = {
1: "https://buy.stripe.com/test_eVa5nA3FhblYg1y3cc",
5: "https://buy.stripe.com/test_6oE3fs7Vx0Hk4iQcMN",
10: "https://buy.stripe.com/test_aEU3fs4Jl89M7v26oq",
20: "https://buy.stripe.com/test_bIY4jwcbN61E2aI4gj",
};
const handleTip = () => {
window.location.href = paymentLinks[tipAmount];
};

4. Handling successful transactions

After configuring and creating your Payment Links, we need to give our val the ability to handle successful transactions. Here’s the code used in the example val above:

Client codeRun in Val Town ↗
function Success() {
useEffect(() => {
confetti({
particleCount: 100,
spread: 70,
origin: { y: 0.6 },
});
}, []);
return (
<div className="container mx-auto max-w-md p-6 bg-white rounded-lg shadow-lg text-center">
<h1 className="text-2xl font-bold mb-4">Thank You for Your Tip!</h1>
<p className="mb-4">Your generosity is greatly appreciated.</p>
<a href="/" className="text-indigo-600 hover:underline">Back to Home</a>
</div>
);
}
function client() {
const root = document.getElementById("root");
if (root) {
const url = new URL(window.location.href);
if (url.pathname === "/success") {
createRoot(root).render(<Success />);
}
...
}
...
}
Server codeRun in Val Town ↗
export default async function server(request: Request): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/success") {
return new Response(
`
<html>
<head>
<title>Tip Successful</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 flex items-center justify-center min-h-screen">
<div id="root"></div>
<script src="https://esm.town/v/std/catch"></script>
<script type="module" src="${import.meta.url}"></script>
</body>
</html>
`,
{
headers: {
"content-type": "text/html",
},
},
);
}
...
}

Take it further

Congratulations 🥳! You can now use Stripe in Val Town with Payment Links!

What if we wanted to allow users to tip custom amounts though? In order to give users the ability to tip custom amounts, we must use the more customizable Stripe Checkout.

Obtain credentials from the Stripe dashboard

Head over to the Stripe dashboard and get your secret key.

stripe-keys.png

Add Stripe to your Val Town environment variables

Go to your Val Town environment varibales and add your secret key to Val Town.

stripe-env.png

Use Stripe in the server

Create checkout session endpointRun in Val Town ↗
...
if (url.pathname === "/create-checkout-session" && req.method === "POST") {
const stripe = new Stripe(Deno.env.get("STRIPE_SECRET_KEY"), {
apiVersion: "2022-11-15",
});
const data = await req.json();
const { amount, name, comment } = data;
const session = await stripe.checkout.sessions.create({
payment_method_types: ["card"],
line_items: [
{
price_data: {
currency: "usd",
product_data: {
name: "Tip",
},
unit_amount: Math.round(amount * 100),
},
quantity: 1,
},
],
mode: "payment",
success_url: `${url.origin}/success?session_id={CHECKOUT_SESSION_ID}&amount=${amount}&name=${
encodeURIComponent(name)
}&comment=${encodeURIComponent(comment)}`,
cancel_url: `${url.origin}`,
});
return new Response(JSON.stringify({ url: session.url }), {
headers: { "Content-Type": "application/json" },
});
}
...
Success endpointRun in Val Town ↗
...
if (url.pathname === "/success") {
const sessionId = url.searchParams.get("session_id");
const amount = url.searchParams.get("amount");
const name = url.searchParams.get("name");
const comment = url.searchParams.get("comment");
const stripe = new Stripe(Deno.env.get("STRIPE_SECRET_KEY"), {
apiVersion: "2022-11-15",
});
const session = await stripe.checkout.sessions.retrieve(sessionId);
const paymentIntent = await stripe.paymentIntents.retrieve(session.payment_intent as string);
if (paymentIntent.status === "succeeded") {
const sanitizedName = name.replace(/'/g, "");
const sanitizedComment = comment.replace(/'/g, "");
await sqlite.execute(
`INSERT INTO ${KEY}_payments_${SCHEMA_VERSION} (name, amount, comment) VALUES (?, ?, ?)`,
[sanitizedName, amount, sanitizedComment],
);
}
const paymentsJson = JSON.stringify([{ name, amount, comment }]);
...
}
...