> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://mpp.dev/api/mcp` to find what you need.

# Accept multiple payment methods \[Stablecoins, cards, and Bitcoin on a single endpoint]

## Choose a signing account

### Direct

Create a server-only wallet.ts module, then import account wherever an example creates a local signing account.

```ts
import { privateKeyToAccount } from 'viem/accounts'

export const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
```

### Privy

Create an EVM wallet in Privy, fund it with the required currency on this page's network, and keep PRIVY\_APP\_SECRET server-side.

```bash
pnpm add @privy-io/node
```

Create a server-only privy.ts module, then import its account wherever an example configures account or feePayer.

```ts
import { PrivyClient } from '@privy-io/node'
import { createViemAccount } from '@privy-io/node/viem'

const privy = new PrivyClient({
  appId: process.env.PRIVY_APP_ID!,
  appSecret: process.env.PRIVY_APP_SECRET!,
})

export const account = createViemAccount(privy, {
  address: process.env.PRIVY_WALLET_ADDRESS as `0x${string}`,
  walletId: process.env.PRIVY_WALLET_ID!,
})
```

createViemAccount delegates signatures to the Privy wallet, so it replaces any local viem account in the examples on this page.

Build a payment-gated API that accepts [Tempo](/payment-methods/tempo) stablecoins, [Stripe](/payment-methods/stripe) cards, and [Lightning](/payment-methods/lightning) Bitcoin—all on the same endpoint. The server returns a `402` Challenge advertising every available method, and the client pays with whichever rail it supports.

:::info
MPP's multi-method support is additive. Each payment method is independent—you can start with one and add more at any time without changing your route handlers.
:::

## Prompt mode

Paste this into your coding agent to build the entire guide in one prompt:

```text
Use https://mpp.dev/guides/multiple-payment-methods.md as reference.
Add mppx to my app with a payment-gated endpoint that accepts
three payment methods: Tempo, Stripe, and Lightning. Charge $0.01 per request.
When payment is verified via any method, return a JSON response.
```

## How it works

When multiple methods are registered, the `402` response includes a `WWW-Authenticate` header for each one. The client picks the method it supports and sends the appropriate Credential.

```http
HTTP/1.1 402 Payment Required
WWW-Authenticate: Payment method="tempo", intent="charge", ...
WWW-Authenticate: Payment method="stripe", intent="charge", ...
WWW-Authenticate: Payment method="lightning", intent="charge", ...
```

The server verifies whichever Credential it receives. Intent shorthand such as `mppx.charge(options)` implicitly composes every registered method with that intent when they share compatible request units. Compose methods explicitly when one method needs different options—Lightning uses satoshis, while this guide prices Tempo and Stripe in US dollars.

## Server setup

::::steps
### Install dependencies

:::code-group
```bash [npm]
$ npm install mppx stripe @buildonspark/lightning-mpp-sdk viem
```

```bash [pnpm]
$ pnpm add mppx stripe @buildonspark/lightning-mpp-sdk viem
```

```bash [bun]
$ bun add mppx stripe @buildonspark/lightning-mpp-sdk viem
```
:::

### Configure payment methods

Register all three methods in a single `Mppx.create` call. Each method has its own configuration—Tempo needs a recipient address and currency, Stripe needs API credentials, and Lightning needs a wallet mnemonic.

```ts [server.ts]
import Stripe from 'stripe'
import { Mppx, stripe, tempo } from 'mppx/server'
import { spark } from '@buildonspark/lightning-mpp-sdk/server'

const stripeClient = new Stripe(process.env.STRIPE_SECRET_KEY!)

const mppx = Mppx.create({
  methods: [
    tempo.charge({
      currency: '0x20c0000000000000000000000000000000000000', // pathUSD on Tempo
      recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
      testnet: true,
    }),
    stripe.spt({
      client: stripeClient,
      currency: 'usd',
      decimals: 2,
      networkId: 'internal',
      paymentMethodTypes: ['card'],
    }),
    spark.charge({
      mnemonic: process.env.MNEMONIC!,
    }),
  ],
  secretKey: process.env.MPP_SECRET_KEY || crypto.randomBytes(32).toString('base64'),
})
```

### Create a payment-gated route

Compose the methods explicitly so each offer receives an equivalent price in its native unit. Resolve the Lightning amount from a trusted BTC/USD price feed, then pass the dollar amount to Tempo and Stripe and the converted satoshi amount to Lightning.

