mcpField
P
payment-provider-framework

payment-provider-framework

github.com/vtex開発47950 回インストール2 週間前に更新
リポジトリ

payment-provider-framework について

VTEX IOで決済コネクターを開発・保守するためのスキルです。フレームワークの接続、設定、カードリクエスト、テストとリリースに加え、TypeScriptやスキーマの制約を扱います。

主な機能

  1. 01paymentProvider builderとconfiguration.jsonの設定
  2. 02PaymentProviderのルートとサービスの実装
  3. 03Gatewayの再試行とsecure proxyのカード処理
  4. 04TypeScript 3.9.7と依存関係の互換性確認
  5. 05テスト、リリース、VTEX homologationの計画

活用シーン

  1. 01VTEX IO決済コネクターの新規作成
  2. 02builderや型宣言に起因するビルド失敗の診断
  3. 03決済方法、カスタムフィールド、機能フラグの設定
  4. 04決済コネクターのテストとリリース

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/vtex/skills --skill payment-provider-framework

インストール後、Claude Code で/payment-provider-frameworkを入力して読み込まれたか確認します。

詳細

Payment Provider Framework(VTEX IO)

このスキルを使う場面

次の場合にこのスキルを使います:

  • VTEX IO アプリとして実装された決済コネクターを作成または保守する場合(自分でホストするスタンドアロンの HTTP サービスは対象外です)
  • node/index.ts で @vtex/payment-provider、PaymentProvider、PaymentProviderService を接続する場合
  • paymentProvider ビルダー、configuration.json(決済方法、customFields、機能フラグ)を設定する場合
  • IO での Gateway の再試行セマンティクスに対応するため、this.retry(request) を実装する場合
  • IO でのカードフローのリクエストに対して SecureExternalClient を拡張し、secureProxy を渡す場合
  • 決済アフィリエーション、ワークスペース、ベータ版/安定版リリース、VTEX App Store、VTEX のホモロゲーションを通じてテストする場合

次の場合、このスキルは使わないでください:

  • PPP の HTTP コントラクト、レスポンスの各フィールド要件、抽象的な9つのエンドポイントについては、payment-provider-protocol を使ってください
  • 冪等性と paymentId の重複処理については、payment-idempotency を使ってください
  • 非同期の undefined ステータス、callbackUrl 通知と再試行(IO 対非 IO)については、payment-async-flow を使ってください
  • IO での接続設定を超える PCI ルール、ログ記録、トークンのセマンティクスについては、payment-pci-security を使ってください
  • POS/Sales App のカード提示型オーバーレイ(Venda Direta メソッド、Payment Apps、端末シリアル番号)については、このスキルと併せて payment-ppp-pos を使ってください

