# 決済を受け付けます MCP アプリで決済を安全に受け付けます。 # リダイレクト > This is a リダイレクト for when platform is web and ui is stripe-hosted. View the full page at https://docs.stripe.com/agentic-commerce/apps/accept-payment?platform=web&ui=stripe-hosted. Collect payments outside your app with a prebuilt Stripe-hosted Checkout page. This guide shows how to: - 商品表示および顧客による購入商品の選択を可能にするための Model Context Protocol (MCP) ツールを定義します。 - Stripe ホスト型の構築済みの[決済画面](https://docs.stripe.com/payments/checkout.md)で決済の詳細を収集する - 決済成功後の Webhook の監視 ## Stripe をセットアップする Add the Stripe API library to your back end. #### Ruby ```bash # Available as a gem sudo gem install stripe ``` ```ruby # If you use bundler, you can add this line to your Gemfile gem 'stripe' ``` ## 商品および価格の設定 This example displays a group of products in the MCP app. [Create products and prices in the Dashboard or with the Stripe CLI](https://docs.stripe.com/products-prices/manage-prices.md). ## Checkout MCP ツールを登録する *価格* (Prices define how much and how often to charge for products. This includes how much the product costs, what currency to use, and the interval if the price is for subscriptions) のセットに対して [Checkout Session](https://docs.stripe.com/api/checkout/sessions.md) を作成する MCP ツールを登録します。このツールは後のステップで MCP アプリから呼び出します。 ```javascript import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server"; import { readFileSync } from "node:fs"; import Stripe from "stripe"; import { z } from "zod"; // Follow https://docs.stripe.com/keys-best-practices to protect your Stripe API keys. const stripe = new Stripe(process.env.STRIPE_API_KEY); const server = new McpServer({ name: "my-mcp-server", version: "1.0.0" }); const resourceUri = "ui://list-products.html"; 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; } // Register the tool that creates a checkout 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, }, }; } ); ``` ## UI ツールとリソースの登録 MCP ツールとリソースを登録して、MCP アプリの UI を設定します。この UI は以下の機能を提供します。 1. 商品のリストを表示する 1. 購入する商品を顧客が選択できるようにする 1. Stripe Checkout にリダイレクトして決済を完了する ### Register a product list MCP tool Create a product list MCP tool. Its callback returns the price IDs for the products to display in the UI. ```javascript registerAppTool( server, "list-products", { title: "List products", description: "List the products available for purchase", _meta: { ui: { resourceUri } }, }, async () => { const suggestedProducts = [ // The price IDs from the earlier step { priceId: "{{PRICE_ID}}", name: "Test product 1" }, { priceId: "{{PRICE_ID}}", name: "Test product 2" }, ]; return { structuredContent: { products: suggestedProducts }, content: [], }; } ); ``` ### Register a product list UI resource 商品リストウィジェットの MCP リソースを作成します。商品を表示する UI コードを定義します。 ```javascript // Register the resource that serves the bundled HTML registerAppResource( server, "list-products-widget", resourceUri, { mimeType: RESOURCE_MIME_TYPE }, async () => { const html = readFileSync("dist/ui/list-products.html", "utf8"); return { contents: [ { uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html, }, ], }; } ); ``` This example uses minimal markup. In a production app, use a framework such as React. See the [MCP Apps documentation](https://modelcontextprotocol.github.io/ext-apps/) for additional examples. ```html
``` ```js import { App } from "@modelcontextprotocol/ext-apps"; const app = new App({ name: "ProductList", version: "1.0.0" }); // Establish communication with the host await app.connect(); /** * UI markup and event handlers */ const renderProduct = (product) => { return ` `; }; const handleSubmit = async (event) => { // We'll fill this in next } const renderApp = (products) => { const root = document.querySelector("#root"); root.innerHTML = `

Select products to purchase

