Typestate
Sign in

Subscription app starter

People sign in with Google, pick a plan, and pay on Stripe. Your app asks which plan a person has before it opens a feature.

For a builder who sells a web app by the month.

Stripe logo StripeResend logo ResendGoogle logo Google
Start it with your agentStarting from a blueprint does not count against the action changes of your plan.

Start the subscription app starter blueprint on Typestate

Asked you for your website's address and the address your emails come from.

Started Subscription app starter: 1 record types, 4 web endpoints.

8 actions are compiling.

Deployed to Sandbox. Try it at your Sandbox address.

What your agent does when you start this blueprint

What runs

Each step has 3 parts: who starts it, how it reaches your backend, and the action that answers.

  1. A personsigns in with Google and opens an account
    Web endpointPOST /account
    Sign UpOpens an account on the free plan.saves an Account
  2. A personpicks Basic or Pro and pays on a Stripe page
    Web endpointPOST /plan
    Start a PlanSends the person to pay on Stripe.opens a Stripe page
  3. Stripesays the plan started or changed
    Payment eventa subscription starts
    Record a PlanPuts the plan on the account.changes an Account
  4. Your appasks the plan before it opens a feature
    Web endpointGET /plan
    Read My PlanAnswers the plan and whether the person pays.reads only
  5. A personchanges or cancels the plan on the Stripe billing page
    Web endpointPOST /billing
    Open BillingSends the person to the Stripe billing page.opens a Stripe page
  6. Stripesays a payment failed
    Payment eventa payment fails
    Warn of a Failed PaymentMarks the account late and emails the person.changes an Accountsends 1 email
  • Web endpoint: your website or app calls it
  • Payment event: Stripe tells your backend

The rule you will want to change first

A person whose payment failed keeps their plan until Stripe ends it, so a card that fails once does not lock them out.

It lives in the action Read My Plan. Tell your agent how you want it instead: one sentence changes, and you read the change before it goes Live.

What it is made of

The map shows the records it keeps, the actions it runs and the services it calls.

What it keeps

Accountperson, email, plan, state, subscription

What it does

Sign Upweb endpoint
Start a Planweb endpoint
Record a Planwhen a subscription starts
End a Planwhen a subscription ends
Warn of a Failed Paymentwhen a payment fails
Warn of a Trial Endingwhen a trial ends in 3 days
Read My Planweb endpointThe rule to change first
Open Billingweb endpoint

What it reaches

StripeTake one-off payments, deposits and subscriptions on your own Stripe account.
ResendSend email from your own address: sign-in links, receipts, reminders.
GoogleLet people sign in with their Google account.
Your website4 web endpoints

What it keeps

Account

5 fields

One person's account, with their plan and where their payments stand.

person
required
email
required, an email address
plan
required, one of Free, Basic, Pro
state
required, one of free, active, late, ended
subscription
optional

A person reads only their own account. Only Stripe's events change a plan.

What it does

Open an action to read its steps: each sentence, with the calls that do it under it.

Sign UpA signed-in person opens an account on the free plan.

Who starts it

From the webPOST /account

What it is given

Nothing. It needs no input.

What it does

  • accountsa value it works out
  • Accounta record type
  • Accountan attribute of a record or an answer
  • "Free"a fixed text or number
  • caller.idwhat it knows without being told
  • Refusewhere it says no

Count the Accounts whose person_id is the signed-in person.When there is one, refuse with "You already have an account".

accounts is

Count Accounts where

person_id is caller.id

When accounts is more than 0

Refuse "You already have an account"

Create an Account with person_id set to the signed-in person, email set to the address the person signed in with, plan set to Free, and state set to free.

Add an Account

set email to caller.email

set person_id to caller.id

set plan to "Free"

set state to "free"

Answer with that Account's plan and state.

Answer

plan is account.plan

state is account.state

Start a PlanA person picks Basic or Pro and gets a Stripe page to pay on.

Who starts it

From the webPOST /plan

What it is given

Planone of Basic, Pro, needed

What it does

  • accounta value it works out
  • Accounta record type
  • Accountan attribute of a record or an answer
  • "active"a fixed text or number
  • caller.idwhat it knows without being told
  • refusewhere it says no

