> ## 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>;
};

幂等性确保当同一个请求被执行多次时，其结果与仅执行一次相同。在支付场景中，这可以防止因网络超时触发重试而导致的重复扣款。

## 幂等键

Waffo 使用 `paymentRequestId`（支付）和 `subscriptionRequest`（订阅）作为幂等键。

### 规则

* 最大长度：32 个字符
* 每个新请求必须生成唯一值
* **必须在发送请求之前持久化到数据库**
* 重试时，**复用原始幂等键**；不要生成新的

## 模式

```typescript theme={null}
// 1. 生成不超过 32 字符的唯一幂等键
const paymentRequestId = generateUniqueRequestId();

// 2. Persist (before sending the request!)
await db.order.create({ paymentRequestId, status: 'PENDING' });

// 3. Send the request
try {
  const response = await waffo.order().create({ paymentRequestId, ... });
} catch (e) {
  if (e instanceof WaffoUnknownStatusException) {
    // 4. Unknown status: inquire, do not retry create
    const inquiry = await waffo.order().inquiry({ paymentRequestId });
  }
}
```

## 常见错误

<div className="flex flex-col gap-3">
  <DoAndDont
    type="incorrect"
    title="错误做法"
    items={[
  "网络超时后生成新的 `paymentRequestId` 并重试。",
  "仅在请求发送后才将 `paymentRequestId` 写入数据库。",
  "收到未知状态后立即关闭订单。",
]}
  />

  <DoAndDont
    type="correct"
    title="正确做法"
    items={[
  "使用原始 `paymentRequestId`，先发起查询（inquiry），再决定后续处理。",
  "在发送请求之前先持久化。",
  "发起查询（inquiry）或等待 Webhook。",
]}
  />
</div>
