Appearance
Payment Intents and subscriptions
This page covers two flows: one-time payments using Payment Intents (with a payment form in your app) and subscriptions (recurring charges) created from your backend.
Handling payments in a real app
- Never store card numbers — Card data stays in Stripe. You use Stripe’s UI (Checkout or the payment element) to collect it; your backend only tells Stripe what to charge.
- Use a Stripe customer per user — In most apps, each signed-in user has a Stripe Customer. Store the customer ID (
cus_...) in your database so you can charge them again or create subscriptions. - Use the backend for the charge — Create or update Stripe objects from the API Endpoints tab of the Backend Workflows subtab in Data & API (or backend workflows), and return only what your interface needs (e.g.
client_secret,url, or a success flag).
One-time payments (Payment Intents)
Use this when you charge once (e.g. a single purchase) and you want the payment form inside your app rather than on a Stripe-hosted page.
Flow
- Create or reuse a customer — When a user pays for the first time, use Create Customer and save the returned Customer ID (
cus_...) in your database (e.g. on the user record). - Create a Payment Intent — In an API (e.g.
POST /payments/intent), use Create Payment Intent with:- Amount (in cents) and Currency
- Customer (your saved
cus_..., if you have it) - Optional: Receipt Email, Description, Metadata (e.g. order ID)
- Return the client_secret (and optionally the Payment Intent
id) to the front end. - Collect payment in the interface — Mount Stripe’s payment form in your page with Stripe.js, using that
client_secret. This step is custom code. On submit, Stripe confirms the payment. - Handle success and refunds — To refund, use Create Refund with the Payment Intent (or Charge).
You can also confirm a payment intent from the backend with Confirm Payment Intent. A declined card is a normal outcome that surfaces as a thrown error with no result, so wrap the action in a Try/Catch and read the reason with Retrieve Payment Intent (last_payment_error). For manual-capture flows, capture the held funds later with Capture Payment Intent.
What to store in your database
- Customer ID —
cus_...for the signed-in user. - Payment Intent ID —
pi_...for support and refunds. - Your own reference — e.g. order ID in Metadata.
Subscriptions
Use subscriptions when you charge repeatedly (e.g. monthly access to a plan).
Flow
- Create products and prices in Stripe — Use Create Product for the plan and Create Price with Recurring (e.g.
{"interval":"month"}). - Create or reuse a customer — If the user doesn’t have a Stripe customer yet, use Create Customer and store
cus_.... - Create the subscription — In an API (e.g.
POST /subscriptions), use Create Subscription with:- Customer = your stored
cus_... - Items = at least one item with a Price (
price_...)
- Customer = your stored
- Keep your app in sync — Use Retrieve Subscription or List Subscriptions to check status (
active,canceled, etc.). Use Update Subscription to change plans and Cancel Subscription (or cancel at period end) to stop access.
What to store in your database
- Customer ID —
cus_... - Subscription ID —
sub_...for that user. - Price ID —
price_...so you can show their plan and handle upgrades/downgrades.
You can also offer subscriptions via Stripe Checkout (mode subscription); see Stripe Checkout.
Good to know
- Create needs a recurring price —
Create Subscriptionrejects one-time prices. The customer also needs a default payment method, or you defer payment withpayment_behavior: "default_incomplete"or atrial_period_days. - Cancel is immediate —
Cancel Subscriptionends the subscription right away. To stop it at the end of the paid period, useUpdate Subscriptionwithcancel_at_period_end: "true";"false"un-schedules a pending cancellation. - Two different pauses —
Update Subscriptionwithpause_collectionpauses billing while the status staysactive; clear it with the Resume choice on that action (it sends an emptypause_collection). The separateResume Subscriptionaction only targets a subscription whose status ispaused(a trial that ended with no payment method) and rejects anything else, including canceled ones. Resuming issues an invoice, and the subscription only returns toactiveonce that invoice is paid. - Migrate is one-way —
Migrate Subscriptionupgrades to theflexiblebilling mode and cannot be reversed. - The billing period lives on the items —
current_period_startandcurrent_period_endare on each item (items.data[]) in what the API returns, not at the top level. Webhook payloads still carry the legacy top-level fields, so read fromitems.data[0]when you re-read a subscription through an action. List SubscriptionsHides canceled ones by default — omittingstatusreturns only non-canceled subscriptions; passallorendedto include canceled ones.
Next: Webhooks or Workflow examples

