マシンペイメント(MPPとx402、テストネット)
エージェントがチェックアウト画面を経由せず、HTTP 402の支払いリンクで、MPPまたはx402により予約の支払いを行います。
テストネット現在はテストネットのみです。受け付けるのはテストネットのトークンだけで、実際のお金は動きません。
このページは、連携を担当するエンジニア向けです。事業者の方は読む必要はありません。
マシンペイメントとは
マシンペイメントは、エージェントがチェックアウト画面を経由せず、APIやサービスの料金を直接支払う仕組みです。サーバーがHTTP 402と支払い要件を返し、エージェントが支払ったうえで支払い証明を付けて再送すると、結果と領収情報が得られます。MPP(Machine Payments Protocol)とx402はそのための2つの方式で、Stripeはどちらでもステーブルコインでの受け取りに対応しています。SOPはこれを予約に使います。支払い待ちの予約ごとに支払いリンクが付き、エージェントはMPPまたはx402で支払えます。
支払いの流れ
手順
- エージェント → SOP:支払いなしで支払いリンクにPOST
- SOP → エージェント:402とMPP・x402のチャレンジを返す
- エージェント → エージェント:ウォレットでネットワーク上で支払う
- エージェント → SOP:支払い証明を付けて再送
- SOP → SOP:支払いを検証
- SOP → エージェント:200と領収情報。予約を支払い済みに
- SOP → 事業者:支払い済みの予約を通知
支払いリンク
支払い待ちの予約のレスポンスにはpayment_linkが加わります。URL、対応プロトコル(x402、mpp)、米ドルの金額、ネットワーク、testnet: trueが含まれます。URLは/pay/{business_id}/{booking_id}?t=…の形です。含まれるコードは支払いにしか使えず、予約の閲覧、変更、キャンセルはできません。ブラウザーで開くと、AIエージェント向けの支払いリンクであることを説明するページが表示されます。
支払いなしでリクエストを送ると、サーバーは402を返し、2つのプロトコルのチャレンジを付けます。x402のPAYMENT-REQUIREDヘッダーと、MPPのWWW-Authenticate: Paymentヘッダーです。
マシン決済:402チャレンジ(x402とMPP、テストネット)
curl
# <PAYMENT_LINK> is booking.payment_link.url from an awaiting_payment booking (testnet only).
LINK="<PAYMENT_LINK>"
curl -s -D headers.txt -o /dev/null -X POST "$LINK" -H "User-Agent: my-agent/1.0"
head -n 1 headers.txt
grep -i '^www-authenticate:' headers.txt
grep -i '^payment-required:' headers.txt | cut -d' ' -f2 | tr -d '\r' | base64 -d; echo
Python
import base64
import json
import urllib.error
import urllib.request
LINK = "<PAYMENT_LINK>" # booking.payment_link.url from an awaiting_payment booking (testnet only)
req = urllib.request.Request(LINK, method="POST", headers={"User-Agent": "my-agent/1.0"})
try:
urllib.request.urlopen(req)
except urllib.error.HTTPError as err: # the 402 challenge arrives as an HTTPError
print("HTTP", err.code)
print("WWW-Authenticate:", err.headers["WWW-Authenticate"]) # MPP challenges (evm and tempo)
required = json.loads(base64.b64decode(err.headers["PAYMENT-REQUIRED"])) # x402 requirements
print(json.dumps(required, indent=2))
Node.js
const LINK = "<PAYMENT_LINK>"; // booking.payment_link.url from an awaiting_payment booking (testnet only)
(async () => {
const res = await fetch(LINK, { method: "POST", headers: { "User-Agent": "my-agent/1.0" } });
console.log("HTTP", res.status);
console.log("WWW-Authenticate:", res.headers.get("www-authenticate")); // MPP challenges (evm and tempo)
const required = JSON.parse(Buffer.from(res.headers.get("payment-required"), "base64").toString()); // x402 requirements
console.log(JSON.stringify(required, null, 2));
})();
レスポンス例
HTTP 402
WWW-Authenticate: Payment id="vRToYQ7TnWS1YWLEM_jL3lsSSJukGrQY_fqB9yVqs9g", realm="mcp.srct.tw", method="evm", intent="charge", request="…", description="… booking 88QBMCY5 (Example Studio (test data))", expires="2026-10-21T17:02:58.846Z", opaque="eyJib29raW5nX2lkIjoiODhRQk1DWTUiLCJidXNpbmVzc19pZCI6ImV4YW1wbGUtc3R1ZGlvIn0",
Payment id="dqLCoVLnoZGkwD_-4UG_pr2KORDUXFIh1-IShyvfdKA", realm="mcp.srct.tw", method="tempo", intent="charge", request="…", description="… booking 88QBMCY5 (Example Studio (test data))", expires="2026-10-21T17:02:58.846Z", opaque="eyJib29raW5nX2lkIjoiODhRQk1DWTUiLCJidXNpbmVzc19pZCI6ImV4YW1wbGUtc3R1ZGlvIn0"
{
"accepts": [
{
"amount": "18530000",
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"extra": {
"assetTransferMethod": "eip3009",
"name": "USDC",
"version": "2"
},
"maxTimeoutSeconds": 300,
"network": "eip155:84532",
"payTo": "0x3b0b371ae3cB08F272b87319684f6518b3b313a0",
"scheme": "exact"
}
],
"error": "Payment is required (… booking 88QBMCY5 (Example Studio (test data))).",
"extensions": {
"mppx": {
"info": {
"method": "POST",
"opaque": "eyJib29raW5nX2lkIjoiODhRQk1DWTUiLCJidXNpbmVzc19pZCI6ImV4YW1wbGUtc3R1ZGlvIn0"
},
"schema": {
"additionalProperties": false,
"properties": {
"_mppx_scope": {
"type": "string"
},
"digest": {
"type": "string"
},
"method": {
"type": "string"
},
"nonce": {
"type": "string"
},
"opaque": {
"type": "string"
}
},
"required": [
"method"
],
"type": "object"
}
}
},
"resource": {
"url": "https://mcp.srct.tw/pay/example-studio/88QBMCY5?t=<PAY_TOKEN>"
},
"x402Version": 2
}ローカル環境で実行済み:コネクターのローカルコピー(実際のWorker実行環境)で、テストネットのみです。request="…"は省略した長いbase64値で、PAYMENT-REQUIREDはデコードして示しています。payToはStripeサンドボックスの入金アドレスで、?t=の支払いトークンは<PAY_TOKEN>に置き換えています。
MPPで支払う
次のサンプルは、mppxのコマンドラインウォレットで、TempoテストネットでpathUSDを使って支払います。支払いが成功すると、予約は支払い済み(支払い方法はmpp_tempo)になり、事業者の確認ルールに従って確定または確認待ちとなり、事業者に通知されます。
マシン決済:mppx CLIでリンクを支払う(テストネット)
Shell
# One-time setup (testnet wallet): npx mppx@0.13.1 account create --account my-wallet
# npx mppx@0.13.1 account fund --account my-wallet
# <PAYMENT_LINK> is booking.payment_link.url from an awaiting_payment booking (testnet only).
# --currency picks pathUSD on the Tempo testnet; without it mppx may choose the Base Sepolia USDC offer instead.
npx mppx@0.13.1 "<PAYMENT_LINK>" -X POST --account my-wallet --network testnet \
--currency 0x20c0000000000000000000000000000000000000
Python
import subprocess
LINK = "<PAYMENT_LINK>" # booking.payment_link.url from an awaiting_payment booking (testnet only)
# mppx answers the 402 challenge with the testnet wallet and retries the request.
result = subprocess.run(
["npx", "mppx@0.13.1", LINK, "-X", "POST", "--account", "my-wallet", "--network", "testnet",
"--currency", "0x20c0000000000000000000000000000000000000"], # pathUSD on the Tempo testnet
capture_output=True,
text=True,
)
print(result.stdout)
print(result.stderr)
Node.js
const LINK = "<PAYMENT_LINK>"; // booking.payment_link.url from an awaiting_payment booking (testnet only)
(async () => {
const { execFileSync } = await import("node:child_process");
// mppx answers the 402 challenge with the testnet wallet and retries the request.
console.log(execFileSync("npx", ["mppx@0.13.1", LINK, "-X", "POST", "--account", "my-wallet", "--network", "testnet", "--currency", "0x20c0000000000000000000000000000000000000"], { encoding: "utf8" }));
})();
レスポンス例
{
"ok": true,
"booking": {
"booking_id": "3K3T8RED",
"status": "pending",
"status_text": "Waiting for the business to confirm",
"business_id": "example-studio",
"business_name": "Example Studio (test data)",
"service_id": "small_group",
"service_name": "Small-group strength class",
"slot": "2026-10-25T10:00",
"start": "2026-10-25T10:00:00+08:00",
"label": "10/25(日)10:00",
"quantity": 1,
"quantity_unit": "seat",
"payment": "Paid online: NT$600",
"details": [],
"payment_details": {
"mode": "online",
"status": "paid",
"amount": {
"value": 60000,
"currency": "twd",
"text": "NT$600"
},
"collected_by": "Seller Tech U.S.A. LLC through Stripe",
"paid_at": "2026-10-22T00:49:00+08:00",
"method": "mpp_tempo",
"reference": "0x60373d2fc9f6547ab9817f6bbe911689094159ea8dc072235b644dcf468a91c6"
},
"why_pending": "First booking with this business from these contact details, so the business confirms it.",
"confirm_by": "2026-10-22T00:59:00+08:00",
"hold": "Bookings that need the business's confirmation are held for 10 minutes. If the business does not confirm by then, the hold is released automatically and the customer can choose another slot.",
"online_cancel_until": "2026-10-25T08:00:00+08:00"
},
"payment": {
"status": "succeeded",
"method": "mpp_tempo",
"reference": "0x60373d2fc9f6547ab9817f6bbe911689094159ea8dc072235b644dcf468a91c6",
"amount_usd": "18.53",
"recorded_in_stripe": false
}
}ローカル環境で実行済み:コネクターのローカルコピー、Tempoテストネット、テストトークンのみのウォレットで実行しました。コネクターがオンチェーンで支払いを検証し、予約は支払い済み(payment_details.methodはmpp_tempo、referenceはトランザクションハッシュ)になります。このローカルコピーにはStripeキーがないため、recorded_in_stripeはfalseです。
ネットワークと現況
| プロトコル | ネットワークと通貨 | 現況 |
|---|---|---|
| MPP | Tempoテストネット(Moderato)、pathUSD | ローカル環境でテスト用ウォレットによる支払いに成功済み。本番URLの支払いリンクは未検証 |
| x402 | Base Sepolia、USDC | サーバー側の検証は動作。支払い成功の記録はまだなし(テスト用ウォレットにテスト用USDCがないため) |
テストネットの支払いリンクはテスト用の事業者にだけ開かれており、実在の事業者には表示されません。そのため、現在、外部の方が試せる予約はありません。
金額と返金
- 金額:予約の新台湾ドル額を為替レートで米ドルに換算し、セント単位に切り上げます。レートの出典と時刻は支払いリンクに含まれます。
- 返金:Stripeの支払い記録と対応づけられるものは、全額返金します。対応づけられないものは「手動返金が必要」(
refund: manual)とし、スケジューラーが誤って返金することはありません。
ディスカバリー文書
/openapi.jsonは、支払いできる操作にx-payment-infoを付けています。予約ごとに金額が異なるため、金額の欄はnullです。
ディスカバリー文書の取得:/openapi.json
curl
curl -sS "https://mcp.srct.tw/openapi.json" \
-H "User-Agent: my-agent/1.0"
Python
import json
import urllib.error
import urllib.request
BASE = "https://mcp.srct.tw"
def call(method, path, body=None, headers=None):
req = urllib.request.Request(
BASE + path,
data=None if body is None else json.dumps(body).encode(),
method=method,
headers={"User-Agent": "my-agent/1.0", "Content-Type": "application/json", **(headers or {})},
)
try:
with urllib.request.urlopen(req) as res:
return res.status, json.loads(res.read())
except urllib.error.HTTPError as err: # 4xx and 5xx responses still carry a JSON body
return err.code, json.loads(err.read())
def show(status, body):
print("HTTP", status)
print(json.dumps(body, ensure_ascii=False, indent=2))
show(*call("GET", "/openapi.json"))
Node.js
const BASE = "https://mcp.srct.tw";
async function call(method, path, body, headers = {}) {
const res = await fetch(BASE + path, {
method,
headers: { "User-Agent": "my-agent/1.0", "Content-Type": "application/json", ...headers },
body: body === undefined ? undefined : JSON.stringify(body),
});
return { status: res.status, body: await res.json() };
}
function show({ status, body }) {
console.log("HTTP", status);
console.log(JSON.stringify(body, null, 2));
}
(async () => {
show(await call("GET", "/openapi.json"));
})();
レスポンス例
HTTP 200
{
"openapi": "3.1.0",
"info": {
"title": "… booking payments",
"version": "1.0.0",
"description": "Pay for … bookings with x402 or MPP. Create the booking first with the MCP tool request_booking or POST /api/v1/businesses/{business_id}/bookings; use the payment_link it returns."
},
"servers": [
{
"url": "https://mcp.srct.tw"
}
],
"x-service-info": {
"categories": [
"booking",
"fitness",
"services"
],
"docs": {
"homepage": "https://mcp.srct.tw/",
"apiReference": "https://mcp.srct.tw/docs"
}
},
"paths": {
"/pay/{business_id}/{booking_id}": {
"post": {
"summary": "Pay for a booking (x402 or MPP)",
"parameters": [
{
"name": "business_id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "booking_id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "t",
"in": "query",
"required": true,
"schema": {
"type": "string"
},
"description": "pay token from the booking's payment_link"
}
],
"x-payment-info": {
"offers": [
{
"intent": "charge",
"method": "evm",
"amount": null,
"currency": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"description": "USDC on Base Sepolia (test network); also accepted as x402 exact"
},
{
"intent": "charge",
"method": "tempo",
"amount": null,
"currency": "0x20c0000000000000000000000000000000000000",
"description": "pathUSD on Tempo test network (Moderato)"
}
]
},
"responses": {
"200": {
"description": "Paid: booking and payment result"
},
"402": {
"description": "Payment Required: x402 PAYMENT-REQUIRED and MPP WWW-Authenticate challenges"
},
"409": {
"description": "Booking not payable"
},
"410": {
"description": "Payment window passed"
}
}
},
"get": {
"summary": "Pay for a booking (x402 or MPP)",
"parameters": [
{
"name": "business_id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "booking_id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "t",
"in": "query",
"required": true,
"schema": {
"type": "string"
},
"description": "pay token from the booking's payment_link"
}
],
"x-payment-info": {
"offers": [
{
"intent": "charge",
"method": "evm",
"amount": null,
"currency": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"description": "USDC on Base Sepolia (test network); also accepted as x402 exact"
},
{
"intent": "charge",
"method": "tempo",
"amount": null,
"currency": "0x20c0000000000000000000000000000000000000",
"description": "pathUSD on Tempo test network (Moderato)"
}
]
},
"responses": {
"200": {
"description": "Paid: booking and payment result"
},
"402": {
"description": "Payment Required: x402 PAYMENT-REQUIRED and MPP WWW-Authenticate challenges"
},
"409": {
"description": "Booking not payable"
},
"410": {
"description": "Payment window passed"
}
}
}
}
}
}本番サービスで実行済み(読み取りのみ)
まだないもの
- 本番ネットワーク:未開放です。どの事業者に開放するか、料金の範囲をどうするかは、まだ決まっていません。
- x402:エンドツーエンドで動くことを確認するには、テストネットでの支払い成功が1件必要です。
関連ページ
- お客さまに代わって支払う:お客さまが承認した支払い認証情報で予約の支払いを完了する
- 支払い認証情報:Shared Payment Tokenと、事業者・エージェント・プラットフォームの3つの役割
- プロトコルと標準:MCP、REST、UCP、MPP、x402とSOPの対応