payment-provider-protocol について
payment-provider-protocolは、VTEX決済コネクターのミドルウェアを構築する開発者向けスキルです。必須のPPPエンドポイントやレスポンス契約、マニフェスト、認定テストの要件を説明します。
主な機能
- 01必須の決済フロー用6エンドポイントを実装
- 02PPPのレスポンス項目とステータスコードに準拠
- 03マニフェストに対応決済方法を宣言
- 04任意の加盟店設定エンドポイントを追加
- 05通信セキュリティと応答時間の要件を確認
活用シーン
- 01PSPとVTEXをつなぐコネクターの構築
- 02PPPエンドポイントのデバッグや拡張
- 03VTEX Payment Provider Test Suiteの認定準備
- 04決済エンドポイントのレスポンス形式の検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ vtex/ skills --skill payment-provider-protocolインストール後、Claude Code で/payment-provider-protocolを入力して読み込まれたか確認します。
詳細
PPP エンドポイントの実装
このスキルを使用する場面
以下の場合にこのスキルを使用します。
- PSP を VTEX Payment Gateway と統合する新しい決済コネクターミドルウェアを構築する場合
- 9 つの PPP エンドポイントのいずれかを実装、デバッグ、または拡張する場合
- VTEX Payment Provider Test Suite の認定に向けてコネクターを準備する場合
以下の場合はこのスキルを使用しないでください。
- 冪等性と重複防止のロジック —
payment-idempotencyを使用してください - 非同期決済フローとコールバック URL —
payment-async-flowを使用してください - PCI コンプライアンスと Secure Proxy によるカード処理 —
payment-pci-securityを使用してください - POS/VTEX Sales App のカード提示型決済(
Venda Direta Credito、Venda Direta Debito、端末 Payment Apps)— エンドポイントの形式については、このスキルと併せてpayment-ppp-posを使用してください
判断ルール
- コネクターは、6 つすべての決済フローエンドポイント(Manifest、Create Payment、Cancel、Capture/Settle、Refund、Inbound Request)を実装する必要があります。
- 設定フロー(3 つのエンドポイント:Create Auth Token、Provider Auth Redirect、Get Credentials)は任意ですが、加盟店のオンボーディングには推奨されます。
- すべてのエンドポイントは、TLS 1.2 を使用し、ポート 443 の HTTPS 経由で提供する必要があります。
- コネクターは、認定テスト中は 5 秒未満、本番環境では 20 秒未満で応答する必要があります。
- プロバイダーは PCI-DSS 認定を受けているか、カード決済に Secure Proxy を使用する必要があります。
- すべての呼び出しは Gateway が開始します。ミドルウェアが Gateway を呼び出すのは、
callbackUrl(非同期通知)と Secure Proxy(カードデータの転送)を介する場合に限られます。
厳守すべき制約
制約:必須の決済フローエンドポイントをすべて実装する
コネクターは、次の 6 つすべての決済フローエンドポイントを実装する必要があります。GET /manifest、POST /payments、POST /payments/{paymentId}/cancellations、POST /payments/{paymentId}/settlements、POST /payments/{paymentId}/refunds、POST /payments/{paymentId}/inbound-request/{action}。
重要な理由 VTEX Payment Provider Test Suite は、認定中にすべてのエンドポイントを検証します。エンドポイントが不足しているとテストに失敗し、コネクターは承認されません。実行時、Gateway はすべてのエンドポイントを必要とします。キャンセルエンドポイントがないと、決済を取り消せません。
検出方法 コネクターのルーター/ハンドラーファイルに 6 つすべての決済フローパスのハンドラーが定義されていない場合は、そこで作業を止め、先に不足しているエンドポイントを追加してください。
正しい例
import { Router } from "express";
const router = Router();
// All 6 payment-flow endpoints implemented
router.get("/manifest", manifestHandler);
router.post("/payments", createPaymentHandler);
router.post("/payments/:paymentId/cancellations", cancelPaymentHandler);
router.post("/payments/:paymentId/settlements", capturePaymentHandler);
router.post("/payments/:paymentId/refunds", refundPaymentHandler);
router.post("/payments/:paymentId/inbound-request/:action", inboundRequestHandler);
export default router;
誤った例
import { Router } from "express";
const router = Router();
// Missing manifest, inbound-request, and refund endpoints
// This will fail homologation and break runtime operations
router.post("/payments", createPaymentHandler);
router.post("/payments/:paymentId/cancellations", cancelPaymentHandler);
router.post("/payments/:paymentId/settlements", capturePaymentHandler);
export default router;
制約:正しい HTTP ステータスコードとレスポンス形式を返す
各エンドポイントは、PPP API に記載された正確なレスポンス形式を返す必要があります。Create Payment は paymentId、status、authorizationId、tid、nsu、acquirer、code、message、delayToAutoSettle、delayToAutoSettleAfterAntifraud、delayToCancel を返す必要があります。Cancel は paymentId、cancellationId、code、message、requestId を返す必要があります。Capture は paymentId、settleId、value、code、message、requestId を返す必要があります。Refund は paymentId、refundId、value、code、message、requestId を返す必要があります。
重要な理由
Gateway はこれらのフィールドをプログラムで解析します。フィールドが不足しているとデシリアライズエラーが発生し、Gateway は決済を失敗として扱います。delayToAutoSettle の値が正しくないと、決済が不適切なタイミングで自動キャンセルまたは自動キャプチャされます。
検出方法 レスポンスオブジェクトに、そのエンドポイントで必須のフィールドが 1 つでも欠けている場合は、そこで作業を止め、不足しているフィールドを追加してください。
正しい例
interface CreatePaymentResponse {
paymentId: string;
status: "approved" | "denied" | "undefined";
authorizationId: string | null;
nsu: string | null;
tid: string | null;
acquirer: string | null;
code: string | null;
message: string | null;
delayToAutoSettle: number;
delayToAutoSettleAfterAntifraud: number;
delayToCancel: number;
paymentUrl?: string;
}
async function createPaymentHandler(req: Request, res: Response): Promise<void> {
const { paymentId, value, currency, paymentMethod, card, callbackUrl } = req.body;
const result = await processPaymentWithAcquirer(req.body);
const response: CreatePaymentResponse = {
paymentId,
status: result.status,
authorizationId: result.authorizationId ?? null,
nsu: result.nsu ?? null,
tid: result.tid ?? null,
acquirer: "MyAcquirer",
code: result.code ?? null,
message: result.message ?? null,
delayToAutoSettle: 21600, // 6 hours in seconds
delayToAutoSettleAfterAntifraud: 1800, // 30 minutes in seconds
delayToCancel: 21600, // 6 hours in seconds
};
res.status(200).json(response);
}
誤った例
// Missing required fields — Gateway will reject this response
async function createPaymentHandler(req: Request, res: Response): Promise<void> {
const result = await processPaymentWithAcquirer(req.body);
// Missing: authorizationId, nsu, tid, acquirer, code, message,
// delayToAutoSettle, delayToAutoSettleAfterAntifraud, delayToCancel
res.status(200).json({
paymentId: req.body.paymentId,
status: result.status,
});
}
制約:マニフェストですべての対応決済方法を宣言する必要があります
GET /manifest エンドポイントは、コネクターが対応するすべての決済方法を、各メソッドの正しい name と allowsSplit の設定とともに列挙した paymentMethods 配列を返さなければなりません。
重要な理由
Gateway はマニフェストを読み込み、利用可能な決済方法を判定します。決済方法が欠けていると、マーチャントは VTEX Admin でその方法を設定できません。allowsSplit の値が間違っていると、分割決済に失敗します。
検出方法
マニフェストハンドラーが空の paymentMethods 配列を返す場合、またはプロバイダーが実際には対応していない決済方法をハードコードしている場合は、作業を停止してマニフェストを修正してください。
正しい例
async function manifestHandler(_req: Request, res: Response): Promise<void> {
const manifest = {
paymentMethods: [
{ name: "Visa", allowsSplit: "onCapture" },
{ name: "Mastercard", allowsSplit: "onCapture" },
{ name: "American Express", allowsSplit: "onCapture" },
{ name: "BankInvoice", allowsSplit: "onAuthorize" },
{ name: "Pix", allowsSplit: "disabled" },
],
};
res.status(200).json(manifest);
}
誤った例
// Empty manifest — no payment methods will appear in the Admin
async function manifestHandler(_req: Request, res: Response): Promise<void> {
res.status(200).json({ paymentMethods: [] });
}
推奨パターン
アーキテクチャの概要:
Shopper → VTEX Checkout → VTEX Payment Gateway → [Your Connector Middleware] → Acquirer/PSP
↕
Configuration Flow (Admin)
すべてのエンドポイント契約に推奨される TypeScript インターフェース:
// --- Manifest ---
interface ManifestResponse {
paymentMethods: Array<{
name: string;
allowsSplit: "onCapture" | "onAuthorize" | "disabled";
}>;
}
// --- Create Payment ---
interface CreatePaymentRequest {
reference: string;
orderId: string;
transactionId: string;
paymentId: string;
paymentMethod: string;
value: number;
currency: string;
installments: number;
card?: {
holder: string;
number: string;
csc: string;
expiration: { month: string; year: string };
};
miniCart: Record<string, unknown>;
callbackUrl: string;
returnUrl?: string;
}
interface CreatePaymentResponse {
paymentId: string;
status: "approved" | "denied" | "undefined";
authorizationId: string | null;
nsu: string | null;
tid: string | null;
acquirer: string | null;
code: string | null;
message: string | null;
delayToAutoSettle: number;
delayToAutoSettleAfterAntifraud: number;
delayToCancel: number;
paymentUrl?: string;
}
// --- Cancel Payment ---
interface CancelPaymentResponse {
paymentId: string;
cancellationId: string | null;
code: string | null;
message: string | null;
requestId: string;
}
// --- Capture/Settle Payment ---
interface CapturePaymentResponse {
paymentId: string;
settleId: string | null;
value: number;
code: string | null;
message: string | null;
requestId: string;
}
// --- Refund Payment ---
interface RefundPaymentResponse {
paymentId: string;
refundId: string | null;
value: number;
code: string | null;
message: string | null;
requestId: string;
}
// --- Inbound Request ---
interface InboundResponse {
requestId: string;
paymentId: string;
responseData: {
statusCode: number;
contentType: string;
content: string;
};
}
6 つのエンドポイントをすべて備えた決済フロールーター:
import { Router, Request, Response } from "express";
const router = Router();
router.get("/manifest", async (_req: Request, res: Response) => {
res.status(200).json({
paymentMethods: [
{ name: "Visa", allowsSplit: "onCapture" },
{ name: "Mastercard", allowsSplit: "onCapture" },
{ name: "Pix", allowsSplit: "disabled" },
],
});
});
router.post("/payments", async (req: Request, res: Response) => {
const body: CreatePaymentRequest = req.body;
const result = await processWithAcquirer(body);
const response: CreatePaymentResponse = {
paymentId: body.paymentId,
status: result.status,
authorizationId: result.authorizationId ?? null,
nsu: result.nsu ?? null,
tid: result.tid ?? null,
acquirer: "MyProvider",
code: result.code ?? null,
message: result.message ?? null,
delayToAutoSettle: 21600,
delayToAutoSettleAfterAntifraud: 1800,
delayToCancel: 21600,
};
res.status(200).json(response);
});
router.post("/payments/:paymentId/cancellations", async (req: Request, res: Response) => {
const { paymentId } = req.params;
const { requestId } = req.body;
const result = await cancelWithAcquirer(paymentId);
res.status(200).json({
paymentId,
cancellationId: result.cancellationId ?? null,
code: result.code ?? null,
message: result.message ?? "Successfully cancelled",
requestId,
});
});
router.post("/payments/:paymentId/settlements", async (req: Request, res: Response) => {
const body = req.body;
const result = await captureWithAcquirer(body.paymentId, body.value);
res.status(200).json({
paymentId: body.paymentId,
settleId: result.settleId ?? null,
value: result.capturedValue ?? body.value,
code: result.code ?? null,
message: result.message ?? null,
requestId: body.requestId,
});
});
router.post("/payments/:paymentId/refunds", async (req: Request, res: Response) => {
const body = req.body;
const result = await refundWithAcquirer(body.paymentId, body.value);
res.status(200).json({
paymentId: body.paymentId,
refundId: result.refundId ?? null,
value: result.refundedValue ?? body.value,
code: result.code ?? null,
message: result.message ?? null,
requestId: body.requestId,
});
});
router.post("/payments/:paymentId/inbound-request/:action", async (req: Request, res: Response) => {
const body = req.body;
const result = await handleInbound(body);
res.status(200).json({
requestId: body.requestId,
paymentId: body.paymentId,
responseData: {
statusCode: 200,
contentType: "application/json",
content: JSON.stringify(result),
},
});
});
export default router;
設定フローのエンドポイント(マーチャントのオンボーディング用、任意):
import { Router, Request, Response } from "express";
const configRouter = Router();
// 1. POST /authorization/token
configRouter.post("/authorization/token", async (req: Request, res: Response) => {
const { applicationId, returnUrl } = req.body;
const token = await generateAuthorizationToken(applicationId, returnUrl);
res.status(200).json({ applicationId, token });
});
// 2. GET /authorization/redirect
configRouter.get("/authorization/redirect", async (req: Request, res: Response) => {
const { token } = req.query;
const providerLoginUrl = buildProviderLoginUrl(token as string);
res.redirect(302, providerLoginUrl);
});
// 3. GET /authorization/credentials
configRouter.get("/authorization/credentials", async (req: Request, res: Response) => {
const { authorizationCode } = req.query;
const credentials = await exchangeCodeForCredentials(authorizationCode as string);
res.status(200).json({
applicationId: "vtex",
appKey: credentials.appKey,
appToken: credentials.appToken,
});
});
export default configRouter;
よくある失敗
- エンドポイントの部分的な実装 — Create Payment と Capture だけを実装し、Manifest、Cancel、Refund、Inbound Request を省略しています。テストスイートはすべてのエンドポイントをテストするため、ホモロゲーションに失敗します。実行時に Gateway は決済をキャンセルまたは返金できません。
- HTTP メソッドの誤り — Manifest エンドポイントに POST を使用したり、Create Payment に GET を使用したりしています。Gateway は特定の HTTP メソッドを送信するため、ハンドラーが一致しない場合は 404 または 405 が返されます。
- 遅延値がない、または 0 — Create Payment のレスポンスから
delayToAutoSettle、delayToAutoSettleAfterAntifraud、またはdelayToCancelを省略するか、0 に設定しています。その結果、自動キャプチャまたは自動キャンセルが即座に実行され、決済が早期に確定したり、決済が失われたりします。 - 不完全なレスポンス形式 —
authorizationId、tid、nsu、acquirerなどを含めず、paymentIdとstatusだけを返しています。Gateway はすべてのフィールドをデシリアライズするため、欠けているフィールドがあると失敗として扱われます。
レビューチェックリスト
- 決済フローのエンドポイント 6 つ(Manifest、Create Payment、Cancel、Capture、Refund、Inbound Request)をすべて実装していますか?
- 各エンドポイントは、必須フィールドをすべて含む完全なレスポンス形式を返しますか?
- Manifest は、プロバイダーが実際に対応するすべての決済方法を宣言していますか?
- 正しい HTTP メソッド(Manifest は GET、それ以外はすべて POST)を使用していますか?
-
delayToAutoSettle、delayToAutoSettleAfterAntifraud、delayToCancelは、適切な 0 以外の値に設定されていますか? - コネクターは、TLS 1.2 を使用してポート 443 の HTTPS 経由で提供されていますか?
- コネクターは、テストスイートでは 5 秒以内、本番環境では 20 秒以内に応答しますか?
- マーチャントによるセルフオンボーディングが必要な場合、設定フローのエンドポイントを実装していますか?
関連スキル
payment-idempotency— 重複防止のための冪等性キー(paymentId、requestId)と状態マシンpayment-async-flow— 非同期決済方法、callbackUrl、および 7 日間の再試行期間payment-pci-security— PCI 準拠、Secure Proxy、カードデータの取り扱いpayment-ppp-pos— POS/Sales App の対面カード決済オーバーレイ。エンドポイントの形式にはこのスキルと併用してくださいvtex-io-application-performance— IO ベースの決済コネクターにおける、クライアントごとのタイムアウト/再試行の調整、VBase の正確性に関する制約、構造化ログ
参考資料
- 決済プロバイダープロトコルの概要 — エンドポイントの要件、共通パラメーター、テストスイートの情報を含む API の概要
- 決済プロバイダーの実装 — リクエスト/レスポンスの例を含む、9 つすべてのエンドポイントを網羅したステップごとのガイド
- 決済プロバイダープロトコル(ヘルプセンター) — 決済フローの図やコールバック URL の使用方法を含む、プロトコルの概要説明
- 購入フロー — オーソリ、キャプチャ、キャンセルのフローの詳細
- 決済プロバイダープロトコル API リファレンス — すべての PPP エンドポイントの完全な OpenAPI 仕様
- VTEX での新しい決済プロバイダーの統合 — 開発からホモロゲーションまでを網羅した、エンドツーエンドの統合ガイド