> ## Documentation Index
> Fetch the complete documentation index at: https://docs.devin.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Build a Stripe Checkout Flow

export const UseCaseHero = ({title, description, prompt, category, features, devinUrl, agent, intent, playbookId, type}) => {
  const encodedPrompt = encodeURIComponent(prompt || '');
  const tag = 'docs-use-case-gallery';
  const utm = 'utm_source=docs&utm_medium=use-case-gallery&utm_campaign=hero-cta';
  const agentParams = (agent ? '&agent=' + agent : '') + (intent ? '&intent=' + intent : '') + (playbookId ? '&playbookId=' + playbookId : '');
  const devinHref = type === 'schedule' ? 'https://app.devin.ai/settings/schedules/create?' + utm + agentParams + (prompt ? '&prompt=' + encodedPrompt : '') : type === 'review' ? 'https://app.devin.ai/review?' + utm : agent === 'ada' ? 'https://app.devin.ai/search?' + utm + '&noSubmit=true' + (prompt ? '&prompt=' + encodedPrompt : '') : devinUrl ? devinUrl.includes('?') ? devinUrl + '&' + utm + agentParams : devinUrl + '?' + utm + agentParams : prompt ? 'https://app.devin.ai/?tags=' + tag + '&' + utm + agentParams + '&prompt=' + encodedPrompt : 'https://app.devin.ai/?' + utm + agentParams;
  const buttonLabel = type === 'schedule' ? 'Schedule in Devin ↗' : type === 'review' ? 'Set Up Devin Review ↗' : agent === 'advanced' ? 'Try in Devin ↗' : agent === 'dana' ? 'Try in Dana ↗' : agent === 'ada' ? 'Try in Ask Devin ↗' : 'Try in Devin ↗';
  const featureList = features ? features.split(',').map(f => f.trim()) : [];
  return <div className="uc-hero">
      <div className="uc-hero-inner">
        <div className="uc-hero-left">
          <h1 className="uc-hero-title">{title}</h1>
          <p className="uc-hero-desc">{description}</p>
          <div>
            <a href={devinHref} target="_blank" rel="noopener noreferrer" className="try-in-devin-btn">
              {buttonLabel}
            </a>
          </div>
        </div>
        <div className="uc-hero-meta">
          <div className="uc-meta-item">
            <span className="uc-meta-label">Author</span>
            <span className="uc-meta-value">Cognition</span>
          </div>
          <div className="uc-meta-item">
            <span className="uc-meta-label">Category</span>
            <span className="uc-meta-value">{category}</span>
          </div>
          {featureList.length > 0 && <div className="uc-meta-item">
              <span className="uc-meta-label">Features</span>
              <span className="uc-meta-value">{featureList.join(', ')}</span>
            </div>}
        </div>
      </div>
    </div>;
};

export const PromptBlock = ({children, type, agent, intent, playbookId}) => {
  var utm = 'utm_source=docs&utm_medium=use-case-gallery&utm_campaign=prompt-block';
  var tag = 'docs-use-case-gallery';
  var agentParams = (agent ? '&agent=' + agent : '') + (intent ? '&intent=' + intent : '') + (playbookId ? '&playbookId=' + playbookId : '');
  var label = type === 'schedule' ? 'Schedule in Devin' : type === 'playbook' ? 'Create Playbook' : type === 'knowledge' ? 'Add to Knowledge' : agent === 'advanced' ? 'Try in Devin' : agent === 'dana' ? 'Try in Dana' : agent === 'ada' ? 'Try in Ask Devin' : 'Try in Devin';
  var buildUrl = function (text) {
    var encoded = encodeURIComponent(text);
    if (type === 'schedule') return 'https://app.devin.ai/settings/schedules/create?' + utm + agentParams + '&prompt=' + encoded;
    if (type === 'playbook') return 'https://app.devin.ai/settings/playbooks/create?' + utm + '&body=' + encoded;
    if (type === 'knowledge') return 'https://app.devin.ai/knowledge?' + utm + '&body=' + encoded;
    if (agent === 'ada') return 'https://app.devin.ai/search?' + utm + '&noSubmit=true&prompt=' + encoded;
    return 'https://app.devin.ai/?tags=' + tag + '&' + utm + agentParams + '&prompt=' + encoded;
  };
  const ref = React.useRef(null);
  const [href, setHref] = React.useState('#');
  React.useEffect(() => {
    if (!ref.current) return;
    var codeEl = ref.current.querySelector('pre code');
    if (codeEl) {
      var text = codeEl.textContent.trim();
      if (text) setHref(buildUrl(text));
    }
    var header = ref.current.querySelector('[data-component-part="code-block-header"]');
    if (header && !header.querySelector('.prompt-block-devin-link')) {
      var link = document.createElement('a');
      link.href = href;
      link.target = '_blank';
      link.rel = 'noopener noreferrer';
      link.className = 'prompt-block-devin-link';
      link.style.cssText = 'display:inline-flex;align-items:center;gap:6px;text-decoration:none;color:#fff;font-size:11px;font-weight:500;padding:4px 10px;border-radius:6px;white-space:nowrap;background:#317CFF;transition:background 0.2s;margin-left:8px;';
      link.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg> ' + label;
      link.onmouseenter = function () {
        link.style.background = '#2968D9';
      };
      link.onmouseleave = function () {
        link.style.background = '#317CFF';
      };
      header.appendChild(link);
    }
    var existingLink = ref.current.querySelector('.prompt-block-devin-link');
    if (existingLink && href !== '#') existingLink.href = href;
  });
  return <div className="prompt-block" ref={ref}>{children}</div>;
};

