> ## Documentation Index
> Fetch the complete documentation index at: https://waffo.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 冪等性

> 冪等キーを使用して二重課金を防止し、決済の安全性を確保します。

export const DoAndDont = ({type = "correct", title, items = []}) => {
  const isCorrect = type === "correct";
  const defaultTitle = isCorrect ? "Correct approach" : "Incorrect approach";
  const label = title ?? defaultTitle;
  const renderText = text => {
    const parts = text.split(/(`[^`]+`)/g);
    return parts.map((part, i) => {
      if (part.startsWith("`") && part.endsWith("`")) {
        return <code key={i} className="bg-slate-100 dark:bg-neutral-800 px-2 rounded text-slate-900 dark:text-slate-100 text-[13px] font-mono">
            {part.slice(1, -1)}
          </code>;
      }
      return part;
    });
  };
  const leftPanelStyle = {
    background: isCorrect ? "repeating-linear-gradient(-45deg, var(--do-stripe-a, #ccfbf1), var(--do-stripe-a, #ccfbf1) 4px, var(--do-stripe-b, #f0fdfa) 4px, var(--do-stripe-b, #f0fdfa) 12px)" : "repeating-linear-gradient(-45deg, var(--dont-stripe-a, #fee2e2), var(--dont-stripe-a, #fee2e2) 4px, var(--dont-stripe-b, #fef2f2) 4px, var(--dont-stripe-b, #fef2f2) 12px)",
    borderRight: isCorrect ? "1px solid var(--do-divider, #14b8a6)" : "1px solid var(--dont-divider, #f87171)"
  };
  return <div className={`do-and-dont-card flex rounded-xl border overflow-hidden bg-white dark:bg-neutral-950 ${isCorrect ? "border-teal-500 dark:border-teal-600" : "border-red-400 dark:border-red-700"}`}>
      <style>{`
        .dark .do-and-dont-card {
          --dont-stripe-a: rgba(127, 29, 29, 0.38);
          --dont-stripe-b: rgba(69, 10, 10, 0.32);
          --dont-divider: #b91c1c;
          --do-stripe-a: rgba(20, 184, 166, 0.16);
          --do-stripe-b: rgba(20, 83, 45, 0.18);
          --do-divider: #0f766e;
        }
      `}</style>
      {}
      <div className={`flex items-start justify-center w-14 shrink-0 pt-4 ${isCorrect ? "dark:bg-teal-950/30" : "dark:bg-red-950/20"}`} style={leftPanelStyle}>
        {isCorrect ? <svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
            <path d="M16 26C21.5228 26 26 21.5228 26 16C26 10.4772 21.5228 6 16 6C10.4772 6 6 10.4772 6 16C6 21.5228 10.4772 26 16 26ZM11 16.295L12.0093 15.2857L14.5714 17.8471L19.9893 12.4286L21 13.4393L14.5714 19.8671L11 16.295Z" fill="#38C793" />
          </svg> : <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" fill="none">
            <path d="M26 16C26 10.4772 21.5229 6 16 6C10.4772 6 6 10.4771 6 16C6 21.5228 10.4771 26 16 26C21.5228 26 26 21.5229 26 16ZM12.6727 11.6393L16 14.9868L19.3273 11.6393L20.3405 12.6464L17.0071 16L20.3405 19.3536L19.3273 20.3607L16 17.0132L12.6727 20.3607L11.6595 19.3536L14.9929 16L11.6595 12.6464L12.6727 11.6393Z" fill="#E93544" />
          </svg>}
      </div>

      {}
      <div className="flex-1 p-4 bg-white dark:bg-neutral-900/60">
        <p className="font-semibold text-base text-slate-900 dark:text-slate-100 mb-3 mt-0">
          {label}
        </p>
        <ul className="flex flex-col gap-3 m-0 list-none p-0">
          {items.map((item, i) => <li key={i} className="flex gap-2 items-start text-slate-600 dark:text-slate-300 text-sm leading-5">
              <span>{renderText(item)}</span>
            </li>)}
        </ul>
      </div>
    </div>;
};

冪等性とは、同じリクエストを複数回実行しても、1 回だけ実行した場合と同じ結果になることを保証する性質です。決済のシナリオでは、ネットワークタイムアウトによるリトライに起因する二重課金を防ぎます。

## 冪等キー

Waffo では `paymentRequestId`(決済)と `subscriptionRequest`(サブスクリプション)を冪等キーとして使用します。

### ルール

* 最大長: 32 文字
* 新しいリクエストごとに一意の値を生成
* **リクエスト送信前にデータベースへ永続化すること**
* リトライ時は **元の冪等キーを再利用** し、新しいキーを生成しないこと

## パターン

```typescript theme={null}
// 1. 32文字以内の一意な冪等キーを生成
const paymentRequestId = generateUniqueRequestId();

// 2. 永続化(リクエスト送信前に!)
await db.order.create({ paymentRequestId, status: 'PENDING' });

// 3. リクエスト送信
try {
  const response = await waffo.order().create({ paymentRequestId, ... });
} catch (e) {
  if (e instanceof WaffoUnknownStatusException) {
    // 4. ステータス不明: create を再試行せず、照会すること
    const inquiry = await waffo.order().inquiry({ paymentRequestId });
  }
}
```

## よくある間違い

<div className="flex flex-col gap-3">
  <DoAndDont
    type="incorrect"
    items={[
  "ネットワークタイムアウト後に新しい `paymentRequestId` を生成してリトライする。",
  "リクエスト送信後に初めて `paymentRequestId` をデータベースに書き込む。",
  "ステータス不明を受け取った直後に注文をクローズする。",
]}
  />

  <DoAndDont
    type="correct"
    items={[
  "元の `paymentRequestId` を使用し、まず照会してから後続処理を決定する。",
  "リクエスト送信前に永続化する。",
  "照会するか、Webhook を待機する。",
]}
  />
</div>
