mcpField
P
payment-pci-security

payment-pci-security

github.com/vtexセキュリティ471.1k 回インストール2 週間前に更新
リポジトリ

payment-pci-security について

VTEX の決済コネクターやヘッドレス決済フローを構築する開発者向けのスキルで、PCI DSS と Secure Proxy の利用を扱います。カードデータを非 PCI 環境に持ち込まず、必要な認可を Secure Proxy 経由にし、機密情報の保存や記録を避ける方法を説明します。

主な機能

  1. 01Secure Proxy の要否を判断
  2. 02認可と認可後の処理を分離
  3. 03BFF によるカード情報転送の防止
  4. 04カード情報の永続保存を禁止
  5. 05機密カード情報のログ出力を防止

活用シーン

  1. 01クレジットカード決済コネクターの構築
  2. 02カード情報の保存とログの監査
  3. 03ヘッドレスストアの決済フロー設計
  4. 04認可と返金などの後続処理の分離

インストール

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

$npx skills add https://github.com/vtex/skills --skill payment-pci-security

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

詳細

PCI コンプライアンスとセキュアプロキシ

このスキルを使用する場面

次の場合にこのスキルを使用してください。

  • クレジットカード、デビットカード、または提携カードを受け付ける決済コネクターを構築する場合
  • コネクターでカードデータを処理する必要がある場合、またはアクワイアラーと通信する必要がある場合
  • ホスティング環境でセキュアプロキシが必要かどうかを判断する場合
  • PCI DSS 準拠についてコネクターを監査する場合(データの保存、ログ記録、送信)

次の場合にはこのスキルを使用しないでください。

  • PPP エンドポイントの契約とレスポンス形式 — payment-provider-protocol を使用してください
  • 冪等性と重複防止 — payment-idempotency を使用してください
  • 非同期決済フロー(Boleto、Pix)とコールバック — payment-async-flow を使用してください
  • POS/VTEX Sales App の対面カード決済(Gateway の card フィールドが null の場合)— payment-ppp-pos を使用してください

判断ルール

  • コネクターが非 PCI 環境(すべての VTEX IO アプリを含む)でホストされている場合、セキュアプロキシを必ず使用してください。
  • コネクターが PCI DSS 認証(QSA が署名した AOC)を取得している場合は、生のカードデータを使用してアクワイアラーに直接接続できます。
  • Create Payment リクエストに secureProxyUrl があるか確認してください。存在する場合、セキュアプロキシが有効であり、必ず使用してください。
  • secureProxyUrl は Create Payment(オーソリ)リクエストにのみ含まれます。 キャンセル、キャプチャ、売上確定、返金の操作ではカードデータを送信せず、このフィールドも受け取りません。オーソリ後の呼び出しは、認証情報と outbound-access ポリシーを使用して PSP API に直接送信します。これらの操作ではカードデータを扱わないため、PCI コンプライアンスへの影響はありません。
  • カードトークン(numberToken、holderToken、cscToken)が有効なのは、secureProxyUrl 経由で送信された場合のみです。プロキシはアクワイアラーに転送する前に、これらを実際のデータに置き換えます。
  • 保存が許可されるのは、card.bin(先頭 6 桁)、card.numberLength、card.expiration のみです。それ以外はすべて禁止されています。
  • 開発環境であっても、カードデータをログ、データベース、ファイル、キャッシュ、エラートラッカー、APM ツールに含めてはいけません。

厳守事項

制約:非 PCI 環境では secureProxyUrl を必ず使用する

コネクターが非 PCI 環境(すべての VTEX IO アプリを含む)でホストされている場合、アクワイアラーとの通信には Create Payment リクエストの secureProxyUrl を必ず使用してください。生のカードデータを使用してアクワイアラーに直接接続してはいけません。リクエストに secureProxyUrl フィールドがある場合、セキュアプロキシが有効であり、必ず使用してください。

