Skip to main content

Stripe API

Launch provides type-safe tRPC endpoints for all Stripe operations. These endpoints handle payment intents, checkout sessions, and product management.

Available Endpoints

stripe.getProducts

Fetches all active products and prices from your Stripe account.
const { data } = trpc.stripe.getProducts.useQuery();

// Returns
{
  products: [
    {
      id: "prod_xxx",
      name: "Pro Yearly",
      description: "Annual subscription",
      prices: [
        { id: "price_xxx", unit_amount: 9999, recurring: { interval: "year" } },
      ],
    },
  ];
}

stripe.createPaymentIntent

Creates a payment intent for one-time purchases.
const mutation = trpc.stripe.createPaymentIntent.useMutation();

const { clientSecret } = await mutation.mutateAsync({
  amount: 999, // Amount in cents
  currency: "usd",
  description: "Pro Lifetime Access",
});

stripe.createCheckoutSession

Creates a checkout session for subscriptions.
const mutation = trpc.stripe.createCheckoutSession.useMutation();

const { url } = await mutation.mutateAsync({
  priceId: "price_xxx",
  mode: "subscription",
  successUrl: "myapp://success",
  cancelUrl: "myapp://cancel",
});

stripe.getSubscriptionStatus

Returns the current Stripe subscription for the authenticated user (if any), plus the local subscription record used for display.
const { data } = trpc.stripe.getSubscriptionStatus.useQuery();

// Returns
{
  hasSubscription: true,
  subscription: { id: "sub_xxx", status: "active", cancel_at_period_end: false },
  localSubscription: {
    id: "sub_xxx",
    status: "active",
    cancelAtPeriodEnd: false,
    currentPeriodEnd: "2026-01-23T12:34:56.000Z",
  },
}

stripe.cancelSubscription

Schedules a subscription to cancel at period end.
const mutation = trpc.stripe.cancelSubscription.useMutation();
await mutation.mutateAsync();

stripe.resumeSubscription

Resumes a subscription that was scheduled to cancel at period end.
const mutation = trpc.stripe.resumeSubscription.useMutation();
await mutation.mutateAsync();

stripe.changeSubscriptionPlan

Switches between subscription prices (monthly ↔ annual). Upgrades take effect immediately with proration; downgrades take effect at renewal.
const mutation = trpc.stripe.changeSubscriptionPlan.useMutation();
await mutation.mutateAsync({
  priceId: "price_new",
});

API Structure

Location: apps/api/src/routers/stripe.ts
export const stripeRouter = router({
  getProducts: publicProcedure.query(async () => {
    // Fetch products from Stripe
  }),

  createPaymentIntent: protectedProcedure
    .input(
      z.object({
        amount: z.number(),
        currency: z.string(),
        description: z.string().optional(),
      })
    )
    .mutation(async ({ input, ctx }) => {
      // Create payment intent
    }),

  createCheckoutSession: protectedProcedure
    .input(
      z.object({
        priceId: z.string(),
        mode: z.enum(["subscription", "payment"]),
        successUrl: z.string(),
        cancelUrl: z.string(),
      })
    )
    .mutation(async ({ input, ctx }) => {
      // Create checkout session
    }),

  getSubscriptionStatus: protectedProcedure.query(async ({ ctx }) => {
    // Return Stripe subscription and local subscription record
  }),

  cancelSubscription: protectedProcedure.mutation(async ({ ctx }) => {
    // Set cancel_at_period_end to true
  }),

  resumeSubscription: protectedProcedure.mutation(async ({ ctx }) => {
    // Set cancel_at_period_end to false
  }),

  changeSubscriptionPlan: protectedProcedure
    .input(z.object({ priceId: z.string() }))
    .mutation(async ({ input, ctx }) => {
      // Swap subscription price with proration rules
    }),
});

Authentication

  • getProducts - Public, no authentication required
  • createPaymentIntent - Protected, requires authenticated user
  • createCheckoutSession - Protected, requires authenticated user
  • getSubscriptionStatus - Protected, requires authenticated user
  • cancelSubscription - Protected, requires authenticated user
  • resumeSubscription - Protected, requires authenticated user
  • changeSubscriptionPlan - Protected, requires authenticated user

Customer Management

When a user makes their first payment, a Stripe customer is automatically created and linked to their account:
// Automatic customer creation
const customer = await stripe.customers.create({
  email: user.email,
  name: user.name,
  metadata: { userId: user.id },
});

Error Handling

All endpoints return structured errors:
try {
  await mutation.mutateAsync({ ... });
} catch (error) {
  // error.message contains Stripe error details
  Alert.alert("Payment Failed", error.message);
}

Test Checklist

  • stripe.getProducts returns products
  • stripe.createPaymentIntent returns a client secret
  • stripe.createCheckoutSession returns a valid URL
  • stripe.getSubscriptionStatus returns subscription info (if subscribed)
  • stripe.cancelSubscription sets cancel_at_period_end
  • stripe.resumeSubscription clears cancel_at_period_end
  • stripe.changeSubscriptionPlan swaps price with correct proration

Troubleshooting

If API calls fail, verify Stripe keys in apps/api/.env (copy from apps/api/example.env first) and restart the API.

Remove / Disable

To disable payments while you configure Stripe, set: apps/mobile/features/feature-registry.tsxfeatureFlags.payments = false For production removal guidance, see Removing Features.

Next Steps