判断ルール

  • IO 上の PPF:「Payment Provider Framework は、VTEX IO ベースで決済コネクターを構築する方法です。」アプリは IO のインフラストラクチャを使用します。API ルート、リクエスト/レスポンスの型、Secure Proxy は VTEX のガイドに従って統合されます。公式の Payment Provider Framework ドキュメントで説明されているサンプルアプリから始めてください。
  • 前提条件:Payment Provider Protocol の記事と、VTEX に新しい決済プロバイダーを統合するためのガイドに記載されている実装の前提条件に従ってください。
  • 依存関係:アプリの node フォルダーに @vtex/payment-provider を追加します(たとえば、package.json で 1.x)。@vtex/api は devDependencies に残します(たとえば、6.x)。リンクによって 6.x より新しいバージョンに更新されても問題ありません。型に関するエラーが発生する場合は、プロジェクトルートと node 内の node_modules および yarn.lock を削除してから、両方で yarn install -f を実行してください。
  • paymentProvider ビルダー:manifest.json で node の隣に "paymentProvider": "1.x" を含め、Payment Gateway コールバックと PPP ルートのポリシーが適用されるようにします。
  • configuration.json:ビルダーが /manifest ですべてを再宣言せずに実装できるよう、paymentMethods を宣言します。List Payment Provider Manifest API リファレンスと一致する名前を使用し、新しい決済方法が本当に新しい場合に限り、新しい名前を作成してください。Admin の新しい決済方法には、サポートチケットが必要な場合があります。
  • PaymentProvider:PPP ルートごとにクラスメソッドを 1 つ定義します。TypeScript が型を検証します。API リファレンスの Payment Flow エンドポイントを参照してください。
  • PaymentProviderService:デフォルトのルート /manifest、/payments、/settlements、/refunds、/cancellations、/inbound を登録します。必要に応じて、追加の routes/clients を渡してください。
  • /manifest のオーバーライド:承認済みのユースケースに限ります。チケットを作成してください。ルートをオーバーライドする形式の例については、Preferred pattern セクションを参照してください。
  • 設定可能なオプション:implementsOAuth、implementsSplit、usesProviderHeadersName、usesBankInvoiceEnglishName、usesSecureProxy、requiresDocument、acceptSplitPartialRefund、usesAutoSettleOptions などのフラグには、configuration.json/ビルダーオプションを使用します。name を設定し、特に別途記載がない限り、IO で自動生成される serviceUrl を使用してください。フィールドを独自に作成しないでください。usesTestSuite などの未定義のキーは、ビルダーの検証エラーの原因になります。正規の一覧と customFields の形式については、以下の「configuration.json schema」の制約を参照してください。
  • ゲートウェイの再試行:PPF では、プロトコルで再試行が必要な場合に this.retry(request) を呼び出してください。PPP の記事の Payment authorization セクションを参照してください。
  • IO 上のカードデータ:「SecureExternalClient を使用し、Create Payment の secureProxy: secureProxyUrl を指定してください。宛先は許可リストに登録されている必要があります。」Secure Proxy でサポートされる Content-Type は application/json と application/x-www-form-urlencoded のみです。重要:secureProxyUrl が含まれるのは Create Payment(オーソリ)リクエストだけです。オーソリ後の操作(キャンセル、キャプチャ、返金)ではカードデータを転送しないため、認証情報と outbound-access ポリシーを使って ExternalClient 経由で PSP API を直接呼び出す必要があります。
  • チェックアウトのテスト:アカウントで IO コネクターが許可されている必要があります(アプリ名とアカウントを記載したチケットを作成してください)。ベータ版を公開して master にインストールし、約 1 時間待ってからアフィリエーション URL を開き、テストモードとワークスペースを有効にして、決済条件を設定し(約 10 分)、テスト注文を作成します。その後、安定版とホモロゲーションに進みます。
  • 公開:Billing Options ガイドに従って billingOptions を設定し、Submitting your app に従って提出します。Integrating a new payment provider on VTEX ガイドに従って、ホモロゲーション用の資料(コネクターアプリ名、パートナーの連絡先、本番環境のエンドポイント、許可されたアカウント、新しい決済方法/フロー)を準備してください(SLA は通常約 30 日です)。
  • 更新:変更を新しいベータ版でリリースし、アフィリエーションを再テストしてから安定版をリリースします。必要に応じて、ホモロゲーションを再度受けてください。

厳格な制約

制約:Builder-Hub は TypeScript 3.9.7 を使用します — コードと依存関係は互換性が必須です

vtex.builder-hub は IO アプリを TypeScript 3.9.7 でコンパイルします。また、tsconfig.json の skipLibCheck: true も 無視します。そのため、node_modules 内のすべての .d.ts ファイルが型チェックされます。つまり、自分のコードが有効でも、推移的依存関係が新しい構文を使った .d.ts を含んでいると、自分のコードとは無関係な数百件のエラーでビルドが失敗します。

重要な理由

エージェントや開発者は、TS 4.x 以降の構文を使ったコードを作成したり、最新の @types/* パッケージをインストールしたりすることがよくあります。開発者が一度も触れていないファイルで不可解なエラーが発生してビルドが失敗し、無駄な修正を何度も繰り返すことになります。

禁止されている構文(TS 3.9.7 と互換性がありません)

構文最低 TS バージョン例
テンプレートリテラル型4.1type X = `${string}/${string}`
型注釈付き catch 句4.0catch (error: any)
override キーワード4.3クラス内の override method()
import type ... = require()4.5import type X = require("pkg")
satisfies 演算子4.9obj satisfies Type

正しい catch ブロックのパターン

// CORRECT — TS 3.9.7 compatible
try {
  // ...
} catch (error) {
  const err = error as any
  console.log(err.message)
}

// WRONG — TS 4.0+ only
try {
  // ...
} catch (error: any) {
  console.log(error.message)
}

未使用の変数は警告ではなくエラーです。 builder-hub は、宣言されているのに使われていない変数をコンパイルエラーとして扱います。使用しないフィールドを分割代入で取り出さないでください。

// WRONG — if callbackUrl is not used, build fails
const { paymentId, callbackUrl, value } = authorization

// CORRECT
const { paymentId, value } = authorization

安全な依存関係のバージョン(TS 3.9.7 と互換性があります)

node/package.json の resolutions を使って、推移的依存関係を新しい .d.ts 構文を含まないバージョンに固定してください。**/<package> パターンを使うと、ネストされたコピーも固定できます。

