Payments

How kreogen handles payments and billing.

kreogen uses Stripe by default for payments and billing. Implementing Stripe in your project is straightforward.

Stripe is an optional integration. If STRIPE_SECRET_KEY is not set, the stripe export will be undefined and payment webhooks will be skipped.

In-App Purchases

You can use Stripe anywhere in your app by importing the stripe object like so:

page.tsx {1,5}
import { stripe } from '@kreogen/payments';

// ...

await stripe?.prices.list();

Webhooks

kreogen ships a Stripe webhook handler at apps/api/app/webhooks/payments. It verifies the signature, rejects a bad one with 400, and records the event id before handling it so that a redelivery is a no-op. See the API app for why each of those matters.

Anti-Fraud

As your app grows, you will inevitably encounter credit card fraud. Stripe Radar is enabled by default if you integrate payments using their SDK as described above. This provides a set of tools to help you detect and prevent fraud.

Stripe Radar supports more advanced anti-fraud features if you embed the Stripe JS script in every page load. This is not enabled by default in kreogen, but you can add it as follows:

1. Load Stripe.js in the app

Add the script to apps/app/app/layout.tsx, between the opening <html> and <body> tags:

apps/app/app/layout.tsx
import './styles.css';
import { AnalyticsProvider } from '@kreogen/analytics/provider';
import { DesignSystemProvider } from '@kreogen/design-system';
import { fonts } from '@kreogen/design-system/lib/fonts';
import Script from 'next/script';
import type { ReactNode } from 'react';

interface RootLayoutProperties {
  readonly children: ReactNode;
}

const RootLayout = ({ children }: RootLayoutProperties) => (
  <html className={fonts} lang="en" suppressHydrationWarning>
    <Script src="https://js.stripe.com/v3/" />
    <body>
      <AnalyticsProvider>
        <DesignSystemProvider>{children}</DesignSystemProvider>
      </AnalyticsProvider>
    </body>
  </html>
);

export default RootLayout;

2. Do the same on the marketing site

apps/web/app/[locale]/layout.tsx needs the identical addition. Radar's signal comes from seeing the visitor before checkout, so loading it only on the page that takes the card defeats most of the point.

3. Allow the origin in the CSP

https://js.stripe.com must be in scriptSrc, and https://api.stripe.com in connectSrc. Both are already grouped under STRIPE in packages/security/proxy.ts — but if you have trimmed that policy, this is the failure that works locally and is blocked in production, because CSP is report-only in development. See security headers.

4. Rate limit the checkout route

Card testing shows up as a burst of attempts from one source. Apply a limiter to whichever route creates a checkout session:

import { createRateLimiter, slidingWindow } from '@kreogen/rate-limit';

const limiter = createRateLimiter({
  limiter: slidingWindow(5, '1 m'),
  prefix: 'checkout',
});

const { success } = await limiter.limit(userId);

if (!success) {
  throw new Error('Too many attempts. Try again shortly.');
}

Fraud beyond the basics

kreogen deliberately ships no managed bot detection or IP reputation service. Those need a vendor account and a network hop on every request, and the right answer differs by project — a low-value subscription and a high-value marketplace have very different exposure.

If a project needs it, the effective options in rough order of effort are:

  1. Stripe Radar — already in the payment path, no new integration, and it sees signals your application cannot.
  2. Edge rules at your CDN or reverse proxy, where the request is cheapest to reject.
  3. A bot-detection vendor in middleware, if the first two prove insufficient.

Reach for these when you have evidence of abuse, not preemptively.