Accept a payment
Securely accept payments with your ChatGPT app.
You can collect payments outside your app with a prebuilt Stripe-hosted Checkout page. This guide shows how to:
- Define Model Context Protocol (MCP) tools to display products and let customers select items to buy
- Collect payment details with a prebuilt Stripe-hosted Checkout page
- Monitor webhooks after a successful payment
Set up Stripe
To set up Stripe, add the Stripe API library to your back end.
Create products and prices
In this example, you can display a group of products in the ChatGPT app. Learn how to create products and prices in the Dashboard or with the Stripe CLI.
Register a Checkout MCP tool
Register an MCP tool that creates a Checkout Session for a set of Prices. You call this tool from the ChatGPT app in a later step.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { readFileSync } from "node:fs"; import Stripe from "stripe"; import { z } from "zod"; const stripe = new Stripe() const server = new McpServer({ name: "my-mcp-server", version: "1.0.0" }); async function createCheckoutSession(priceIds) { const lineItems = priceIds.map((price) => ({ price, quantity: 1 })); const session = await stripe.checkout.sessions.create({ mode: "payment", line_items: lineItems, success_url: "https://example.com/checkout/success", }); return session; } server.registerTool( "buy-products", { title: "Buy products", description: "Create a checkout page link for purchasing the selected products", inputSchema: { priceIds: z.array(z.string()) }, }, async ({ priceIds }) => { const session = await createCheckoutSession(priceIds); return { content: [ { type: "text", text: `[Complete your purchase here](${session.url})`, }, ], structuredContent: { checkoutSessionId: session.id, checkoutSessionUrl: session.url, }, }; } );"sk_test_BQokikJOvBiI2HlWgH4olfQ2"
Register a ChatGPT UI tool and resource
Set up the UI for your ChatGPT app by registering an MCP tool and resource. This UI:
- Displays a list of products
- Lets the customer select products to buy
- Redirects to Stripe Checkout to complete payment
Register a list products MCP tool
Create a list products MCP tool. Its callback returns the price IDs for the products to display in the UI.
const listProductsUri = "ui://list-products-v1.html"; server.registerTool( "list-products", { title: "List products", description: "List the products available for purchase", _meta: { "openai/outputTemplate": listProductsUri }, }, async () => { const suggestedProducts = [ // The price IDs from the earlier step { priceId:, name: "Test product 1" }, { priceId:"{{PRICE_ID}}", name: "Test product 2" }, ]; return { structuredContent: { products: suggestedProducts }, content: [], }; } );"{{PRICE_ID}}"
Register a list products UI resource
Create an MCP resource for the product list widget. It defines the UI code that displays the products.
const listProductsHTML = readFileSync("ui/list-products.html", "utf8"); server.registerResource( "list-products-widget", listProductsUri, {}, async (uri) => ({ contents: [ { uri: uri.href, mimeType: "text/html+skybridge", text: listProductsHTML, _meta: { "openai/widgetCSP": { connect_domains: ["https://checkout.stripe.com"], resource_domains: ["https://checkout.stripe.com"], }, }, }, ], }) );
This example uses minimal markup. In a production app, you can use a framework such as React. See the ChatGPT Apps SDK documentation for additional examples.
<div id="root"></div> <script> /** * UI markup and event handlers */ const renderProduct = (product) => { return ` <label> <input type="checkbox" name="cart[]" value="${product.priceId}"> ${product.name} </label> `; }; const renderApp = (products) => { const root = document.getElementById("root"); root.innerHTML = ` <h1>Select products to purchase</h1> <form onsubmit="handleSubmit(event)"> ${products.map(renderProduct).join("")} <button type="submit">Buy</button> </form> `; }; /** * Render the list of products from the tool's structuredContent */ const handleSetGlobal = (event) => { const { products } = event.detail.globals["toolOutput"] ?? []; renderApp(products); }; window.addEventListener("openai:set_globals", handleSetGlobal, { passive: true, }); </script>
Redirect to Checkout
When the customer clicks Buy in the ChatGPT app:
- Call the tool that you created that buys product to create a Checkout Session URL.
- Open the Checkout Session URL.
const handleSubmit = async (event) => { event.preventDefault(); const formData = new FormData(event.target); const priceIds = Array.from(formData.values()); const { structuredContent } = await window.openai.callTool("buy-products", { priceIds, }); window.openai.openExternal({ href: structuredContent.checkoutSessionUrl }); };
Handle successful orders
You can handle successful orders by listening to checkout. (and checkout. for delayed methods) and calling an idempotent fulfillment function that retrieves the Checkout Session (expand line_), verifies payment_status, and fulfills the items.
Use a success_ landing page to trigger fulfillment immediately after redirecting, but rely on webhooks to make sure that every payment is fulfilled.
Then, you can test locally with the Stripe CLI, and deploy your webhook endpoint.
Learn more about how to fulfill payments received with the Checkout Sessions API.