{
  "dependencies": {
    "@vtex/payment-provider": "1.x"
  },
  "devDependencies": {
    "@vtex/api": "6.50.1",
    "@types/node": "12.20.55",
    "@types/express-serve-static-core": "4.17.2",
    "@types/express": "4.17.10",
    "@types/serve-static": "1.15.0",
    "@opentelemetry/api": "1.0.4",
    "typescript": "3.9.7"
  },
  "resolutions": {
    "@types/node": "12.20.55",
    "@types/express-serve-static-core": "4.17.2",
    "@types/express": "4.17.10",
    "@types/serve-static": "1.15.0",
    "@opentelemetry/api": "1.0.4",
    "**/@types/express-serve-static-core": "4.17.2",
    "**/@types/koa": "2.15.0"
  }
}
パッケージ安全なバージョン最初に問題が発生するバージョン理由
@types/node12.20.5513.x+(一部の API).d.ts に新しい構文が含まれる
@types/express-serve-static-core4.17.24.17.13+テンプレートリテラル型
@types/express4.17.104.17.11+@types/express-serve-static-core@^4.17.18 に依存
@types/koa2.15.03.ximport type ... = require()
@opentelemetry/api1.0.4それ以降のバージョンTS 4.x の構文
@types/serve-static1.15.0それ以降のバージョン推移的依存関係の問題

新たに問題が発生したパッケージの診断: node_modules 内の .d.ts ファイルでエラーが発生した場合は、エラーのパスからパッケージを特定し、新しい構文が含まれないバージョンが見つかるまで古いバージョンを試してください。その後、devDependencies と resolutions の両方に追加します(**/<package> パターンを使用)。

検出

生成されたコードが上記の表にある構文を使っている場合、または package.json に型パッケージの resolutions がない場合は、vtex link を実行する前に必ず停止して修正してください。

制約:configuration.json では有効なスキーマフィールドのみを使い、正しい customFields 形式にする必要があります

paymentProvider ビルダーは、厳密なスキーマに照らして configuration.json を検証します。不明なフィールドがあるとビルドエラーになります。select 型フィールドの customFields[].options 配列では、text キーと value キーを使う必要があります。label は使用しないでください。

重要な理由

usesTestSuite や useAntifraud など、現在のスキーマにないフィールドを指定すると、直ちに vtex link が失敗します。select のオプションで text の代わりに label を使うと、Admin UI が正しく動作しなくなったり、検証に失敗したりします。

正規のフィールド(最新の VTEX ドキュメントで確認してください):

name(必須)、serviceUrl(IO では自動設定)、implementsOAuth、implementsSplit、usesProviderHeadersName、usesBankInvoiceEnglishName、usesSecureProxy、requiresDocument、acceptSplitPartialRefund、usesAutoSettleOptions、paymentMethods、customFields。

ビルドを失敗させることが確認されているフィールド: usesTestSuite(スキーマに存在しません)。

select を使った正しい customFields

{
  "name": "AcmePayConnector",
  "usesAutoSettleOptions": true,
  "paymentMethods": [
    { "name": "Visa", "allowsSplit": "onCapture" }
  ],
  "customFields": [
    {
      "name": "Environment",
      "type": "select",
      "options": [
        { "text": "Sandbox", "value": "sandbox" },
        { "text": "Production", "value": "production" }
      ]
    }
  ]
}

誤った customFields

{
  "customFields": [
    {
      "name": "Environment",
      "type": "select",
      "options": [
        { "label": "Sandbox", "value": "sandbox" }
      ]
    }
  ]
}

検出

configuration.json に正規の一覧にないキーが含まれている場合、または select のオプションで text の代わりに label を使っている場合は、ビルドの前に必ず停止して修正してください。

制約:configuration.json で paymentProvider ビルダーと実際のコネクター識別情報を宣言する

IO コネクターには、manifest.json の paymentProvider ビルダーと、プレースホルダーではない name および正確な paymentMethods を含む paymentProvider/configuration.json が必須です。本番構成に文字どおりのプレースホルダー "MyConnector"(または同等のもの)を含めてはいけません。

重要な理由

ビルダーがないと、PPP のルートと Gateway ポリシーが接続されません。プレースホルダー名は Admin、アフィリエーション、ホモロゲーションに支障をきたします。

検出方法

manifest.json に paymentProvider がない場合、または configuration.json に例示用のプレースホルダー名が残っている場合は、公開前に作業を中断して修正してください。

正しい例

{
  "name": "PartnerAcmeCard",
  "paymentMethods": [
    { "name": "Visa", "allowsSplit": "onCapture" },
    { "name": "BankInvoice", "allowsSplit": "onAuthorize" }
  ]
}

誤った例

{
  "name": "MyConnector",
  "paymentMethods": []
}
制約:PPP ルートは PaymentProvider 実装を使用し、PaymentProviderService 経由でのみ登録する

サービスでは、@vtex/payment-provider の PaymentProvider を継承するクラスを必ずラップして、標準の PPP パスを登録してください。VTEX が別の方法を明示的に指定していない限り、パッケージを使わずに同じルート群を独自に実装してはいけません。

重要な理由