<UseCaseHero title="Build a Stripe Checkout Flow" description="Hand Devin a checkout spec with your Stripe sandbox keys and get a working payment flow — pricing page, checkout session, webhook handler, and confirmation page — verified in the browser." prompt="Implement a Stripe checkout flow for our SaaS app. Pricing page at /pricing with three tiers: Starter $19/mo (5 projects, 10GB, email support), Pro $49/mo (unlimited projects, 100GB, priority support), Team $99/mo (Pro + team management, SSO, audit log). Each card has a Subscribe button that creates a Stripe Checkout session via POST /api/checkout/sessions. Webhook at /api/webhooks/stripe handles checkout.session.completed — update user plan and subscription_id in the database, verify signature with STRIPE_WEBHOOK_SECRET. Success page at /checkout/success shows plan name, amount, and Go to Dashboard button. Before writing code, outline your plan for review. Use our Next.js app, Prisma ORM, follow patterns in src/pages/settings/billing.tsx. STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET from env vars. Write tests for webhook verification, session creation, and plan-update logic. Spin up the dev server and verify the entire flow in the browser with Stripe test card 4242 4242 4242 4242. Do not open a PR until everything works end-to-end." category="Feature Development" features="" />

<div className="uc-detail-wrapper">
  <Tip>Don't want to set this up manually? Paste a link to this page into a Devin session and ask it to set everything up for you.</Tip>

  <Steps>
    <Step title="(Optional) Scope the codebase with Ask Devin">
      If you're not sure how your app handles payments today — or which files and patterns to reference in your spec — use [Ask Devin](https://app.devin.ai/search?utm_source=docs\&utm_medium=use-case-gallery) to investigate first:

      <PromptBlock agent="ada">
        ```txt Scope the checkout implementation theme={null}
        How does our app currently handle billing? Show me:
        1. Where user plans/subscriptions are stored in the database
        2. Any existing Stripe integration files or API routes
        3. How other payment-related pages are structured (e.g., settings/billing)
        4. What ORM we use and how migrations are typically created
        ```
      </PromptBlock>

      Use the answers to fill in your spec — reference specific files, table names, and patterns so Devin builds something that fits naturally into your codebase. You can also start a Devin session directly from Ask Devin, and it will carry over everything it learned as context.
    </Step>

    <Step title="Add Stripe sandbox keys">
      Devin needs Stripe **test-mode** keys to create checkout sessions and verify the webhook handler. Always use sandbox credentials — never give Devin production Stripe keys.

      The simplest approach is to store them as [organization secrets](/product-guides/secrets) before starting the session:

      1. Go to **Settings > Secrets** and add:
         * `STRIPE_SECRET_KEY` — your test-mode secret key from the [Stripe Dashboard](https://dashboard.stripe.com/test/apikeys)
         * `STRIPE_WEBHOOK_SECRET` — the signing secret from your [webhook endpoint settings](https://dashboard.stripe.com/test/webhooks)
      2. Devin accesses these as environment variables, so they never end up hardcoded in your source code.

      <Note>Organization secrets must be added **before** starting the session — they're injected at session start. Alternatively, you can provide secrets during the session using the chat, and Devin will also proactively ask you for any credentials it needs when it encounters missing environment variables.</Note>
    </Step>

    <Step title="Hand off your checkout spec">
      Paste your spec — from a PRD, a Linear ticket, or a detailed Slack message — directly into Devin. A good checkout spec covers the pricing tiers, the payment flow, and what happens after a successful payment. The more structured, the better.

      <PromptBlock>
        ```txt Implement Stripe checkout flow theme={null}
        Implement a Stripe checkout flow for our SaaS app:

        ## Pricing page
        - New route at /pricing with three tier cards:
          - Starter: $19/mo — 5 projects, 10GB storage, email support
          - Pro: $49/mo — unlimited projects, 100GB storage, priority support
          - Team: $99/mo — everything in Pro + team management, SSO, audit log
        - Each card has a "Subscribe" button that initiates checkout

        ## Checkout
        - POST /api/checkout/sessions — creates a Stripe Checkout session
          with the selected price ID, customer email, and success/cancel URLs
        - Redirect the user to Stripe's hosted checkout page
        - On success, redirect to /checkout/success?session_id={CHECKOUT_SESSION_ID}

        ## Webhook
        - POST /api/webhooks/stripe — receives Stripe events
        - Handle checkout.session.completed: update the user's plan and
          subscription_id in the database
        - Verify the webhook signature using STRIPE_WEBHOOK_SECRET

        ## Success page
        - /checkout/success — fetch the session details from Stripe,
          display the plan name, amount, and a "Go to Dashboard" button

        ## Planning
        - Before writing any code, outline your implementation plan and share
          it with me for approval. List the files you'll create or modify,
          the database changes, and the order of implementation.

        ## Technical notes
        - Follow the patterns in src/pages/settings/billing.tsx for the UI
        - Use Prisma: add a subscriptions table (id, user_id, stripe_subscription_id,
          plan, status, current_period_end)
        - STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET are available as env vars

        ## Testing & verification
        - Write tests for webhook signature verification, session creation,
          and the plan-update logic
        - After implementing, spin up the local dev server and verify the
          entire flow in the browser: navigate to /pricing, click Subscribe,
          complete a test payment with Stripe's test card (4242 4242 4242 4242),
          confirm the success page renders, and verify the database was updated
        - Do not open a PR until you've confirmed everything works end-to-end
        ```
      </PromptBlock>

      A good spec for Devin includes three things: **what** to build (pricing tiers, checkout flow, webhook handler), **where** it lives (routes, tables, files), and **how** it fits in (existing patterns to follow). You don't need to specify every implementation detail — Devin investigates your codebase to fill in the gaps.
    </Step>

    <Step title="Devin builds and verifies in the browser">
      Devin reads your spec, explores the codebase for matching patterns, then implements across the full stack. Before opening a PR, it runs your app locally and opens its [built-in browser](/work-with-devin/devin-session-tools) to verify the checkout flow works end-to-end.

      Here's what that looks like for the Stripe checkout example:

      1. **Creates the migration** — Adds the `subscriptions` table with columns for `user_id`, `stripe_subscription_id`, `plan`, `status`, and `current_period_end`
      2. **Builds the pricing page** — Creates the three-tier pricing cards at `/pricing`, each with a "Subscribe" button that posts to the checkout API
      3. **Implements checkout session creation** — Builds `POST /api/checkout/sessions` that creates a Stripe Checkout session with the correct price ID, customer email, and redirect URLs
      4. **Adds the webhook handler** — Implements `POST /api/webhooks/stripe` with signature verification, `checkout.session.completed` event handling, and database updates
      5. **Builds the success page** — Creates `/checkout/success` that fetches the Stripe session, displays the plan name, amount charged, and a "Go to Dashboard" link
      6. **Writes tests** — Tests for webhook signature verification (valid, invalid, missing), checkout session creation, and the plan-update database logic
      7. **Opens the browser** — Starts the dev server, navigates to `/pricing`, clicks "Subscribe" on the Pro tier, verifies the Stripe Checkout redirect works, and checks that the success page renders correctly after a test payment
      8. **Opens a PR** — Delivers all changes with a summary of what was implemented and how it was verified

      The browser verification step catches issues that unit tests miss — a pricing card that doesn't trigger checkout, a redirect URL that 404s, or a success page that fails to load session details. If you've defined a [local testing skill](/product-guides/skills), Devin follows those steps automatically for every feature it builds.
    </Step>

    <Step title="Iterate from the PR">
      Once the PR is open, send follow-up prompts in the same session to extend or adjust the checkout flow.

      <PromptBlock>
        ```txt Add subscription management theme={null}
        Add a "Manage Subscription" section to /settings/billing that shows the
        current plan, next billing date, and a "Cancel Subscription" button.
        Use the Stripe customer portal for cancellation — create a portal session
        and redirect the user.
        ```
      </PromptBlock>

      <PromptBlock>
        ```txt Add annual pricing toggle theme={null}
        Add a monthly/annual toggle on the pricing page. Annual plans get a 20%
        discount. Show the per-month price with a "billed annually" note and a
        "Save 20%" badge on each card when annual is selected.
        ```
      </PromptBlock>
    </Step>

    <Step title="Review the PR with Devin Review">
      Once Devin opens the PR, use [Devin Review](https://app.devin.ai/review?utm_source=docs\&utm_medium=use-case-gallery) to review the changes. Devin Review has full context of your codebase and can catch bugs, security issues, and style inconsistencies across the diff. You can ask follow-up questions in the review chat — for instance, "Does the webhook handler validate the event type before processing?" — and Devin will answer grounded in the actual code.
    </Step>
  </Steps>
</div>