Find the Account whose person_id is the signed-in person.When none matches, refuse with "Sign up first".

account is

Find the first Account where

person_id is caller.id

when none refuse "Sign up first"

When that Account's state is active or late, refuse with "You already have a plan. Change it on the billing page".

When account.state is one of "active" or "late"

Refuse "You already have a plan. Change it on the billing page"

When the chosen plan is Basic, open a payment page for the Basic product, with the signed-in person's id as the reference, and answer with its address.When the chosen plan is Pro, open a payment page for the Pro product, with the signed-in person's id as the reference, and answer with its address.

By the given plan:

When it is "Basic":

Opens a payment page for the product Basic, with the reference the id of the person this run is for, as basic page.

Stops and answers with url = the basic page's url.

When it is "Pro":

Opens a payment page for the product Pro, with the reference the id of the person this run is for, as pro page.

Stops and answers with url = the pro page's url.

Record a PlanStripe says a plan started or changed, and the account takes it.

What it is given

Producttext, needed

Subscriptiontext, needed

Statustext, needed

What it does

  • input.productwhat the caller gives
  • accounta value it works out
  • Accounta record type
  • Accountan attribute of a record or an answer
  • caller.idwhat it knows without being told

A plan started or changed.It reads the product, the subscription and its status, and it runs as the person who pays, who is the signed-in person.Find the Account whose person_id is the signed-in person.When none matches, stop.

account is

Find the first Account where

person_id is caller.id

when none carry on without one

When account holds no value

Answer

Change that Account's plan to the product, its subscription_id to the given subscription, and its state to active when the status is active, or to late for any other status.

Change the Account where

id is account.id

set plan to input.product

set state to "active" when the given status is "active", otherwise "late"

set subscription_id to input.subscription

End a PlanStripe says a plan ended, and the account goes back to free.

What it is given

Nothing. It needs no input.

What it does

  • accounta value it works out
  • Accounta record type
  • Accountan attribute of a record or an answer
  • "Free"a fixed text or number
  • caller.idwhat it knows without being told

A plan ended.It runs as the person who paid, who is the signed-in person.Find the Account whose person_id is the signed-in person.When none matches, stop.

account is

Find the first Account where

person_id is caller.id

when none carry on without one

When account holds no value

Answer

Change that Account's plan to Free and its state to ended.

Change the Account where

id is account.id

set plan to "Free"

set state to "ended"

Warn of a Failed PaymentStripe says a payment failed. The account is marked late and the person gets an email.

What it is given

Nothing. It needs no input.

What it does

  • accounta value it works out
  • Accounta record type
  • Accountan attribute of a record or an answer
  • "late"a fixed text or number
  • caller.idwhat it knows without being told
  • toa part of an email or a payment

When a payment fails, find the Account of the person who pays, whose person_id is the signed-in person.When none matches, stop.

account is

Find the first Account where

person_id is caller.id

when none carry on without one

When account holds no value

Answer

Change that Account's state to late.

The plan stays, because Stripe tries the card again before it ends the plan.

Change the Account where

id is account.id

set state to "late"

Email that Account's address with the subject "Your payment did not go through", naming its plan and the link to the billing page of the app.

Send an email

to account.email

template "Warn of a Failed Payment Email"

link the setting SITE_ADDRESS

plan account.plan

Warn of a Trial EndingStripe says a free trial ends in 3 days, and the person gets an email.

What it is given

Nothing. It needs no input.

What it does

  • accounta value it works out
  • Accounta record type
  • Accountan attribute of a record or an answer
  • caller.idwhat it knows without being told
  • toa part of an email or a payment

A free trial ends in 3 days.It runs as the person who pays, who is the signed-in person.Find the Account whose person_id is the signed-in person.When none matches, stop.

account is

Find the first Account where

person_id is caller.id

when none carry on without one

When account holds no value

Answer

Email that Account's address with the subject "Your free trial ends in 3 days", naming its plan and the link to the billing page of the app.

Send an email

to account.email

template "Warn of a Trial Ending Email"

link the setting SITE_ADDRESS

plan account.plan

Read My PlanYour app asks a person's plan before it opens a feature.