ルートの欠落や不一致は、Gateway 呼び出しやホモロゲーションに支障をきたします。このパッケージを使うことで、ハンドラーをプロトコルに沿った状態に保てます。

検出方法

node/index.ts で PPP パスを手動で公開し、コネクタークラスを指定して PaymentProviderService をインスタンス化していない場合は、ドキュメントに記載されたパターンに合わせて修正してください。

正しい例

import { PaymentProviderService } from "@vtex/payment-provider";
import { YourPaymentConnector } from "./connector";

export default new PaymentProviderService({
  connector: YourPaymentConnector,
});

誤った例

// Ad-hoc router only — no PaymentProviderService / PaymentProvider base
export default someCustomRouterWithoutPPPPackage;
制約:PaymentProviderService の clients フィールドには、クラスを直接ではなく { implementation, options } を指定する

カスタムの IOClients を PaymentProviderService に渡す場合、clients フィールドには、@vtex/api の ServiceConfig インターフェイスに従って、implementation(クラス)と options(リトライ/タイムアウト設定)を含むオブジェクトが必要です。クラスを直接渡すと、実行時エラーが発生します。

重要な理由

これはよくあるミスです。PPF の型では厳密に強制されない場合があるため、明確な型エラーではなく、分かりにくい実行時エラーが発生します。

正しい例

import { PaymentProviderService } from '@vtex/payment-provider'
import MyConnector from './connector'
import { Clients } from './clients'

export default new PaymentProviderService({
  connector: MyConnector,
  clients: {
    implementation: Clients,
    options: {
      default: {
        retries: 2,
        timeout: 15000,
      },
    },
  },
})

誤った例

export default new PaymentProviderService({
  connector: MyConnector,
  clients: Clients,  // WRONG — expects { implementation, options }
})
制約:IO での Gateway リトライには this.retry(request) を使用する

PPP フローで IO のリトライ動作が必要な場合、ハンドラーではプロトコルの仕様どおりに this.retry(request) を必ず呼び出してください。フレームワークを迂回する独自のリトライヘルパーを使ってはいけません。

重要な理由

「Gateway はフレームワークによって制御されるリトライ動作を想定しています。これを省略すると、承認や売上確定の動作に一貫性がなくなります。」

検出方法

決済ハンドラーでプロトコル上のリトライケースを検索してください。this.retry を使わずにリトライを実装している場合は、リリース前に修正してください。

正しい例

// Inside a PaymentProvider subclass method, when the protocol requires retry:
return this.retry(request);

誤った例

// Re-implementing gateway retry with setTimeout/fetch instead of this.retry
await fetch(callbackUrl, { method: "POST", body: JSON.stringify(payload) });
制約:許可リストに登録された送信先を使用し、カード承認リクエストを IO 上の Secure Proxy 経由で転送する

usesSecureProxy の動作を使用する IO 上のカードフローでは、プロキシ経由の HTTP 呼び出しには必ず SecureExternalClient(または同等の VTEX パターン)を使用し、決済リクエストの secureProxyUrl を secureProxy に設定し、VTEX の許可リストに登録された PCI エンドポイントを送信先に指定してください。本文でサポートされるのは application/json または application/x-www-form-urlencoded のみです。usesSecureProxy が false の場合、プロバイダーは PCI 認定を受け、VTEX の要件に従って serviceUrl の AOC を提出する必要があります。

重要な理由

「Secure Proxy を経由しない場合や、誤ったコンテンツタイプを使用した場合、PCI スコープ、プロキシ検証、またはアクワイアラーとの連携に支障が生じ、ホモロゲーションが阻まれたり、カードデータが不適切に公開されたりします。」

検出方法

クライアントコードを確認し、リクエスト設定に secureProxy を指定せずにカードトークンを含む POST を行っていないか、または VTEX に登録されていない送信先を使っていないかを調べてください。

正しい例

import { SecureExternalClient, CardAuthorization } from "@vtex/payment-provider";
import type { InstanceOptions, IOContext, RequestConfig } from "@vtex/api";

export class MyPCICertifiedClient extends SecureExternalClient {
  constructor(protected context: IOContext, options?: InstanceOptions) {
    super("https://pci-certified.example.com", context, options);
  }

  public authorize = (cardRequest: CardAuthorization) =>
    this.http.post(
      "authorize",
      {
        holder: cardRequest.holderToken,
        number: cardRequest.numberToken,
        expiration: cardRequest.expiration,
        csc: cardRequest.cscToken,
      },
      {
        headers: { Authorization: "Bearer ..." },
        secureProxy: cardRequest.secureProxyUrl,
      } as RequestConfig
    );
}

誤った例

