Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 234 additions & 0 deletions src/pages/guides/accept-card-payments.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
---
description: "Accept card payments via Stripe on your MPP-enabled API. Charge Visa, Mastercard, and other card networks—no stablecoin wallet required."
imageDescription: "Accept card payments on your API via Stripe"
---

import { Cards } from 'vocs'
import { OneTimePaymentsCard, PaymentMethodsCard, StripeMethodCard } from '../../components/cards'
import { PromptBlock } from '../../components/PromptBlock'
import { MermaidDiagram } from '../../components/MermaidDiagram'

# Accept card payments [Charge Visa and Mastercard via Stripe]

Accept card payments on your MPP-enabled API using [Stripe](/payment-methods/stripe). Clients pay with Visa, Mastercard, and other card networks—no stablecoin wallet required. The server uses Stripe's [Shared Payment Tokens (SPTs)](https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens) to process payments through Stripe's existing rails.

::::info[Stripe account setup]
Machine payments must be enabled on your Stripe account before you can accept MPP payments. [Request access](https://docs.stripe.com/payments/machine#sign-up) through the Stripe Dashboard.
::::

## How it works

<MermaidDiagram chart={`sequenceDiagram
participant Client
participant Server
participant Stripe
Client->>Server: (1) GET /resource
Server-->>Client: (2) 402 + Challenge (Stripe method)
Client->>Stripe: (3) Create SPT from Challenge
Stripe-->>Client: (4) spt_...
Client->>Server: (5) GET /resource + Credential (SPT)
Server->>Stripe: (6) Create PaymentIntent
Stripe-->>Server: (7) pi_...
Server-->>Client: (8) 200 OK + Receipt
`} />

1. **Client** requests a paid resource.
2. **Server** responds with `402` and a Challenge containing the price, currency, and Stripe method details.
3. **Client** creates a Shared Payment Token (SPT) through the Stripe API.
4. **Client** retries the request with a Credential containing the SPT.
5. **Server** creates a Stripe `PaymentIntent` using the SPT, verifies payment, and returns the resource with a Receipt.

Settlement, refunds, and reporting all happen through your Stripe Dashboard—the same tools you use for any other Stripe payment.

## Prompt mode

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

<PromptBlock>
{`Use https://mpp.dev/guides/accept-card-payments.md as reference.
Add mppx to my app with a payment-gated endpoint that accepts
card payments via Stripe. Charge $1.00 per request using the
Stripe payment method. When payment is verified, return a JSON response.`}
</PromptBlock>

## Server setup

::::steps

#### Install dependencies

:::code-group
```bash [npm]
npm install mppx stripe viem
```
```bash [pnpm]
pnpm add mppx stripe viem
```
```bash [bun]
bun add mppx stripe viem
```
:::

#### Set environment variables

Set your Stripe secret key and [Business Network](https://docs.stripe.com/get-started/account/profile) profile ID.

```bash [.env]
STRIPE_SECRET_KEY=sk_test_...
STRIPE_NETWORK_ID=your_network_id
```

#### Create the server with Stripe payments

Set up an `Mppx` instance with the `stripe.charge` method. The `networkId` is your Stripe Business Network profile ID, and `paymentMethodTypes` controls which card types you accept.

```ts twoslash
import Stripe from 'stripe'
import { Mppx, stripe } from 'mppx/server'

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

const mppx = Mppx.create({
methods: [
stripe.charge({
client: stripeClient,
networkId: process.env.STRIPE_NETWORK_ID!,
paymentMethodTypes: ['card'],
}),
],
})
```

#### Add a paid endpoint

Use `mppx.charge` to gate your endpoint. Set the `amount` in the smallest currency unit (cents for USD), and specify `currency` and `decimals`.

```ts twoslash
import Stripe from 'stripe'
import { Mppx, stripe } from 'mppx/server'

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

const mppx = Mppx.create({
methods: [
stripe.charge({
client: stripeClient,
networkId: process.env.STRIPE_NETWORK_ID!,
paymentMethodTypes: ['card'],
}),
],
})

// ---cut---
export async function handler(request: Request) {
const result = await mppx.charge({
amount: '100',
currency: 'usd',
decimals: 2,
description: 'Premium API access',
})(request)

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

return result.withReceipt(Response.json({ data: 'your response here' }))
}
```

::::

## Client setup

Use `stripe` with `Mppx.create` to handle `402` responses automatically. The client parses the Challenge, creates an SPT through the `createToken` callback, and retries with the Credential.

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

Mppx.create({
methods: [
stripe({
createToken: async (params) => {
const res = await fetch('/api/create-spt', {
body: JSON.stringify(params),
headers: { 'Content-Type': 'application/json' },
method: 'POST',
})
if (!res.ok) throw new Error('Failed to create SPT')
return (await res.json()).spt
},
paymentMethod: 'pm_card_visa',
}),
],
})

const response = await fetch('https://api.example.com/resource')
// @log: Response { status: 200, ... }
```

:::warning[Security: server-side authorization]
The `createToken` callback proxies through your own server because SPT creation requires a Stripe secret key. The server **must** derive SPT parameters (amount, currency, expiry) itself—never accept them from the client. See the [SPT creation proxy endpoint](/payment-methods/stripe/charge#spt-creation-proxy-endpoint) for a secure implementation.
:::

## Accept cards and stablecoins together

Stripe works alongside other payment methods. Add `tempo` to accept both cards and stablecoins on the same endpoint—clients pay with whichever rail they support.

```ts twoslash
import Stripe from 'stripe'
import { Mppx, stripe, tempo } from 'mppx/server'

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

const mppx = Mppx.create({
methods: [
stripe.charge({
client: stripeClient,
networkId: process.env.STRIPE_NETWORK_ID!,
paymentMethodTypes: ['card'],
}),
tempo.charge({
currency: '0x20c0000000000000000000000000000000000000',
recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
testnet: true,
}),
],
})
```

The server returns both methods in the `402` Challenge. See [Accept multiple payment methods](/guides/multiple-payment-methods) for a full walkthrough.

## Add a browser payment page

Set `html` on the Stripe method to render a Stripe Elements card form when a browser visits the endpoint. Programmatic clients with `Authorization` headers are unaffected.

```ts twoslash
import Stripe from 'stripe'
import { Mppx, stripe } from 'mppx/server'

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

const mppx = Mppx.create({
methods: [
stripe.charge({
client: stripeClient,
// [!code hl:start]
html: {
createTokenUrl: '/api/create-spt',
publishableKey: process.env.STRIPE_PUBLISHABLE_KEY!,
},
// [!code hl:end]
networkId: process.env.STRIPE_NETWORK_ID!,
paymentMethodTypes: ['card'],
}),
],
})
```

See [Create a payment link](/guides/payment-links) for a full walkthrough and live demo.

## Next steps

<Cards>
<StripeMethodCard />
<OneTimePaymentsCard />
<PaymentMethodsCard />
</Cards>
4 changes: 4 additions & 0 deletions vocs.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,10 @@ export default defineConfig({
{
text: "Advanced",
items: [
{
text: "Accept card payments",
link: "/guides/accept-card-payments",
},
{
text: "Accept split payments",
link: "/guides/split-payments",
Expand Down
Loading