When to Use
User needs to integrate PayPal REST API for payments, subscriptions, or payouts. Agent handles checkout flows, webhook verification, OAuth token management, and dispute workflows.
Quick Reference
| Topic | File |
|---|
| Code patterns | INLINECODE0 |
| Webhook events |
webhooks.md |
Core Rules
1. Environment URLs are Different
- - Sandbox: INLINECODE2
- Production: INLINECODE3
- Ask which environment BEFORE generating code
- Credentials are environment-specific — never mix
2. OAuth Token Management
// Token expires ~8 hours — handle refresh
const getToken = async () => {
const res = await fetch('https://api.paypal.com/v1/oauth2/token', {
method: 'POST',
headers: {
'Authorization': `Basic ${Buffer.from(`${clientId}:${secret}`).toString('base64')}`,
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'grant_type=client_credentials'
});
return res.json(); // { access_token, expires_in }
};
Never hardcode tokens. Implement refresh logic.
3. Webhook Verification is Mandatory
PayPal webhooks MUST be verified via API call — not simple HMAC:
CODEBLOCK1
4. CAPTURE vs AUTHORIZE — Ask First
| Intent | Behavior |
|---|
| INLINECODE4 | Charges immediately on approval |
| INLINECODE5 |
Reserves funds, capture later (up to 29 days) |
Changing intent after integration breaks the entire flow.
5. Server-Side Validation — Never Trust Client
CODEBLOCK2
6. Idempotency in Webhooks
PayPal may send the same webhook multiple times:
CODEBLOCK3
7. Currency Decimal Rules
Some currencies have NO decimal places:
| Currency | Decimals | Example |
|---|
| USD, EUR | 2 | "10.50" |
| JPY, TWD |
0 | "1050" (NOT "1050.00") |
Sending "10.50" for JPY = API error.
Common Traps
- - IPN vs Webhooks — IPN is legacy. Use Webhooks for new integrations. Never mix.
- Order states — CREATED → APPROVED → COMPLETED (or VOIDED). Handle ALL states, not just happy path.
- Decimal confusion — PayPal uses strings for amounts ("10.50"), not floats. Some currencies forbid decimals.
- Sandbox rate limits — Lower than production. Don't assume prod will fail the same way.
- Payout vs Payment — Payouts API is separate. Don't confuse sending money (Payouts) with receiving (Orders).
使用场景
用户需要集成PayPal REST API实现支付、订阅或付款。代理负责处理结账流程、Webhook验证、OAuth令牌管理和争议处理工作流。
快速参考
| 主题 | 文件 |
|---|
| 代码模式 | patterns.md |
| Webhook事件 |
webhooks.md |
核心规则
1. 环境URL不同
- - 沙箱环境:api.sandbox.paypal.com
- 生产环境:api.paypal.com
- 生成代码前务必确认使用哪个环境
- 凭证与环境绑定——切勿混用
2. OAuth令牌管理
javascript
// 令牌约8小时过期——需处理刷新
const getToken = async () => {
const res = await fetch(https://api.paypal.com/v1/oauth2/token, {
method: POST,
headers: {
Authorization: Basic ${Buffer.from(${clientId}:${secret}).toString(base64)},
Content-Type: application/x-www-form-urlencoded
},
body: grant
type=clientcredentials
});
return res.json(); // { access
token, expiresin }
};
切勿硬编码令牌。需实现刷新逻辑。
3. Webhook验证是强制要求
PayPal Webhook必须通过API调用验证——而非简单HMAC:
javascript
// POST /v1/notifications/verify-webhook-signature
const verification = await fetch(https://api.paypal.com/v1/notifications/verify-webhook-signature, {
method: POST,
headers: { Authorization: Bearer ${token}, Content-Type: application/json },
body: JSON.stringify({
auth_algo: headers[paypal-auth-algo],
cert_url: headers[paypal-cert-url],
transmission_id: headers[paypal-transmission-id],
transmission_sig: headers[paypal-transmission-sig],
transmission_time: headers[paypal-transmission-time],
webhook
id: WEBHOOKID,
webhook_event: body
})
});
// verification_status === SUCCESS
4. CAPTURE与AUTHORIZE——需先确认
| 意图 | 行为 |
|---|
| CAPTURE | 批准后立即扣款 |
| AUTHORIZE |
预授权资金,后续扣款(最长29天) |
集成后更改意图会破坏整个流程。
5. 服务端验证——绝不信任客户端
javascript
// 客户端批准后,在服务端验证再执行
const order = await fetch(https://api.paypal.com/v2/checkout/orders/${orderId}, {
headers: { Authorization: Bearer ${token} }
}).then(r => r.json());
// 验证以下所有项:
if (order.status !== APPROVED) throw new Error(未批准);
if (order.purchase_units[0].amount.value !== expectedAmount) throw new Error(金额不匹配);
if (order.purchaseunits[0].amount.currencycode !== expectedCurrency) throw new Error(币种不匹配);
if (order.purchaseunits[0].payee.merchantid !== YOURMERCHANTID) throw new Error(商户错误);
6. Webhook幂等性
PayPal可能多次发送相同Webhook:
javascript
const processed = await db.webhooks.findOne({ eventId: body.id });
if (processed) return res.status(200).send(已处理);
await db.webhooks.insert({ eventId: body.id, processedAt: new Date() });
// 现在处理事件
7. 货币小数规则
部分货币无小数位:
| 货币 | 小数位 | 示例 |
|---|
| USD, EUR | 2 | 10.50 |
| JPY, TWD |
0 | 1050(非1050.00) |
为JPY发送10.50会导致API错误。
常见陷阱
- - IPN与Webhooks — IPN已过时。新集成请使用Webhooks。切勿混用。
- 订单状态 — CREATED → APPROVED → COMPLETED(或VOIDED)。处理所有状态,不仅限于正常路径。
- 小数混淆 — PayPal使用字符串表示金额(10.50),而非浮点数。部分货币禁止小数。
- 沙箱速率限制 — 低于生产环境。不要假设生产环境会以相同方式失败。
- 付款与支付 — 付款API是独立的。不要混淆发送资金(付款)与接收资金(订单)。