これが重要な理由 非 PCI 環境では、生のカードデータを扱うことが許可されていません。アクワイアラーに直接接続すると、Gateway の安全なデータ処理を回避することになり、PCI DSS に違反します。データ侵害、多額の罰金(月額 10 万ドル以上)、カード処理能力の喪失、法的責任につながる可能性があります。

検出 リクエストに secureProxyUrl があるにもかかわらず、コネクターが secureProxyUrl を経由せずにアクワイアラーのエンドポイントを直接呼び出している場合は、直ちに停止してください。アクワイアラーとの通信はすべてセキュアプロキシ経由で行う必要があります。

正しい例

async function createPaymentHandler(
  req: Request,
  res: Response,
): Promise<void> {
  const { paymentId, secureProxyUrl, card } = req.body;

  if (secureProxyUrl) {
    // Non-PCI: Route through Secure Proxy
    const acquirerResponse = await fetch(secureProxyUrl, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-PROVIDER-Forward-To": "https://api.acquirer.com/v2/payments",
        "X-PROVIDER-Forward-MerchantId": process.env.ACQUIRER_MERCHANT_ID!,
        "X-PROVIDER-Forward-MerchantKey": process.env.ACQUIRER_MERCHANT_KEY!,
      },
      body: JSON.stringify({
        orderId: paymentId,
        payment: {
          cardNumber: card.numberToken, // Token, not real number
          holder: card.holderToken, // Token, not real name
          securityCode: card.cscToken, // Token, not real CVV
          expirationMonth: card.expiration.month,
          expirationYear: card.expiration.year,
        },
      }),
    });

    const result = await acquirerResponse.json();
    // Build and return PPP response...
  }
}

誤った例

async function createPaymentHandler(
  req: Request,
  res: Response,
): Promise<void> {
  const { paymentId, secureProxyUrl, card } = req.body;

  // WRONG: Calling acquirer directly, bypassing Secure Proxy
  // This connector is non-PCI but handles card data as if it were PCI-certified
  const acquirerResponse = await fetch("https://api.acquirer.com/v2/payments", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      MerchantId: process.env.ACQUIRER_MERCHANT_ID!,
    },
    body: JSON.stringify({
      orderId: paymentId,
      payment: {
        // These are tokens but sent directly to acquirer — acquirer can't read tokens!
        // And if raw data were here, this would be a PCI violation
        cardNumber: card.numberToken,
        holder: card.holderToken,
        securityCode: card.cscToken,
      },
    }),
  });

  // This request will fail: acquirer receives tokens instead of real card data
  // And the Secure Proxy was completely bypassed
}
セキュアプロキシの適用範囲はカードのオーソリのみです。オーソリ後の操作には適用されません

secureProxyUrl フィールドが含まれるのは、Create Payment リクエスト(カードのオーソリ)のみです。キャンセル、キャプチャ(売上確定)、返金のリクエストにはカードデータが含まれないため、secureProxyUrl は含まれません。これらのリクエストでは、tid、authorizationId、または paymentId を使ってトランザクションを参照します。

オーソリ後の呼び出しは、manifest.json の outbound-access ポリシーで認可された API 認証情報を持つ ExternalClient を使用して、PSP API に直接送信する必要があります。

これが重要な理由

キャンセル、キャプチャ、返金をセキュアプロキシ経由でルーティングしようとすると、これらのリクエストでは secureProxyUrl が undefined であるため失敗します。アーキテクチャ上、PCI に関する懸念はありません。機密性の高いカードデータを含むのはオーソリのみです。すべての PSP 通信をセキュアプロキシ経由にする必要があると思い込むエージェントや開発者は、undefined のプロキシ URL のデバッグに多くの時間を費やすことになります。

検出方法

キャンセル、キャプチャ、返金のハンドラーが secureProxyUrl を参照する、SecureExternalClient を使用する、またはセキュアプロキシ経由でのルーティングを試みる場合は、処理を中止してください。オーソリ後の操作には、直接 API 呼び出しを行う ExternalClient を使用してください。