// Direct outbound call with raw card fields and no secureProxy
await http.post("https://acquirer.example/pay", { pan, cvv, expiry });
制約:secureProxyUrlを受け取るのはCreate Paymentのみ — 認証後の操作ではPSPを直接呼び出します

secureProxyUrlフィールドが含まれるのは、Create Payment(オーソリ)リクエストのみです。キャンセル、キャプチャ、返金の操作ではカードデータを扱わず、secureProxyUrlも受け取りません。これらの操作では、manifest.jsonのoutbound-accessポリシーで保護されたAPI認証情報を使い、@vtex/apiのExternalClientでPSP APIを直接呼び出す必要があります。

重要な理由

キャンセル、キャプチャ、返金のハンドラーでSecureExternalClientやsecureProxyUrlを使おうとしても、これらのリクエストではフィールドがundefinedのため失敗します。これはPCIに関する問題ではありません。これらの操作で参照するのはトランザクションIDのみで、カードデータは扱わないためです。

検出方法

キャンセル、キャプチャ、返金のハンドラーがsecureProxyUrlを参照する、またはSecureExternalClientを使用している場合は、作業を中止してください。これらの操作では、PSPへの直接HTTP呼び出しにExternalClientを使用する必要があります。

正しい例 — 2クライアントパターン

// clients/pspSecure.ts — for authorization only (via Secure Proxy)
import { SecureExternalClient } from "@vtex/payment-provider";
import type { InstanceOptions, IOContext } from "@vtex/api";

export class PspSecureClient extends SecureExternalClient {
  constructor(ctx: IOContext, opts?: InstanceOptions) {
    super("https://api.psp.com", ctx, opts);
  }

  public async authorize(data: object, secureProxyUrl: string) {
    return this.http.post("/v1/payments", data, {
      secureProxy: secureProxyUrl,
    } as any)
  }
}

// clients/psp.ts — for cancel, capture, refund (direct calls)
import { ExternalClient } from "@vtex/api";
import type { InstanceOptions, IOContext } from "@vtex/api";

export class PspClient extends ExternalClient {
  constructor(ctx: IOContext, opts?: InstanceOptions) {
    super("https://api.psp.com", ctx, opts);
  }

  public async capture(transactionId: string, amount: number) {
    return this.http.post(`/v1/payments/${transactionId}/capture`, { amount })
  }

  public async cancel(transactionId: string) {
    return this.http.post(`/v1/payments/${transactionId}/cancel`, {})
  }

  public async refund(transactionId: string, amount: number) {
    return this.http.post(`/v1/payments/${transactionId}/refund`, { amount })
  }
}

誤った例

// WRONG — trying to use SecureExternalClient for capture
async settle(settlement: SettlementRequest) {
  const client = this.context.clients.pspSecure as PspSecureClient
  // secureProxyUrl is undefined here — this will fail
  await client.capture(settlement.tid, settlement.value, settlement.secureProxyUrl)
}
制約:クライアントクラスの外部からクライアントインスタンスの.httpにアクセスしないでください

ExternalClientとSecureExternalClientのhttpプロパティはprotectedです。PaymentProviderサブクラスからclient.http.post(...)を呼び出すと、builder-hubでTypeScriptのコンパイルエラーが発生します。

重要な理由

開発者がクライアントのパブリックメソッドを介さず、コネクタークラスからHTTPリクエストを送ろうとしてよく起こる間違いです。builder-hubはprotectedアクセスを強制するため、ビルドに失敗します。

検出方法

コネクターのコードがクライアントインスタンスの.httpにアクセスしている場合(例:this.context.clients.myClient.http.post(...))、作業を中止してください。代わりに、クライアントのサブクラスにパブリックメソッドを用意してください。

正しい例

// Inside the client class — this.http is accessible (protected = same class)
export class PspClient extends ExternalClient {
  public async capturePayment(tid: string, amount: number) {
    return this.http.post(`/v1/payments/${tid}/capture`, { amount })
  }
}

// Inside the connector — call the public method
async settle(settlement: SettlementRequest) {
  const clients = this.context.clients as any as { psp: PspClient }
  return clients.psp.capturePayment(settlement.tid, settlement.value)
}

誤った例

// Inside the connector — .http is protected, build fails
async settle(settlement: SettlementRequest) {
  const clients = this.context.clients as any as { psp: PspClient }
  return clients.psp.http.post(`/v1/capture`, { amount: settlement.value })
  //                  ^^^^ TS error: Property 'http' is protected
}

推奨パターン

