diff --git a/src/pages/guides/accept-card-payments.mdx b/src/pages/guides/accept-card-payments.mdx new file mode 100644 index 00000000..6204a7a1 --- /dev/null +++ b/src/pages/guides/accept-card-payments.mdx @@ -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 + +>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: + + +{`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.`} + + +## 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 + + + + + + diff --git a/vocs.config.ts b/vocs.config.ts index 25134578..27d80847 100644 --- a/vocs.config.ts +++ b/vocs.config.ts @@ -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",