正しい例 — 2 クライアント構成

// Authorization (Create Payment) — via Secure Proxy
import { SecureExternalClient } from "@vtex/payment-provider";

export class PspSecureClient extends SecureExternalClient {
  public async authorize(data: object, secureProxyUrl: string) {
    return this.http.post("/payments", data, {
      secureProxy: secureProxyUrl,
    } as any);
  }
}

// Cancel, Capture, Refund — direct API calls
import { ExternalClient } from "@vtex/api";

export class PspClient extends ExternalClient {
  public async capture(tid: string, amount: number) {
    return this.http.post(
      `/payments/${tid}/capture`,
      { amount },
      {
        headers: { "X-API-Key": "..." },
      },
    );
  }
}

誤った例

// WRONG — trying to use SecureExternalClient for all operations
async settle(request: SettlementRequest) {
  // secureProxyUrl does not exist on SettlementRequest — this is undefined
  await this.secureClient.capture(request.tid, request.value, request.secureProxyUrl)
}
制約:ヘッドレスストアフロントの BFF は、カードデータを vtexpayments.com.br にプロキシしてはなりません

ヘッドレスストアフロントでは、Send payments information 呼び出し(POST https://{account}.vtexpayments.com.br/api/pub/transactions/{tid}/payments)は、購入者のブラウザーまたはネイティブアプリから発行する必要があります。マーチャントの BFF(Node、Next.js のルートハンドラー、エッジ関数、Lambda、リバースプロキシ、その他のサーバーサイドコンポーネント)は、ブラウザーからカード情報を受け取ってはならず、リダクションを行う場合でも、サーバー側に appKey/appToken がある場合でも、転送されるのが「トークン化」されたフィールドだけに見える場合でも、Payment Gateway に転送してはなりません。

この制約は、Secure Proxy の基盤となる PCI の原則を拡張したものです。PCI に準拠していない環境では、カードデータを扱うことは許可されません。決済コネクターの場合、その環境は IO アプリです。ヘッドレスストアフロントの場合は、マーチャントの BFF です。宛先は異なります。Secure Proxy はコネクターからアクワイアラーへの経路を保護し、ブラウザーから vtexpayments.com.br へのパターンはストアフロントから Payment Gateway への経路を保護します。しかし、ルールは同じです。生のカードデータは、認定された範囲内にとどめてください。

重要な理由

ヘッドレスストアフロントを運営するマーチャントが PCI DSS レベル 1 認定を取得していることは、ほとんどありません。カード番号、カード名義人の氏名、CVV をマーチャントの BFF 経由でルーティングすると、BFF と、BFF が接続するすべてのシステム(アプリケーションログ、APM、リバースプロキシ、ロードバランサー、エラートラッカー)が PCI の適用範囲に含まれます。カードデータを扱う PCI 非準拠環境の運用は、PCI DSS 要件 3 および 4 に違反し、カードネットワークから月額 5,000 ドルから 100,000 ドル超の罰金、必須のフォレンジック調査費用、カード決済処理能力の喪失、集団訴訟リスク、一部の管轄区域での刑事責任につながる可能性があります。

VTEX Payment Gateway(vtexpayments.com.br)は PCI DSS レベル 1 認定を取得しています。ブラウザーから Payment Gateway への経路を使えば、カードデータは認定された範囲内にとどまり、マーチャントの BFF はカードデータのフローから完全に除外されます。Send payments information エンドポイントは、前の Place Order ステップで設定された購入者のセッション Cookie によって認証されます。この通信にマーチャントの認証情報は必要ありません。

検出方法

BFF/サーバーサイドのコード(server/、bff/、api/、ルートハンドラー、ミドルウェア、エッジ関数、Lambda)に次のいずれかが見つかった場合は、直ちに作業を中止してください。

  • BFF から https://*.vtexpayments.com.br/api/pub/transactions/.../payments へのリクエスト
  • ブラウザーから cardNumber、holderName、validationCode、csc、dueDate、または決済の fields 一式を受け取るハンドラー
  • Send payments information エンドポイント用に、サーバー上で vtexpayments.com.br を参照する「Payments client」/ExternalClient/axios インスタンス

BFF が公開すべきエンドポイントは /api/bff/order/place(transactionId、orderGroup、merchantName を返す)と /api/bff/order/process(カードデータを使わずに /api/checkout/pub/gatewayCallback/{orderGroup} を呼び出す)です。カード情報を vtexpayments.com.br に転送するエンドポイントは決して公開してはなりません。

正しい例

async function sendPaymentDataDirect(args: {
  account: string;
  transactionId: string;
  orderGroup: string;
  merchantName: string;
  paymentInformation: PaymentField[];
}): Promise<void> {
  const {
    account,
    transactionId,
    orderGroup,
    merchantName,
    paymentInformation,
  } = args;

  const response = await fetch(
    `https://${account}.vtexpayments.com.br/api/pub/transactions/${transactionId}/payments?orderId=${orderGroup}`,
    {
      method: "POST",
      credentials: "include",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
      },
      body: JSON.stringify(
        paymentInformation.map((p) => ({
          ...p,
          transaction: { id: transactionId, merchantName },
        })),
      ),
    },
  );

  if (!response.ok) {
    throw new Error(`Payment submission failed: ${response.status}`);
  }
}