プロジェクトのファイル構成
/
├── manifest.json                          # App identity, builders, policies
├── paymentProvider/
│   └── configuration.json                 # Payment methods, connector options
├── service.json                           # Runtime config (memory, timeout, replicas, custom routes)
└── node/
    ├── package.json                       # Dependencies WITH resolutions for TS 3.9.7
    ├── tsconfig.json                      # TypeScript config (skipLibCheck ignored by builder)
    ├── yarn.lock                          # REQUIRED — builder rejects build without it
    ├── index.ts                           # Entry point: exports PaymentProviderService
    ├── connector.ts                       # Class extending PaymentProvider
    ├── clients/
    │   ├── index.ts                       # IOClients with getOrSet
    │   ├── psp.ts                         # ExternalClient for direct PSP calls (cancel/capture/refund)
    │   └── pspSecure.ts                   # SecureExternalClient for card authorization via Secure Proxy
    └── typings/
        └── psp.ts                         # TypeScript interfaces

重要なファイルに関する注意事項:

  • node/内の**yarn.lockは必須**です。これがないとビルダーがビルドを拒否します。.vtexignoreに追加しないでください。
  • **service.json**はnode/内ではなく、プロジェクトのルートに配置します。
  • **configuration.json**はルートではなく、paymentProvider/内に配置します。
manifest.jsonのビルダーとポリシー
{
  "builders": {
    "node": "7.x",
    "paymentProvider": "1.x"
  },
  "policies": [
    { "name": "vbase-read-write" },
    { "name": "outbound-access", "attrs": { "host": "api.psp.com", "path": "/*" } },
    { "name": "outbound-access", "attrs": { "host": "api.sandbox.psp.com", "path": "/*" } }
  ]
}

注:状態の保存にVBaseを使用する場合は、vbase-read-writeポリシーが必要です。このポリシーがないと、vbase.saveJSON()は403を返します。