```ts [server.ts]
import crypto from 'crypto'
import Stripe from 'stripe'
import { Mppx, stripe, tempo } from 'mppx/server'
import { spark } from '@buildonspark/lightning-mpp-sdk/server'

declare function quoteUsdInSats(usdAmount: string): Promise<string>

const stripeClient = new Stripe(process.env.STRIPE_SECRET_KEY!)

const mppx = Mppx.create({
  methods: [
    tempo.charge({
      currency: '0x20c0000000000000000000000000000000000000', // pathUSD on Tempo
      recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
      testnet: true,
    }),
    stripe.spt({
      client: stripeClient,
      currency: 'usd',
      decimals: 2,
      networkId: 'internal',
      paymentMethodTypes: ['card'],
    }),
    spark.charge({
      mnemonic: process.env.MNEMONIC!,
    }),
  ],
  secretKey: process.env.MPP_SECRET_KEY || crypto.randomBytes(32).toString('base64'),
})

// [!code focus:start]
const description = 'Premium API access'
const usdAmount = '0.01'
const lightningSats = await quoteUsdInSats(usdAmount)
const charge = mppx.compose(
  ['lightning/charge', { amount: lightningSats, description }],
  ['stripe/charge', { amount: usdAmount, description }],
  ['tempo/charge', { amount: usdAmount, description }],
)

Bun.serve({
  async fetch(request) {
    const result = await charge(request)

    if (result.status === 402) return result.challenge

    return result.withReceipt(Response.json({ message: 'Paid content' }))
  },
})
// [!code focus:end]
```

### Test via the `mppx` CLI

The `mppx` CLI uses Tempo by default. Each payment method has its own client SDK—see the individual method docs for client setup.

```bash [terminal]
# Validate the paid route
$ npx mppx validate http://localhost:3000

# Create account funded with testnet tokens
$ npx mppx account create

# Make a paid request (pays with Tempo)
$ npx mppx http://localhost:3000
```
::::

## Framework examples

The `Mppx.create` configuration is the same across frameworks—only the route handler syntax changes. These examples resolve the Lightning quote at startup for brevity; refresh it before it becomes stale according to your pricing policy.

### Hono

```ts [server.ts]
import crypto from 'crypto'
import { Hono } from 'hono'
import Stripe from 'stripe'
import { Mppx, stripe, tempo } from 'mppx/hono'
import { spark } from '@buildonspark/lightning-mpp-sdk/server'

declare function quoteUsdInSats(usdAmount: string): Promise<string>

const app = new Hono()
const stripeClient = new Stripe(process.env.STRIPE_SECRET_KEY!)

const mppx = Mppx.create({
  methods: [
    tempo.charge({
      currency: '0x20c0000000000000000000000000000000000000', // pathUSD on Tempo
      recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
      testnet: true,
    }),
    stripe.spt({
      client: stripeClient,
      currency: 'usd',
      decimals: 2,
      networkId: 'internal',
      paymentMethodTypes: ['card'],
    }),
    spark.charge({
      mnemonic: process.env.MNEMONIC!,
    }),
  ],
  secretKey: process.env.MPP_SECRET_KEY || crypto.randomBytes(32).toString('base64'),
})

const description = 'Premium API access'
const usdAmount = '0.01'
const lightningSats = await quoteUsdInSats(usdAmount)
const charge = mppx.compose(
  ['lightning/charge', { amount: lightningSats, description }],
  ['stripe/charge', { amount: usdAmount, description }],
  ['tempo/charge', { amount: usdAmount, description }],
)

app.get(
  '/api/resource',
  charge,
  async (c) => c.json({ message: 'Paid content' }),
)
```

### Express