誤った例

const VTEX_APP_KEY = process.env.VTEX_APP_KEY!;
const VTEX_APP_TOKEN = process.env.VTEX_APP_TOKEN!;

paymentRoutes.post("/", async (req: Request, res: Response) => {
  const { transactionId, orderGroup, paymentInformation } = req.body as {
    transactionId: string;
    orderGroup: string;
    paymentInformation: Array<{
      paymentSystem: number;
      installments: number;
      value: number;
      fields: {
        cardNumber: string;
        holderName: string;
        validationCode: string;
        dueDate: string;
      };
    }>;
  };

  const url = `https://${process.env.VTEX_ACCOUNT}.vtexpayments.com.br/api/pub/transactions/${transactionId}/payments?orderId=${orderGroup}`;
  const response = await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-VTEX-API-AppKey": VTEX_APP_KEY,
      "X-VTEX-API-AppToken": VTEX_APP_TOKEN,
    },
    body: JSON.stringify(paymentInformation),
  });

  res.json({ status: response.status });
});
制約:生のカードデータを保存してはなりません

コネクターは、完全なカード番号(PAN)、CVV/CSC、カード名義人の氏名、またはカードトークンの値を、データベース、ファイルシステム、キャッシュ、セッションストア、その他の永続的な媒体など、いかなる永続ストレージにも保存してはなりません。カードデータが存在してよいのは、リクエストのライフサイクル中のメモリ内に限られます。

重要な理由 生のカードデータの保存は PCI DSS 要件 3 に違反します。データ侵害が発生すると、顧客が不正利用の被害に遭うおそれがあります。その結果、カードネットワークから月額 5,000~100,000 ドルの罰金、必須のフォレンジック調査費用(50,000 ドル以上)、カード決済処理能力の喪失、集団訴訟、一部の管轄区域での刑事責任につながる可能性があります。

検出方法 コードがカード番号、CVV、カード名義人の氏名、またはトークンの値をデータベース、ファイル、キャッシュ(Redis、VBase)、その他の永続ストレージに書き込んでいる場合は、直ちに作業を中止してください。保存できるのは card.bin(先頭 6 桁)と card.numberLength のみです。

正しい例