Who starts it

From the webGET /plan

What it is given

Nothing. It needs no input.

What it does

  • accounta value it works out
  • Accounta record type
  • Accountan attribute of a record or an answer
  • "active"a fixed text or number
  • caller.idwhat it knows without being told
  • refusewhere it says no

Find the Account whose person_id is the signed-in person.When none matches, refuse with "Sign up first".

account is

Find the first Account where

person_id is caller.id

when none refuse "Sign up first"

Answer with that Account's plan and state, and paid set to yes when the state is active or late.

A person whose payment failed keeps their plan until Stripe ends it, so a card that fails once does not lock them out.

Answer

paid is

account.state is one of "active" or "late"

plan is account.plan

state is account.state

Open BillingA person who pays opens the Stripe billing page, to change the card or the plan, or to cancel.

Who starts it

From the webPOST /billing

What it is given

Nothing. It needs no input.

What it does

  • accounta value it works out
  • Accounta record type
  • Accountan attribute of a record or an answer
  • "free"a fixed text or number
  • caller.idwhat it knows without being told
  • refusewhere it says no

Find the Account whose person_id is the signed-in person.When none matches, refuse with "Sign up first".

account is

Find the first Account where

person_id is caller.id

when none refuse "Sign up first"

When that Account's state is free, refuse with "Pick a plan first".

When account.state is "free"

Refuse "Pick a plan first"

Open the Stripe billing page of the signed-in person, and answer with its address.

Opens the billing portal of the person this run is for, as billing.

Answer

url is billing.url

Who signs in and what they may read

People sign up and sign in with an email address and a password, or with a provider you add, such as Google.

  • Account: A person reads only their own account. Only Stripe's events change a plan.

Money

  • Basic, EUR 9.00, every month. Basic: EUR 9 a month, free for the first 14 days. Paid on a Stripe page, on your own Stripe account.
  • Pro, EUR 29.00, every month. Pro: EUR 29 a month, free for the first 14 days. Paid on a Stripe page, on your own Stripe account.
  • When a subscription starts, Record a Plan runs.
  • When a subscription changes, Record a Plan runs.
  • When a subscription ends, End a Plan runs.
  • When a payment fails, Warn of a Failed Payment runs.
  • When a trial ends in 3 days, Warn of a Trial Ending runs.

Web endpoints

What it doesWho may callAddress
A signed-in person opens an account.a signed-in personPOST /account
A person picks a plan.a signed-in personPOST /plan
Your app reads the person's plan.a signed-in personGET /plan
A person opens the billing page.a signed-in personPOST /billing

What it cannot do yet

  • It does not build your app or its pages. Your app calls it with the person's sign-in.
  • It does not count use, such as seats or calls. Each plan is one price a month.
  • A plan changes or ends on the Stripe billing page. Stripe sends the change back, and the account follows it.

Build the pages with your tool

Typestate is the backend. Paste this brief into Lovable, v0, Claude Code or the tool that builds your pages.

Show the brief
Build the pages for "Subscription app starter". The backend runs on Typestate and answers these web endpoints: POST /account: A signed-in person opens an account. Who may call: a signed-in person. POST /plan: A person picks a plan. Who may call: a signed-in person. GET /plan: Your app reads the person's plan. Who may call: a signed-in person. POST /billing: A person opens the billing page. Who may call: a signed-in person. People sign up at POST /auth/signup and sign in at POST /auth/login, which answers a token. Send it as Authorization: Bearer <token>. A refused call answers a sentence that can be shown to the person as it is.

Start it

  1. Tell your agent the sentence on the right.
  2. It asks you for the https address of your app, such as https://app.example.com. People come back to it after they pay.
  3. It asks you for the address emails come from, on a domain you verified at Resend, such as "Riverside Physio <hello@riverside.example>".
  4. It tells you what only you can do, such as pasting your Stripe key in the cockpit. A key never goes through the agent.
  5. Open your Sandbox address and try it yourself.

Starting from this blueprint does not count against the action changes of your plan. The Free plan runs it on Sandbox. Taking real money on Live needs a paid plan.

Start the subscription app starter blueprint on TypestateChoose a plan

Related blueprints