tsconfig.json
{
  "compilerOptions": {
    "target": "es2019",
    "module": "commonjs",
    "lib": ["es2019"],
    "outDir": "./dist",
    "rootDir": ".",
    "strict": false,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["**/*.ts"],
  "exclude": ["node_modules", "**/node_modules", "dist"]
}

注:builder-hubは独自の値でoutDir、rootDir、pathsを上書きします。skipLibCheck: trueは無視されます。すべての.d.tsファイルがチェックされます。代わりに、package.jsonのresolutionsで依存関係の型のバージョンを制御してください。

IOClientsの登録
import { IOClients } from '@vtex/api'
import { PspClient } from './psp'
import { PspSecureClient } from './pspSecure'

export class Clients extends IOClients {
  public get psp(): PspClient {
    return this.getOrSet('psp', PspClient)
  }

  public get pspSecure(): PspSecureClient {
    return this.getOrSet('pspSecure', PspSecureClient)
  }
}
コネクターでのカスタムクライアントとIOContextへのアクセス
import { PaymentProvider } from '@vtex/payment-provider'
import type { Clients } from './clients'

export default class MyConnector extends PaymentProvider {
  // IOContext is at this.context.vtex, NOT this.context directly
  private get account(): string {
    const vtexCtx = (this.context as any).vtex as { account: string; workspace: string }
    return vtexCtx?.account || ''
  }

  private get workspace(): string {
    const vtexCtx = (this.context as any).vtex as { account: string; workspace: string }
    return vtexCtx?.workspace || 'master'
  }

  // Custom clients require a cast
  private get clients() {
    return this.context.clients as any as {
      psp: Clients['psp']
      pspSecure: Clients['pspSecure']
      vbase: import('@vtex/api').VBase
    }
  }

  // VBase is available directly
  private get vbase() {
    return this.context.clients.vbase
  }
}
クライアント、カスタムルート、service.jsonを使用するPaymentProviderService
// node/index.ts
import { PaymentProviderService } from '@vtex/payment-provider'
import MyConnector from './connector'
import { Clients } from './clients'

export default new PaymentProviderService({
  connector: MyConnector,
  clients: {
    implementation: Clients,
    options: {
      default: {
        retries: 2,
        timeout: 15000,
      },
    },
  },
  // Optional: custom routes (e.g., for redirect callbacks)
  // routes: {
  //   myCallback: myCallbackHandler,
  // },
})

カスタムルートを使用する場合は、プロジェクトルートのservice.jsonに登録してください。

{
  "memory": 256,
  "ttl": 10,
  "timeout": 10,
  "minReplicas": 2,
  "maxReplicas": 10,
  "routes": {
    "myCallback": {
      "path": "/_v/my-connector/callback",
      "public": true
    }
  }
}

PPFビルダーは独自のルート(PPPエンドポイント)とユーザー定義のルートをマージします。標準のPPPルートを再定義する必要はありません。

アフィリエーションによる認証情報

PPFはアフィリエーションヘッダーをコネクターのプロパティに自動的にマッピングします。

VTEXヘッダーコネクターのプロパティ一般的な用途
X-PROVIDER-API-AppKeythis.apiKeyPSPのクライアントID/APIキー
X-PROVIDER-API-AppTokenthis.appTokenPSPのクライアントシークレット/APIトークン

this.isTestSuiteは、トランザクションがテスト(サンドボックス)かどうかを示します。

configuration.jsonのcustomFieldsにあるカスタム設定は、各リクエストのオーソリオブジェクトから利用できます(正確なアクセス方法はPPFのバージョンによって異なるため、@vtex/payment-providerの型定義を確認してください)。

PPF レスポンスヘルパー — 各ヘルパーが自動で設定する項目

@vtex/payment-provider の Authorizations、Settlements、Refunds ヘルパーを使用する場合:

ヘルパーリクエストから自動設定される項目第 2 引数に渡さない項目
Authorizations.approve(auth, { ... })paymentId—
Authorizations.deny(auth, { ... })paymentIdnsu(deny の型には含まれません)
Settlements.approve(settle, { settleId })paymentId、valuevalue(すでにリクエストからマージされています)
Refunds.approve(refund, { refundId })paymentId、valuevalue(すでにリクエストからマージされています)

厳密な型に delayToCancel が含まれていない場合に、approveCard で delayToCancel を追加するには、型アサーションを使ったスプレッドを使用します。

// TS 3.9.7 compatible
const baseResponse = Authorizations.approve(authorization, {
  authorizationId: result.authorizationId,
  nsu: result.nsu,
  tid: result.tid,
  acquirer: 'MyPSP',
  code: '200',
  message: 'Approved',
})
const response = {
  ...baseResponse,
  delayToCancel: 21600,
  delayToAutoSettle: 21600,
  delayToAutoSettleAfterAntifraud: 1800,
} as any
return response
PSP 統合チェックリスト(プロバイダー非依存)

PSP クライアントを接続する前に:

  1. PSP の API Explorer / OpenAPI 仕様を開き、環境ごと(テスト/本番)の ベース URL を確認します。
  2. baseURL と操作パスで、バージョンやパスのセグメントを重複させないでください(たとえば、ベースが https://checkout-test.psp.com/v71 の場合、支払いのパスは /v71/payments ではなく /payments です)。
  3. コネクターの実装を完了する前に、テスト呼び出し(例:支払いの作成)で検証します。
  4. PSP が OAuth トークンを必要とする場合は、アクセストークンをメモリー内にキャッシュする実装にします。VTEX Gateway の一部のフローにはタイムアウトが 2 秒あり、トークン取得と API 呼び出しを逐次実行するとタイムアウトを超過します。
// Simple in-memory token cache (TS 3.9.7 compatible)
const tokenCache: Record<string, { token: string; expiresAt: number }> = {}

function getCachedToken(clientId: string): string | null {
  const entry = tokenCache[clientId]
  if (entry && Date.now() < entry.expiresAt) return entry.token
  return null
}

function setCachedToken(clientId: string, token: string, expiresIn: number): void {
  tokenCache[clientId] = {
    token,
    expiresAt: Date.now() + (expiresIn - 300) * 1000,
  }
}
テスト用のアフィリエーション URL パターン
https://{account}.myvtex.com/admin/affiliations/connector/Vtex.PaymentGateway.Connectors.PaymentProvider.PaymentProviderConnector_{connector-name}/

{connector-name} を ${vendor}-${appName}-${appMajor} に置き換えます(例:vtex-payment-provider-example-v1)。

テストフローの概要:ベータ版を公開し(例:[email protected] — 「アプリを公開する」ドキュメントを参照)、master にインストールして約 1 時間待ち、アフィリエーションを開きます。Payment Control で Enable test mode を有効にし、Workspace(多くの場合は master)を設定して、決済条件を追加します。約 10 分待ってから注文し、その後、安定版をデプロイしてホモロゲーションを完了します。

本番環境に移行する前に、すべてのサンプルのベンダー名、エンドポイント、認証情報を実際のアプリの値に置き換えてください。

よくある失敗の原因

  • paymentProvider ビルダーがない、または paymentMethods が空か誤っているため、/manifest と Admin にメソッドが正しく表示されません。
  • node_modules/.d.ts ファイルで数百件の TS エラーが発生してビルドに失敗する — package.json に resolutions がなく、型パッケージが TS 3.9.7 互換のバージョンに固定されていません。
  • skipLibCheck: true が効かない — builder-hub では無視されます。代わりに resolutions を使ってください。
  • コネクターのコードで catch (error: any) やその他の TS 4.x 以降の構文を使っている — TS 3.9.7 互換のパターンを使ってください。
  • 無効なフィールド(usesTestSuite や独自に追加したキー)を含む configuration.json — ビルダーの検証エラーが発生します。
  • customFields の選択肢で text ではなく label を使っている — Admin UI が正しく動作しないか、検証に失敗します。
  • node/ ディレクトリーに yarn.lock がない — ビルダーがビルドを拒否します。
  • service.json がプロジェクトのルートではなく node/ 内に配置されている。
  • { implementation: Clients, options: {...} } ではなく、clients: Clients を PaymentProviderService に直接渡している。
  • コネクターから client.http.post(...) にアクセスしている — http は protected です。クライアントクラスに公開メソッドを用意してください。
  • キャンセル/キャプチャ/返金に SecureExternalClient を使っている — secureProxyUrl を持つのは Create Payment のみです。認証後の処理では ExternalClient を使ってください。
  • this.context.vtex.account ではなく this.context.account にアクセスしている — IOContext は .vtex の下にあります。
  • ルートディレクトリーと node の両方でクリーン再インストールの手順を踏まずに、型やインストールのバージョンがずれている(@vtex/api/@vtex/payment-provider)。
  • this.retry(request) を省略して、その場しのぎの HTTP 処理でリトライを重複させている — Gateway の動作が PPP と異なってきます。
  • secureProxy を使わないカード処理、不正な Content-Type、または許可リストにない宛先 — Secure Proxy または PCI の審査に失敗します。
  • アカウントの許可リストへの登録を行わず、販売可能な商品も用意せず、またはマスターインストール/支払い条件の反映を待たずにテストしている。
  • VTEX の承認なしに /manifest をオーバーライドしている、またはメジャーバージョンの更新後に古い x-provider-app を残している。
  • ホモロゲーションのチケットに、本番エンドポイント、許可対象アカウント、または購入フローの詳細が記載されていない。
  • vtex link --no-watch ではコンパイルエラーが表示されない — エラーの詳細を確認できるよう、watch モードを使ってください。
  • master ワークスペースで vtex link が失敗する — 開発用ワークスペースを使ってください(vtex use dev-workspace)。
  • manifest.json に vbase-read-write ポリシーがない — vbase.saveJSON() が 403 を返します。
  • PSP のベース URL にパスセグメントが重複している — たとえば、バージョンがすでにベース URL に含まれているのに /checkout/v71/v71/payments となっている。

レビューチェックリスト

  • このコネクターは、単独のミドルウェアガイドではなく、PaymentProvider と PaymentProviderService を使用する IO アプリですか?
  • manifest.json と paymentProvider/configuration.json は、実際のコネクター名と対応するメソッドに一致していますか?
  • configuration.json では有効なスキーマフィールドのみを使用していますか?customFields の選択肢では、label ではなく text を使用していますか?
  • package.json には、@types/* やその他のパッケージを TS 3.9.7 と互換性のあるバージョンに固定する resolutions が含まれていますか?
  • すべてのコードは TS 3.9.7 と互換性がありますか?(catch (e: any)、テンプレートリテラル型、override、satisfies を使用していませんか?)
  • yarn.lock は node/ にあり、.vtexignore には含まれていませんか?
  • service.json はプロジェクトのルートにありますか?(node/ の中ではありませんか?)
  • PaymentProviderService は、クラスを直接渡すのではなく、clients: { implementation, options } を指定してインスタンス化されていますか?
  • クライアントクラスは、外部からの this.http へのアクセスに頼らず、パブリックメソッドを公開していますか?
  • SecureExternalClient は Create Payment(オーソリ)にのみ使用し、キャンセル/キャプチャ/返金には ExternalClient を使用していますか?
  • マニフェストのオーバーライドはチケットで承認されていますか?また、handler/ヘッダー/x-provider-app は正しく設定されていますか?
  • すべてのルート実装は、@vtex/payment-provider の型およびレスポンス形式について payment-provider-protocol と一致していますか?
  • 必要な箇所で、Gateway のリトライを this.retry(request) によって実装していますか?
  • カードフローでは、secureProxy: secureProxyUrl と許可リストに登録された宛先を指定して、SecureExternalClient(または同等のもの)を使用していますか?
  • 安定版に移行する前に、アフィリエーション、テストモード、ワークスペース、決済条件の手順に従って、ベータ版/ステージング環境でテストしましたか?
  • 請求、App Store への申請、ホモロゲーションの前提条件は、内部リリースチェックリストに記載されていますか?
  • manifest.json には、PSP ホスト向けの vbase-read-write ポリシーと outbound-access ポリシーが含まれていますか?

関連スキル

  • payment-provider-protocol — PPP のエンドポイント、HTTP メソッド、レスポンス形式
  • payment-idempotency — paymentId/requestId とリトライ
  • payment-async-flow — undefined のステータス、callbackUrl(IO のリトライと通知)、リダイレクトベースのフロー
  • payment-pci-security — IO の配線にとどまらない PCI と Secure Proxy の動作
  • payment-ppp-pos — POS/Sales App の対面カード決済オーバーレイ(Venda Direta メソッド、Payment Apps、端末コールバック)

参考資料

コメント