```ts [server.ts]
import crypto from 'crypto'
import express from 'express'
import Stripe from 'stripe'
import { Mppx, stripe, tempo } from 'mppx/express'
import { spark } from '@buildonspark/lightning-mpp-sdk/server'

declare function quoteUsdInSats(usdAmount: string): Promise<string>

const app = express()
const stripeClient = new Stripe(process.env.STRIPE_SECRET_KEY!)

const mppx = Mppx.create({
  methods: [
    tempo.charge({
      currency: '0x20c0000000000000000000000000000000000000', // pathUSD on Tempo
      recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
      testnet: true,
    }),
    stripe.spt({
      client: stripeClient,
      currency: 'usd',
      decimals: 2,
      networkId: 'internal',
      paymentMethodTypes: ['card'],
    }),
    spark.charge({
      mnemonic: process.env.MNEMONIC!,
    }),
  ],
  secretKey: process.env.MPP_SECRET_KEY || crypto.randomBytes(32).toString('base64'),
})

const description = 'Premium API access'
const usdAmount = '0.01'
const lightningSats = await quoteUsdInSats(usdAmount)
const charge = mppx.compose(
  ['lightning/charge', { amount: lightningSats, description }],
  ['stripe/charge', { amount: usdAmount, description }],
  ['tempo/charge', { amount: usdAmount, description }],
)

app.get(
  '/api/resource',
  charge,
  async (req, res) => res.json({ message: 'Paid content' }),
)
```

### Next.js

```ts [app/api/resource/route.ts]
import crypto from 'crypto'
import Stripe from 'stripe'
import { Mppx, stripe, tempo } from 'mppx/nextjs'
import { spark } from '@buildonspark/lightning-mpp-sdk/server'

declare function quoteUsdInSats(usdAmount: string): Promise<string>

const stripeClient = new Stripe(process.env.STRIPE_SECRET_KEY!)

const mppx = Mppx.create({
  methods: [
    tempo.charge({
      currency: '0x20c0000000000000000000000000000000000000', // pathUSD on Tempo
      recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
      testnet: true,
    }),
    stripe.spt({
      client: stripeClient,
      currency: 'usd',
      decimals: 2,
      networkId: 'internal',
      paymentMethodTypes: ['card'],
    }),
    spark.charge({
      mnemonic: process.env.MNEMONIC!,
    }),
  ],
  secretKey: process.env.MPP_SECRET_KEY || crypto.randomBytes(32).toString('base64'),
})

const description = 'Premium API access'
const usdAmount = '0.01'
const lightningSats = await quoteUsdInSats(usdAmount)
const charge = mppx.compose(
  ['lightning/charge', { amount: lightningSats, description }],
  ['stripe/charge', { amount: usdAmount, description }],
  ['tempo/charge', { amount: usdAmount, description }],
)

export const GET = charge(
  async () => Response.json({ message: 'Paid content' }),
)
```

## Method-specific configuration

Each payment method has its own parameters. Refer to the individual method docs for the full configuration reference:

## Client preferences

Clients can declare which payment methods they prefer by passing `paymentPreferences` to `Mppx.create`. This sends an `Accept-Payment` header on every request, and the server uses it to filter Challenges down to the methods the client supports.

### Accounts SDK

```ts twoslash
import { Provider } from 'accounts'
import { Mppx, stripe, tempo } from 'mppx/client'

const provider = Provider.create({ mpp: false }) // Avoid double 402 handling; mppx is configured below.
await provider.request({ method: 'wallet_connect' })

Mppx.create({
  methods: [
    tempo({
      account: provider.getAccount({ signable: true }),
      getClient: provider.getClient,
    }),
    stripe.charge({
      createToken: async (opts) => {
        const res = await fetch('/api/create-spt', {
          body: JSON.stringify(opts),
          headers: { 'Content-Type': 'application/json' },
          method: 'POST',
        })
        const { spt } = await res.json() as { spt: string }
        return spt
      },
    }),
  ],
  // [!code hl:start]
  paymentPreferences: ({ tempo, stripe }) => ({
    [tempo.charge]: 1,
    [stripe.charge]: 0.5,
    [tempo.session]: 0.2,
  }),
  // [!code hl:end]
})
```

### viem

```ts twoslash
import { Mppx, stripe, tempo } from 'mppx/client'
import { privateKeyToAccount } from 'viem/accounts'

const account = privateKeyToAccount('0x...')

Mppx.create({
  methods: [
    tempo({ account }),
    stripe.charge({
      createToken: async (opts) => {
        const res = await fetch('/api/create-spt', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(opts),
        })
        const { spt } = await res.json() as { spt: string }
        return spt
      },
    }),
  ],
  // [!code hl:start]
  paymentPreferences: ({ tempo, stripe }) => ({
    [tempo.charge]: 1,
    [stripe.charge]: 0.5,
    [tempo.session]: 0.2,
  }),
  // [!code hl:end]
})
```

See the [`paymentPreferences` parameter reference](/sdk/typescript/client/Mppx.create#paymentpreferences-optional) for the full configuration options.

## Next steps