async function createPaymentHandler(
  req: Request,
  res: Response,
): Promise<void> {
  const { paymentId, card, secureProxyUrl } = req.body;

  // Only store non-sensitive card metadata
  await paymentStore.save(paymentId, {
    paymentId,
    cardBin: card.bin, // First 6 digits — safe to store
    cardNumberLength: card.numberLength, // Length — safe to store
    cardExpMonth: card.expiration.month, // Expiration — safe to store
    cardExpYear: card.expiration.year,
    // DO NOT store: card.numberToken, card.holderToken, card.cscToken
  });

  // Use card tokens only in-memory for the Secure Proxy call
  const acquirerResult = await callAcquirerViaProxy(secureProxyUrl, card);

  // Return response — card data is now out of scope
  res.status(200).json(buildResponse(paymentId, acquirerResult));
}

誤った例

async function createPaymentHandler(
  req: Request,
  res: Response,
): Promise<void> {
  const { paymentId, card } = req.body;

  // CRITICAL PCI VIOLATION: Storing full card data in database
  await database.query(
    `INSERT INTO payments (payment_id, card_number, cvv, holder_name)
     VALUES ($1, $2, $3, $4)`,
    [paymentId, card.number, card.csc, card.holder],
  );
  // This single line can result in:
  // - $100K/month fines from card networks
  // - Mandatory forensic audit ($50K+)
  // - Loss of card processing ability
  // - Criminal liability
}
制約:機密性の高いカードデータをログに記録してはなりません

コネクターは、カード番号、CVV/CSC 値、カード名義人名、トークン値を、コンソール、ファイル、監視サービス、エラートラッカー、APM ツールなど、いかなるロギングシステムにも記録してはなりません。デバッグモードでも、開発環境でも同様です。

重要な理由 ログは通常、平文で保存され、長期間保持され、多くのチームメンバーがアクセスできます。ログ内のカードデータは PCI DSS 違反であり、データ侵害につながります。ログ集約サービス(Datadog、Splunk、CloudWatch)は複数のリージョンにデータを保存することがあり、侵害の影響範囲が拡大する可能性があります。

検出 コードに console.log、console.error、logger.info、logger.debug、または card.number、card.csc、card.holder、card.numberToken、card.holderToken、card.cscToken を含むロギング呼び出し、あるいはマスキングされていないリクエストボディ全体を含むロギング呼び出しがある場合は、直ちに作業を停止してください。ログに記録する前に、すべての機密フィールドをマスキングするか、記録対象から除外してください。

正しい例

async function createPaymentHandler(
  req: Request,
  res: Response,
): Promise<void> {
  const { paymentId, card, paymentMethod, value } = req.body;

  // Safe logging — only non-sensitive fields
  console.log("Processing payment", {
    paymentId,
    paymentMethod,
    value,
    cardBin: card?.bin, // First 6 digits only — safe
    cardNumberLength: card?.numberLength, // Safe
  });

  // NEVER log the full request body for payment requests
  // It contains card tokens or raw card data
}

function redactSensitiveFields(
  body: Record<string, unknown>,
): Record<string, unknown> {
  const redacted = { ...body };
  if (redacted.card && typeof redacted.card === "object") {
    const card = redacted.card as Record<string, unknown>;
    redacted.card = {
      bin: card.bin,
      numberLength: card.numberLength,
      expiration: card.expiration,
      // All other fields redacted
    };
  }
  return redacted;
}

誤った例

async function createPaymentHandler(
  req: Request,
  res: Response,
): Promise<void> {
  // CRITICAL PCI VIOLATION: Logging the entire request body
  // This includes card number, CVV, holder name, and/or token values
  console.log("Payment request received:", JSON.stringify(req.body));

  // ALSO WRONG: Logging specific card fields
  console.log("Card number:", req.body.card.number);
  console.log("CVV:", req.body.card.csc);
  console.log("Card holder:", req.body.card.holder);

  // ALSO WRONG: Logging tokens (they reference real card data)
  console.log("Card token:", req.body.card.numberToken);
}

推奨パターン

Secure Proxy のデータフロー:

1. Gateway → POST /payments (with secureProxyUrl + tokenized card data) → Connector
2. Connector → POST secureProxyUrl (tokens in body, X-PROVIDER-Forward-To: acquirer URL) → Gateway
3. Gateway replaces tokens with real card data → POST acquirer URL → Acquirer
4. Acquirer → response → Gateway → Connector
5. Connector → Create Payment response → Gateway

オーソリ後のデータフロー(Secure Proxy なし):

1. Gateway → POST /payments/{id}/settlements → Connector
2. Connector → POST acquirer API directly (tid + amount, no card data) → Acquirer
3. Acquirer → response → Connector
4. Connector → Settlement response → Gateway

Secure Proxy モードを検出する:

interface CreatePaymentRequest {
  paymentId: string;
  value: number;
  currency: string;
  paymentMethod: string;
  card?: {
    holder?: string; // Raw (PCI) or absent (Secure Proxy)
    holderToken?: string; // Token (Secure Proxy only)
    number?: string; // Raw (PCI) or absent (Secure Proxy)
    numberToken?: string; // Token (Secure Proxy only)
    bin: string; // Always present — first 6 digits
    numberLength: number; // Always present
    csc?: string; // Raw (PCI) or absent (Secure Proxy)
    cscToken?: string; // Token (Secure Proxy only)
    expiration: { month: string; year: string };
  };
  secureProxyUrl?: string; // Present when Secure Proxy is active
  secureProxyTokensURL?: string; // For custom token operations
  callbackUrl: string;
  miniCart: Record<string, unknown>;
}

function isSecureProxyActive(req: CreatePaymentRequest): boolean {
  return !!req.secureProxyUrl;
}

トークンまたは生の値を使ってアクワイアラー向けリクエストを作成する:

function buildAcquirerRequest(paymentReq: CreatePaymentRequest) {
  const card = paymentReq.card!;

  return {
    merchantOrderId: paymentReq.paymentId,
    payment: {
      // Use tokens if Secure Proxy, raw values if PCI-certified
      cardNumber: card.numberToken ?? card.number!,
      holder: card.holderToken ?? card.holder!,
      securityCode: card.cscToken ?? card.csc!,
      expirationDate: `${card.expiration.month}/${card.expiration.year}`,
      amount: paymentReq.value,
    },
  };
}

適切なヘッダーを設定して Secure Proxy 経由でアクワイアラーを呼び出す:

async function callAcquirerViaProxy(
  secureProxyUrl: string,
  acquirerRequest: object,
): Promise<AcquirerResponse> {
  const response = await fetch(secureProxyUrl, {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
      // X-PROVIDER-Forward-To tells the proxy where to send the request
      "X-PROVIDER-Forward-To": process.env.ACQUIRER_API_URL!,
      // Custom headers for the acquirer — prefix is stripped by the proxy
      "X-PROVIDER-Forward-MerchantId": process.env.ACQUIRER_MERCHANT_ID!,
      "X-PROVIDER-Forward-MerchantKey": process.env.ACQUIRER_MERCHANT_KEY!,
    },
    body: JSON.stringify(acquirerRequest),
  });

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(
      `Secure Proxy call failed: ${response.status} ${errorText}`,
    );
  }

  return response.json() as Promise<AcquirerResponse>;
}

// For PCI-certified environments, call acquirer directly
async function callAcquirerDirect(
  acquirerRequest: object,
): Promise<AcquirerResponse> {
  const response = await fetch(process.env.ACQUIRER_API_URL!, {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
      MerchantId: process.env.ACQUIRER_MERCHANT_ID!,
      MerchantKey: process.env.ACQUIRER_MERCHANT_KEY!,
    },
    body: JSON.stringify(acquirerRequest),
  });

  return response.json() as Promise<AcquirerResponse>;
}

安全なロギングユーティリティ:

function safePaymentLog(label: string, body: Record<string, unknown>): void {
  const safe = {
    paymentId: body.paymentId,
    paymentMethod: body.paymentMethod,
    value: body.value,
    currency: body.currency,
    orderId: body.orderId,
    hasCard: !!body.card,
    hasSecureProxy: !!body.secureProxyUrl,
    cardBin: (body.card as Record<string, unknown>)?.bin,
    // Everything else is intentionally omitted
  };

  console.log(label, JSON.stringify(safe));
}

よくある失敗パターン

  • PCI 非準拠環境でカードを直接扱う — Secure Proxy を使わずにアクワイアラー API を直接呼び出すことです。アクワイアラーは実際のカード番号ではなく、トークン(例:#vtex#token#d799bae#number#)を受け取り、取引を拒否します。生データを利用できる場合でも、PCI 非準拠環境から送信することは PCI DSS 違反です。
  • キャンセル/キャプチャ/返金に Secure Proxy を使う — これらの操作では secureProxyUrl は受け取らず、カードデータも含まれません。Secure Proxy 経由で送ろうとすると、URL が undefined であるため失敗します。オーソリ後の操作には、ExternalClient を使用して API を直接呼び出してください。
  • 完全なカード番号(PAN)を保存する — 「参照」や「照合」のために、完全なカード番号をデータベースに保存することです。このデータが一度侵害されるだけで、月額 10 万ドルの罰金、義務付けられたフォレンジック監査、カード処理能力の永久的な喪失につながる可能性があります。
  • デバッグのためにカード情報をログに記録する — 支払いの問題を調査するために console.log(req.body) や console.log(card) を追加し、削除し忘れることです。カードデータはログファイル、監視ダッシュボード、ログ集約サービスに残ります。これは開発環境でも PCI 違反です。
  • X-PROVIDER-Forward ヘッダーを削除する — X-PROVIDER-Forward-To ヘッダーを設定せずに Secure Proxy へリクエストを送信することです。プロキシはリクエストの転送先が分からず、エラーを返します。
  • トークン値を保存する — card.numberToken、card.holderToken、card.cscToken はトークンだから「安全」だと考えて、データベースやキャッシュに書き込むことです。トークンは実際のカードデータを参照するため、永続化してはなりません。

レビューチェックリスト

  • Create Payment リクエストに secureProxyUrl がある場合、コネクターはそれを使用していますか?
  • SecureExternalClient は Create Payment(オーソリ)のみに使用され、キャンセル/キャプチャ/返金には使用されていませんか?
  • オーソリ後の操作(キャンセル、キャプチャ、返金)では、ExternalClient を使って API を直接呼び出していますか?
  • Secure Proxy を呼び出す際、X-PROVIDER-Forward-To にアクワイアラーの API URL を設定していますか?
  • プロキシ経由で送信する場合、アクワイアラー固有のヘッダーに X-PROVIDER-Forward- を付けていますか?
  • データベースに保存されるのは card.bin、card.numberLength、card.expiration のみですか?
  • カード番号、CVV、カード名義人名、トークン値は、すべてのログ出力から除外されていますか?
  • 支払いリクエストデータを安全にログ出力するためのマスキングユーティリティがありますか?
  • コネクターは Secure Proxy(非 PCI)モードと直接接続(PCI 認定)モードの両方をサポートしていますか?
  • エラーレスポンスをログに記録する際、アクワイアラー向けリクエストボディ(トークンを含む)は記録されていませんか?

関連スキル

  • payment-provider-framework — PPF の IO 配線、2 クライアントパターン(SecureExternalClient + ExternalClient)、および outbound-access ポリシー
  • payment-provider-protocol — エンドポイントの契約とレスポンス形式
  • payment-idempotency — paymentId/requestId の冪等性とステートマシン
  • payment-async-flow — 非同期決済方法、コールバック、7 日間の再試行期間
  • payment-ppp-pos — POS のカード対面決済。Gateway の card は null で、Secure Proxy は適用されません

参考資料

コメント