Hunter Henrichsen

Hunter Henrichsen

Search Circle
< lecture

Lecture 13 - Payments and State Management

posted over 1 year ago 8 min read

Lecture 13 - State Management (Part 2) and Payments#

News and Housekeeping#

Feedback and Q&A Forms#

Lecture 12 Follow-Up#

Payments and Subscriptions#

I don’t want to take too long on this unless some of you want to; there are two main types of ways that your users can give you money for your product.

Payments vs Subscriptions#

Payments are used where users pay once for a software license and keep it forever. Lots of software, especially desktop software, start out like this. If your product is built around individual events, this might be the way to go.

Subscriptions (sometimes called billing) are the other alternative. They have become much more popular over the past decade or so. This is where instead of a permanent license, users will pay for each month they want to have access to their software. There’s a fine balance here of profit-driving and user friendliness. This quickly turns into an ethics discussion for which I have opinions, but not qualifications.

What I will say here, at least in terms of product perspective, is a couple pieces of advice. Users who like your product and what you help them do are not litigious users, and will frequently help other users find your products. You have many opportunities to be user-friendly in terms of pricing. Combined with user access data which you should be collecting, you have the option to remind users that they are going to be billed when they are not using your product, or even pause billing for months that they don’t use the product.

I don’t want to harp on this forever, but the perception of looking out for your fellow people can go a long way, but that normally comes at the expense of profit.

On Providers#

When you’re starting out, having people Venmo you the money, setting the flag in their account on the database, and creating a reminder in your calendar to remove their access in a month might work, but that can quickly get out of hand.

Stripe, Square, and Plaid are all pretty common and trusted providers for payments, and especially where payments are concerned, trust is important. There’s a tradeoff here, though, since consumer trust is part of the fees you pay on each transaction. Each of these have their own niche; Plaid is generally used for banking and user-to-user transactions, Square is more intwined with point of sale and physical goods, and Stripe is used with software.

I strongly recommend against building a payment processor yourself. There are lots of rules, and lots of things that can go wrong.

Getting Payment Information#

This looks a lot like authentication, because both of them should be secure, and both of them have different needs based on what you’re building. Here are some of the options that I’ve seen or heard about:

State Management Part 2: Demo#

I was working on a more complex example for this, but I encountered this example instead which I think better illustrates what I’m trying to do. For my app, I want to build a navigation display that looks something like this:

(Spoiler alert I already built it while writing this lecture)

I want this to have different behavior depending on the page, too:

Generally the way to communicate to children is via props, and the way to communicate to parents is via event listeners:

Because this lives in the root layout, I can’t exactly control it with props a couple layers deep, and the listeners to facilitate this would be crazy even without SSR. This is where state management comes in. State management is mainly used to pass data around outside of the tree of the document:

While multiple parts of the document may depend or update the data, that data is not truly part of the tree. That’s what gives it the flexibility that we want to take advantage of here.

So for mine, I’m going to use jotai, and create an atom outside of the DOM tree:

apps/web/app/currentsubpage.ts

import { atom } from "jotai";
export const currentSubpage = atom<string | undefined>();

Now, I want to depend on it in my header. Unfortunately for me, my header is only partly clientside rendered, and state management happens entirely on the clientside. So I’m going to need to convert my full header to be clientside rendered:

apps/web/app/clientheader.tsx (relevant parts)

"use client";
import { useAtom } from "jotai";
import Link from "next/link";
import { currentSubpage } from "./currentsubpage";
export function ClientHeader(): JSX.Element {
const subpage = useAtom(currentSubpage)[0];
return (
<header className="sticky top-0 flex items-center justify-between bg-slate-50 text-slate-950 shadow-md dark:bg-slate-950 dark:text-slate-50">
<div className="flex flex-row items-end justify-center gap-2 p-4">
<Link href="/" style={{ textDecoration: "none" }}>
<h1 className="m-0 text-xl font-bold text-slate-950 dark:text-slate-50">
hvz.gg
</h1>
</Link>
{subpage ? (
<span className="invisible flex flex-row items-end gap-2 sm:visible">
<span className="text-xl text-slate-600 dark:text-slate-400">
/
</span>
<h2 className="text-l font-bold text-slate-950 dark:text-slate-50">
{subpage.title}
</h2>
</span>
) : null}
</div>
</header>
);
}

Now, each of the places that need it should also have a client component to be added to the serverside tree and help me manage state on the client:

apps/web/app/client.tsx

import { useAtom } from "jotai";
import { currentSubpage } from "./currentsubpage";
export function RootClientComponent(): JSX.Element {
const [subpage, setSubpage] = useAtom(currentSubpage);
// Remove the subpage state when the root page is loaded,
// but only do so when there is a subpage so we don't
// rerender all the time.
if (subpage) {
setSubpage(undefined);
}
// Probabaly not needed, but I like having components
// actually return components.
return <span></span>;
}

And use that on the rendered page:

apps/web/app/page.tsx

export default function Home(): JSX.Element {
return (
<div>
...
<RootClient />
</div>
);
}

Now, I need a client portion to my Games page:

apps/web/app/games/client.tsx

import { useAtom } from "jotai";
import { currentSubpage } from "./currentsubpage";
const subpageName = "Games";
export function RootClientComponent(): JSX.Element {
const [subpage, setSubpage] = useAtom(currentSubpage);
// Set the subpage state when the game index page is loaded,
// but only do so when it's different than expected so we don't
// rerender all the time.
if (subpage !== subpageName) {
setSubpage(subpageName);
}
return <span></span>;
}

And again one for the game detail. This one needs a bit more information, because the game details need to be retrieved from the server:

apps/web/app/games/[gameSlug]/client.tsx

"use client";
import type { PublicGame } from "@pointcontrol/types";
import { useAtom } from "jotai";
import { currentSubpage } from "../../headerstack.store";
export function GameDetailClientComponent({
game,
}: {
game: PublicGame;
}): JSX.Element {
const [subpage, setSubpage] = useAtom(currentSubpage);
// Pull the subpage from the props, updating it as needed
if (subpage !== game.name) {
setSubpage(game.name);
}
return <h1 className="text-4xl font-bold">{game.name}</h1>;
}

And here’s where I provide that info:

apps/web/app/games/[gameSlug]/page.tsx

import { prisma } from "@pointcontrol/db/lib/prisma";
import { notFound } from "next/navigation";
import { GameDetailClientComponent } from "./client";
export default async function GameHome({
params,
}: {
params: { gameSlug: string };
}): Promise<JSX.Element> {
const game = await prisma.game.findUnique({
where: {
slug: params.gameSlug,
},
});
if (!game) {
return notFound();
}
// Pass the game as a prop into the client so that
// it has access to that data.
return <GameDetailClientComponent game={game} />;
}