${products.map(renderProduct).join("")}
`; document .querySelector("#product-form") ?.addEventListener("submit", handleSubmit); }; /** * Render the list of products from the tool's structuredContent */ app.ontoolresult = (params) => { const { products } = params.structuredContent ?? {}; if (products) { renderApp(products); } }; ``` ## Checkout へのリダイレクト 顧客が MCP アプリで **購入** をクリックすると、次のようになります。 1. Call your “buy products” tool to create a Checkout Session URL. 1. Checkout Session URL を開きます。 `handleSubmit` 関数を更新します。 ```javascript const handleSubmit = async (event) => { event.preventDefault(); const formData = new FormData(event.target); const priceIds = Array.from(formData.values()); // Call the buy-products tool to create a Checkout Session const { structuredContent } = await app.callServerTool({ name: "buy-products", arguments: { priceIds }, }); if (typeof structuredContent?.checkoutSessionUrl === "string") { await app.openLink({ url: structuredContent.checkoutSessionUrl }); } }; ``` ## 成功した注文を処理する Handle successful orders by listening to `checkout.session.completed` (and `checkout.session.async_payment_succeeded` for delayed methods) and calling an idempotent fulfillment function that retrieves the Checkout Session (expand `line_items`), verifies payment_status, and fulfills the items. `success_url` ランディングページを使用してリダイレクトの直後にフルフィルメントを開始しますが、Webhook を使用してすべての決済がフルフィルメントされるようにします。 Test locally with the Stripe CLI, then deploy your webhook endpoint. For step-by-step instructions, see [Fulfill payments received with the Checkout Sessions API](https://docs.stripe.com/checkout/fulfillment.md?payment-ui=stripe-hosted). ## See also - [税金の回収](https://docs.stripe.com/payments/checkout/taxes.md) - [配送先情報やその他の顧客情報を収集する](https://docs.stripe.com/payments/checkout/collect-additional-info.md) - [ブランディングをカスタマイズする](https://docs.stripe.com/payments/checkout/customization.md) - [完了ページをカスタマイズする](https://docs.stripe.com/payments/checkout/custom-success-page.md) # 即時決済 > This is a 即時決済 for when platform is web and ui is direct-api. View the full page at https://docs.stripe.com/agentic-commerce/apps/accept-payment?platform=web&ui=direct-api. > - ChatGPT アプリ内での決済の受け付けは、アメリカで OpenAI によって承認された企業が利用できます。 - ChatGPT 即時決済でサポートされている決済手段は、カード、Apple Pay、Google Pay、および [Link](https://docs.stripe.com/payments/link.md) です。 This guide shows how to accept a payment within your ChatGPT app using Instant Checkout and Stripe. For details about this framework, see the [ChatGPT Instant Checkout documentation](https://openai.com/index/buy-it-in-chatgpt/). ## 取引の図 ChatGPT 即時決済フローの図 (See full diagram at https://docs.stripe.com/agentic-commerce/apps/accept-payment) ## Stripe のプロフィールを作成し、ChatGPT アプリを連結させます Stripe プロフィールを作成し、OpenAI による Stripe アカウントへの接続を承認します。これにより ChatGPT で、顧客の支払い詳細を示す [Shared Payment Token (SPT)](https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens.md) を安全に提供できるようになります。 1. Stripe ダッシュボードで [Stripe プロフィール](https://docs.stripe.com/get-started/account/profile.md)を作成します。 1. Stripe の代理販売者規約に同意し、 **OpenAI を有効にするためにオーソリ** をクリックし、プロフィールに接続してください。 1. Copy your Network ID. You need this ID to build checkout requests in the ChatGPT app. ## Stripe をセットアップする Add the Stripe API library to your back end: #### Ruby ```bash # Available as a gem sudo gem install stripe ``` ```ruby # If you use bundler, you can add this line to your Gemfile gem 'stripe' ``` ## Stripe で購入ウィジェットを構築する Set up the UI for your ChatGPT app by creating a “buy product” MCP tool and UI resource. This flow: - チャットのコンテキストから商品を抽出します。 - 商品情報を表示し、顧客の配送先住所を収集します。 - 顧客がプロセスを進める準備ができたら、`window.openai.requestCheckout` を呼び出します。 ### 商品および価格の設定 This example shows a checkout flow for a product in the ChatGPT app. [Create products and prices in the Dashboard or with the Stripe CLI](https://docs.stripe.com/products-prices/manage-prices.md). > 自社専用のカスタム商品ロジックがある場合は、Stripe 用に商品を設定する必要はありません。代わりに、以降のセクションの Stripe Product API コールを自社の商品ロジックに置き換えてください。 ### Register a “buy product” resource and tool in your MCP server 顧客が特定の商品を購入するためのチャットを表示したときに、ChatGPT が決済ウィジェットをレンダリングするように設定します。 ```javascript import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE, } from "@modelcontextprotocol/ext-apps/server"; import { readFileSync } from "node:fs"; import Stripe from "stripe"; import { z } from "zod"; // Follow https://docs.stripe.com/keys-best-practices to protect your Stripe API keys. const stripe = new Stripe(process.env.STRIPE_API_KEY); const server = new McpServer({ name: "my-mcp-server", version: "1.0.0" }); const resourceUri = "ui://widget/buy-product-template.html"; registerAppResource( server, "buy-product-widget", resourceUri, { title: "Buy Product", description: "Buy Product widget", mimeType: RESOURCE_MIME_TYPE, }, async () => { const html = readFileSync("dist/ui/buy-product.html", "utf8"); return { contents: [ { uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html, }, ], }; } ); registerAppTool( server, "show-buy-product-widget", { title: "Buy Product", description: "Start a checkout flow for a specific product.", // Add inputs here to help you find the product id inputSchema: { product_name: z.string() }, _meta: { ui: { resourceUri } }, annotations: { readOnlyHint: true } }, async () => { // Add logic here to get product id from input schema const product = await stripe.products.retrieve('{{PRODUCT_ID}}'); const amount = (await stripe.prices.retrieve(product.default_price as string)).unit_amount; return { content: [], structuredContent: { productName: product.name, amount: amount, priceID: product.default_price, }, }; } ); ``` ### Create the “buy product” UI with the Apps SDK This UI appears when the tool from the previous step runs. The following example uses minimal markup. In a production app, use a framework such as React. See the [ChatGPT Apps SDK documentation](https://developers.openai.com/apps-sdk) for more examples. ```html
``` ```js import { App } from "@modelcontextprotocol/ext-apps"; const app = new App({ name: "BuyProduct", version: "1.0.0" }); await app.connect(); if (!window.openai?.requestCheckout) { throw new Error("requestCheckout is not available in this host"); } const root = document.getElementById("root"); let product = { name: "", amount: 0, priceID: "" }; const render = () => { root.innerHTML = `

${product.name || "Loading..."}

$${(product.amount / 100).toFixed(2)}

`; }; app.ontoolresult = (params) => { const { productName, amount, priceID } = params.structuredContent ?? {}; product = { name: productName ?? "", amount: amount ?? 0, priceID: priceID ?? "" }; render(); }; render(); ``` ### Stripe で配送先住所を収集して税金を回収する Stripe Tax API を使用して、次のステップで使用する税金を計算できます。詳細については、[スタンドアロン Tax API の使用](https://docs.stripe.com/tax/standalone-tax-api.md)を参照してください。 ### ChatGPT 即時決済モダールを開く これにより、決済手段を選択するよう促すメッセージが顧客に表示されます。前のステップの価格 ID に対応するよう、決済セッションを作成するロジックを追加します。以下のコードスニペットは価格 ID に UUID を付け加えて Checkout Session ID を作成します。 ```javascript const getTax = (priceID) => { // Add your tax integration }; const createCheckoutSession = (priceID) => { const uuid = crypto.randomUUID(); return `${priceID}::${uuid}`; }; const handleSubmit = (e) => { e.preventDefault(); const { name, amount, priceID } = product; const tax = getTax(priceID); window.openai.requestCheckout({// This is priceID passed in from the MCP buy product tool id: createCheckoutSession(priceID), // remove this when you are ready for live mode payment_mode: "test", payment_provider: { provider: "stripe",// Insert your Network ID from the Stripe dashboard merchant_id: networkID, supported_payment_methods: ["card"], }, status: "ready_for_payment", currency: "USD", line_items: [ { id: "line_items_123", item: { id: priceID, quantity: 1, }, base_amount: product.amount, subtotal: product.amount, tax: tax, total: product.amount + tax, }, ], totals: [ { type: "items_base_amount", display_text: product.name, amount: product.amount, }, { type: "subtotal", display_text: "Subtotal", amount: product.amount, }, { type: "tax", display_text: "Tax", amount: tax, }, { type: "total", display_text: "Total", amount: product.amount + tax, }, ], fulfillment_options: [], fulfillment_address: null, messages: [], links: [ { type: "terms_of_service", url: "https://example.com/terms", }, ], }); } ``` ## 決済を完了するための MCP ツールを登録する When the customer selects a payment method in the ChatGPT payment UI and clicks **Pay**, ChatGPT calls your `complete_checkout` tool and returns the SPT that you use to create a `PaymentIntent`. 入力として共有決済認証済みトークンを受け取り、[Payment Intents API](https://docs.stripe.com/api/payment_intents.md) に渡して処理する `complete_checkout` MCP ツールを登録します。 ```javascript const retrievePriceID = (checkout_session_id: string) => { const [priceID, uuid] = checkout_session_id.split('::'); return priceID; }; server.registerTool( "complete_checkout", { description: "Complete the checkout and process the payment", inputSchema: { checkout_session_id: z.string(), buyer: z .object({ name: z.string().nullable(), email: z.string().nullable(), phone_number: z.string().nullable(), }) .nullable(), payment_data: z.object({ token: z.string(), provider: z.string(), billing_address: z .object({ name: z.string(), line_one: z.string(), line_two: z.string().nullable(), city: z.string(), state: z.string(), country: z.string(), postal_code: z.string(), phone_number: z.string().nullable(), }) .nullable(), }), }, }, async ({checkout_session_id, buyer, payment_data}) => { const price = (await stripe.prices.retrieve(retrievePriceID(checkout_session_id as string))) // Add your tax logic const tax = getTax() // confirms the SPT stripe.paymentIntents.create({ amount: price.unit_amount + tax, currency: price.currency, shared_payment_granted_token: payment_data.token, confirm: true, }); return { content: [], structuredContent: { id: checkout_session_id, status: "completed", currency: price.currency, buyer, line_items: [], order: { id: "{{ORDER_ID}}", checkout_session_id, permalink_url: "", }, }, }; } ); ``` ## テスト 実際のお金を動かさずにアプリをテストするには、Stripe のテスト環境で ChatGPT の決済テストモードを使用します。 1. Stripe ダッシュボードで[サンドボックス](https://docs.stripe.com/sandboxes.md)に移動します。 1. テスト用 Stripe プロフィールを作成し、テスト環境内で ChatGPT に連結させ、テスト用のネットワーク ID をコピーします。 1. テストカードを想定してテスト用 SPT を生成するために、決済テスト環境を使用するように ChatGPT アプリの設定を更新してください。 1. 決済をリクエストする際は、テスト用プロファイル ID を指定し、`payment_mode` を `test` に設定してください。これにより、ChatGPT はテストカードを想定し、テスト用 SPT を生成します。 ```javascript window.openai.requestCheckout({ id: priceID,payment_mode: "test", payment_provider: { provider: "stripe",merchant_id: "profile_test", supported_payment_methods: ["card"], }, ``` 1. ChatGPT からのテスト用 SPT を処理するために、MCP ツールの実装では Stripe のテスト用 API キーを使用してください。キーを安全に使用するために[ベストプラクティス](https://docs.stripe.com/keys-best-practices.md)に従ってください。キーをコードに直接埋め込むことは避け、利用しているプラットフォームからキーが提供される場合にはシークレットマネージャーを使用してください。 1. 本番環境とテスト環境で同一の Webhook 構成を設定し、テスト用 Webhook ハンドラーが本番システムに影響を与えないことを確認してください。例えば、本番用の Webhook が出荷をトリガーした場合、本番環境ではテストエンドポイントは出荷されたことだけをログに記録します。 これらのステップを完了したら、実際の資金を動かさずにアプリ内の決済フローを評価してください。 ### アプリを本番環境に公開する アプリを本番環境に昇格する準備ができたとき: 1. **Payment Intents: Write** 権限を持つ本番環境用の[制限付き API キー](https://docs.stripe.com/keys-best-practices.md#limit-access) (`rk_live_...`) を作成してください。制限付きキーを使用することで、MCP ツールに必要な権限のみを正確に付与できます。 1. 本番環境用の制限付き API キーを使用できるように MCP ツールを更新してください。 1. アプリの決済リクエストを本番プロフィール ID で更新し、テスト `payment_mode` オプションを削除してください。 Your app is then ready to handle live payments. You must complete this step before submitting for ChatGPT app review. > ChatGPT アプリを提出後は、本番の顧客に公開されているため、テスト決済モードを使用しないでください。 ## See also - [Model Context Protocol](https://modelcontextprotocol.io/) - [ChatGPT Apps SDK](https://developers.openai.com/apps-sdk)