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

# Coming from Stripe?

> How moneydevkit differs from Stripe and how to migrate your payment flow

If you're familiar with Stripe, you'll find moneydevkit's approach refreshingly simple. The biggest difference: **no webhook handlers to write**. You set up the `/api/mdk` route once during initial setup, and that's it—no webhook event handling code required.

## Key Differences

| Stripe                                    | moneydevkit                             |
| ----------------------------------------- | --------------------------------------- |
| Create PaymentIntent, wait for webhook    | Create checkout, check status directly  |
| Write webhook handlers for payment events | One-time route setup, no event handlers |
| Handle webhook verification & retries     | Payment status available immediately    |
| Complex idempotency handling              | Simple client-side state                |

## No Webhook Handlers to Write

With Stripe, your backend typically relies on webhooks to know when a payment succeeds:

```tsx theme={null}
// Stripe: You need a webhook endpoint
app.post('/webhooks/stripe', async (req, res) => {
  const event = stripe.webhooks.constructEvent(...)
  if (event.type === 'payment_intent.succeeded') {
    // Now you know payment succeeded
    await fulfillOrder(event.data.object.metadata.orderId)
  }
})
```

With moneydevkit, payment status is available directly on your success page:

```tsx theme={null}
// moneydevkit: No webhook needed
'use client'
import { useCheckoutSuccess } from '@moneydevkit/nextjs'

export default function SuccessPage() {
  const { isCheckoutPaid, metadata } = useCheckoutSuccess()

  if (isCheckoutPaid) {
    // Payment confirmed - fulfill the order
    return <p>Payment confirmed!</p>
  }

  return <p>Verifying payment…</p>
}
```

## What About the `/api/mdk` Route?

The initial setup includes an `/api/mdk` route:

```js theme={null}
// app/api/mdk/route.js
export { POST } from '@moneydevkit/nextjs/server/route'
```

This is a one-time setup—you export the handler and you're done. Unlike Stripe webhooks, you don't write any event handling code. The route is internal infrastructure that allows your serverless Lightning node to receive payments. You never need to parse events, verify signatures, or handle retries.

<Info>
  The `/api/mdk` route may handle additional functionality in the future, but payment confirmation will always be available directly via `useCheckoutSuccess`.
</Info>

## Linking Checkouts to Your Users

moneydevkit has first-class support for linking checkouts to your authenticated users via `customer.externalId`.

### Using `externalId` for Authenticated Users

When a user is logged into your app, pass their ID via `customer.externalId`:

```tsx theme={null}
'use client'
import { useCheckout } from '@moneydevkit/nextjs'

export default function PurchasePage({ user }) {
  const { createCheckout, isLoading } = useCheckout()

  const handlePurchase = async () => {
    const result = await createCheckout({
      type: 'AMOUNT',
      amount: 500,
      currency: 'USD',
      title: 'Premium Plan',
      successUrl: '/checkout/success',
      customer: {
        externalId: user.id,  // Your app's user ID
        name: user.name,
        email: user.email,
      }
    })

    if (!result.error) {
      window.location.href = result.data.checkoutUrl
    }
  }

  return <button onClick={handlePurchase} disabled={isLoading}>Upgrade</button>
}
```

When `externalId` is provided:

* The checkout is linked to a customer record in moneydevkit
* If the customer already exists (matched by `externalId`), their stored data is used
* Returning customers won't be asked for information you already have
* All checkouts and orders are linked to the same customer record

### Using `metadata` for Custom Data

For other custom data (like a registration ID, order reference, or plan type), use the `metadata` field:

```tsx theme={null}
const result = await createCheckout({
  type: 'AMOUNT',
  amount: 500,
  currency: 'USD',
  title: 'Event Registration',
  successUrl: '/checkout/success',
  customer: {
    externalId: user.id,
  },
  metadata: {
    registrationId: 'reg_xyz789',
    plan: 'premium'
  }
})
```

Retrieve it on your success page:

```tsx theme={null}
'use client'
import { useCheckoutSuccess } from '@moneydevkit/nextjs'

export default function SuccessPage() {
  const { isCheckoutPaid, metadata } = useCheckoutSuccess()

  if (isCheckoutPaid) {
    const registrationId = metadata?.registrationId
    // Use registrationId to update your database, send confirmation, etc.
    return <p>Registration {registrationId} confirmed!</p>
  }

  return <p>Verifying payment…</p>
}
```

See the [Customers documentation](/dashboard/customers) for more details.

## Quick Comparison: Checkout Flow

<Steps>
  <Step title="Stripe: Create PaymentIntent">
    ```tsx theme={null}
    // Server-side
    const paymentIntent = await stripe.paymentIntents.create({
      amount: 500,
      currency: 'usd',
      metadata: { orderId: '123' }
    })
    // Return clientSecret to frontend
    ```
  </Step>

  <Step title="moneydevkit: Create Checkout">
    ```tsx theme={null}
    // Client-side
    const result = await createCheckout({
      type: 'AMOUNT',
      amount: 500,
      currency: 'USD',
      metadata: { orderId: '123' },
      successUrl: '/success'
    })
    window.location.href = result.data.checkoutUrl
    ```
  </Step>
</Steps>

<Steps>
  <Step title="Stripe: Write Webhook Handler">
    ```tsx theme={null}
    // Server-side webhook handler you must write and maintain
    if (event.type === 'payment_intent.succeeded') {
      await fulfillOrder(event.data.object.metadata.orderId)
    }
    ```
  </Step>

  <Step title="moneydevkit: Check on Success Page">
    ```tsx theme={null}
    // No webhook handler needed - check status directly
    const { isCheckoutPaid, metadata } = useCheckoutSuccess()
    if (isCheckoutPaid) {
      // metadata.orderId available here
    }
    ```
  </Step>
</Steps>

## Summary

* **No webhook handlers to write** - One-time `/api/mdk` route setup, then you're done
* **Payment status available directly** - Use `useCheckoutSuccess` instead of waiting for webhook events
* **`externalId` is first-class** - Link checkouts to your authenticated users, with automatic data backfill for returning customers
* **Use `metadata`** for custom checkout data (registration IDs, order references, etc.)
