惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

aimingoo的专栏
aimingoo的专栏
S
Securelist
博客园 - Franky
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
IT之家
IT之家
GbyAI
GbyAI
Microsoft Azure Blog
Microsoft Azure Blog
The Cloudflare Blog
云风的 BLOG
云风的 BLOG
N
News and Events Feed by Topic
AI
AI
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Schneier on Security
Schneier on Security
Attack and Defense Labs
Attack and Defense Labs
Vercel News
Vercel News
腾讯CDC
Google DeepMind News
Google DeepMind News
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
M
MIT News - Artificial intelligence
WordPress大学
WordPress大学
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
N
Netflix TechBlog - Medium
量子位
S
Schneier on Security
Hacker News: Ask HN
Hacker News: Ask HN
Cyberwarzone
Cyberwarzone
S
Security Affairs
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
N
News and Events Feed by Topic
T
Tenable Blog
PCI Perspectives
PCI Perspectives
MyScale Blog
MyScale Blog
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Cyber Attacks, Cyber Crime and Cyber Security
W
WeLiveSecurity
N
News | PayPal Newsroom
P
Proofpoint News Feed
O
OpenAI News
C
CERT Recently Published Vulnerability Notes
B
Blog
Cisco Talos Blog
Cisco Talos Blog
Microsoft Security Blog
Microsoft Security Blog
V
Visual Studio Blog
MongoDB | Blog
MongoDB | Blog
大猫的无限游戏
大猫的无限游戏
A
Arctic Wolf
Y
Y Combinator Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Spread Privacy
Spread Privacy

博客园 - warm3snow

Loop Engineering — 从"写 prompt"到"设计循环",AI Agent 的下一次进化 Hermes 多角色实战:我做了一个智能投研系统 Claude Code 为什么放弃 RAG:用 grep 干代码搜索的真实架构 Agent Eval 最佳实践:从 Benchmark 到生产监控的完整落地指南 如何打造一支 AI 原生团队:5-7 人小团队的四步搭建指南 一份来自 Karpathy 的 AI 编程 skill 用 Backtrader 拆解量化交易:一周内你会真正理解的七件事 AI 写代码比你快 10 倍,你还剩什么?——读 mattpocock/skills 多方协作平台的领域模型:为什么必须是"联盟、租户、项目、任务"四层 从龙虾到爱马仕:一个老虾农的"叛逃"实录 读懂加密市场(五):进阶之路 读懂加密市场(四):交易者的内功 读懂加密市场(三):合约、DEX 与工具 读懂加密市场(二):建立你的认知框架 读懂加密市场(一):加密市场的游戏规则 读懂加密市场:系列总览 一张图看懂巴菲特 48 年投资帝国:知识图谱效果全展示 用 AI 构建巴菲特投资知识图谱:从 48 封公开信到可交互的投资智慧网络 「同事.skill」爆火:当 AI 学会"炼化"你的同事 在线支付系列(五):统一支付网关架构设计 在线支付系列(三):Stripe & 信用卡——一件跨境商品的卡支付之旅 在线支付系列(二):支付宝 & 微信支付——一杯咖啡的扫码之旅 在线支付系列(一):一笔订单触发的支付之旅 Harness Engineering 最佳实践:从概念到落地的完整操作手册 Claude Code 源码泄露全复盘:51.2 万行代码裸奔,Anthropic 在同一个坑里摔了两次 数字货币交易所系列(四):DEX 深度解析——从 AMM 到链上订单簿,去中心化交易的技术与未来 数字货币交易所系列(三):安全、风控与监管——从 Mt.Gox 到 FTX,CEX 的信任是怎么建立(和崩塌)的? 数字货币交易所系列(二):CEX 核心技术架构——撮合引擎、钱包系统与充提链路的深度拆解
在线支付系列(四):PayPal——一位海外买家的安全支付之旅
warm3snow · 2026-04-04 · via 博客园 - warm3snow

在线支付系列(四):PayPal——一位海外买家的安全支付之旅

Emma 为什么不敢在陌生网站输入卡号?

伦敦的设计师 Emma 在 Instagram 上刷到了一个小众手工包品牌。她点进链接,一个设计简洁的独立站,标价 $89 的手工皮包看起来很不错。

她心动了,但手指悬在「Buy Now」按钮上方犹豫了三秒。

这个网站她从没听说过。页面底部没有什么知名品牌的 logo,About Us 写得语焉不详。如果她输入了信用卡号,万一是诈骗网站怎么办?被盗刷了怎么办?

然后她看到了支付选项里的 PayPal 按钮

"PayPal 我信得过。就算有问题,PayPal 会帮我追回来。"

她点击 PayPal 按钮,跳转到 PayPal 登录页面,输入邮箱和密码,确认支付。全程没有在这个陌生网站上输入任何银行卡信息

这就是 PayPal 存在的核心价值:在商户和买家之间建立信任层。对 Emma 来说,PayPal 是"安全感";对商户来说,PayPal 意味着能转化那些"不敢在陌生网站输卡号"的用户。

这篇文章,就从 Emma 的这笔 $89 说起。


一、PayPal 的商业逻辑

在讲技术之前,先理解 PayPal 在支付生态中的位置。

1.1 PayPal 不是信用卡,也不是银行

PayPal 的角色有点像支付宝——它是一个数字钱包。用户在 PayPal 绑定信用卡或银行账户,购物时只需要登录 PayPal 确认,不需要向商户暴露任何银行信息。

关键数据:

  • 4.31 亿活跃账户,主要在北美和欧洲
  • 覆盖 200+ 个国家/地区
  • 支持 25+ 种货币
  • 注册门槛极低——有个邮箱就能开始收款

1.2 PayPal 的费用结构

但安全感和低门槛是有代价的。PayPal 是四大支付渠道中费率最高的

Emma 这笔 $89 的交易:

  • 手续费:$89 × 3.49% + $0.49 = $3.60
  • 商户到账:$85.40

如果 Emma 后来申请了退款呢?

  • PayPal 不退原交易手续费——商户白亏 $3.59
  • 这是和 Stripe 最大的区别。Stripe 退款时会退手续费,PayPal 不会。

如果 Emma 发起了争议呢?

  • PayPal 收取 $15 标准争议费
  • 高争议量账户(争议率过高):升至 $30/笔
  • 如果交易未通过 PayPal 账户而直接走信用卡网络,退单费为 $20/笔

一句话总结 PayPal 的成本特点:费率最高、退款最贵(不退手续费),但门槛最低、买家信任度最高。适合需要转化"谨慎型"海外用户的场景。


二、PayPal 的技术架构

PayPal 的支付流程和 Stripe 不同。Stripe 的核心交互在前端(Stripe.js),而 PayPal 是一种"前端触发 + 后端完成"的模式。

2.1 整体流程

让我们跟着 Emma 的 $89 走一遍:

Emma 的浏览器              商户后端               PayPal 服务器
    │                       │                      │
    │ ① 点击 PayPal 按钮     │                      │
    │──────────────────────→│                      │
    │                       │ ② 创建 Order          │
    │                       │─────────────────────→│
    │                       │ ③ 返回 Order ID       │
    │                       │←─────────────────────│
    │ ④ 返回 Order ID       │                      │
    │←──────────────────────│                      │
    │                       │                      │
    │ ⑤ PayPal SDK 弹出登录窗口                     │
    │─────────────────────────────────────────────→│
    │ ⑥ Emma 登录 PayPal 确认支付                   │
    │─────────────────────────────────────────────→│
    │                       │                      │
    │ ⑦ PayPal 返回 "APPROVED"                     │
    │←─────────────────────────────────────────────│
    │                       │                      │
    │ ⑧ 告诉后端去 Capture   │                      │
    │──────────────────────→│                      │
    │                       │ ⑨ Capture Order       │
    │                       │─────────────────────→│
    │                       │ ⑩ 返回 "COMPLETED"    │
    │                       │←─────────────────────│
    │                       │ ⑪ 更新订单状态         │

注意第 ⑨ 步:和 Stripe 不同,PayPal 的扣款需要后端主动调用 Capture。用户在 PayPal 弹窗中确认后,钱还没有真正扣——你需要再调一次 API 来"捕获"这笔款。

这其实就是信用卡的"授权-捕获"分离思想——PayPal 也借鉴了这个设计。

2.2 认证方式:OAuth 2.0

PayPal API 使用 OAuth 2.0 认证。每次调用 API 前,你需要用 Client ID 和 Secret 换取一个 Access Token。

这和支付宝/微信的签名机制完全不同。支付宝/微信是每个请求都签名,PayPal 是先拿 token 再带着 token 请求。


三、动手:PayPal 对接全流程

3.1 接入准备

第一步:访问 developer.paypal.com 登录
    ▼
第二步:创建 App(My Apps & Credentials → Create App)
    ▼
第三步:获取 Client ID 和 Secret
    ▼
第四步:创建沙盒测试账号(一个买家账号 + 一个商家账号)
    ▼
第五步:配置 Webhook Endpoint
密钥 用途 安全级别
Client ID 标识应用,前端 SDK + 后端 OAuth 可公开
Secret 后端 API 认证 🔴 绝密
Webhook ID 验证 Webhook 来源 🔴 绝密

3.2 OAuth 2.0:先拿 Token

每次调用 PayPal API 之前,你都需要先获取 Access Token。Token 有效期通常是几小时,过期后重新获取即可。

import base64, requests, os

PAYPAL_CLIENT_ID = os.getenv("PAYPAL_CLIENT_ID")
PAYPAL_SECRET = os.getenv("PAYPAL_SECRET")
PAYPAL_BASE = "https://api-m.sandbox.paypal.com"  # 沙盒环境

def get_access_token():
    """用 Client ID + Secret 换取 Access Token"""
    auth = base64.b64encode(
        f"{PAYPAL_CLIENT_ID}:{PAYPAL_SECRET}".encode()
    ).decode()
    
    resp = requests.post(
        f"{PAYPAL_BASE}/v1/oauth2/token",
        headers={
            "Authorization": f"Basic {auth}",
            "Content-Type": "application/x-www-form-urlencoded",
        },
        data={"grant_type": "client_credentials"}
    )
    return resp.json()["access_token"]

和支付宝/微信的区别:支付宝和微信是"每个请求都用私钥签名",PayPal 是"先用凭证换 token,然后带着 token 请求"。两种方式都安全,但 OAuth 的方式更符合 REST API 的风格。

3.3 前端:Smart Payment Buttons

PayPal 提供了一套官方的前端 SDK——Smart Payment Buttons。它会根据用户所在地区自动显示最合适的 PayPal 按钮样式。

<!-- 引入 PayPal SDK -->
<script src="https://www.paypal.com/sdk/js?client-id=YOUR_CLIENT_ID&currency=USD"></script>

<!-- PayPal 按钮容器 -->
<div id="paypal-button-container"></div>
paypal.Buttons({
    // ① 用户点击按钮后,调用你的后端创建订单
    createOrder: async () => {
        const res = await fetch('/api/pay/paypal/create-order', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                amount: '89.00',
                currency: 'USD',
                description: 'Handmade Leather Bag',
                order_id: 'ORD_20260404002',
            })
        });
        const data = await res.json();
        return data.paypal_order_id;  // 返回给 PayPal SDK
    },
    
    // ② Emma 在 PayPal 弹窗中确认后,调用后端去 Capture
    onApprove: async (data) => {
        const res = await fetch('/api/pay/paypal/capture-order', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ paypal_order_id: data.orderID })
        });
        const result = await res.json();
        if (result.status === 'COMPLETED') {
            alert('支付成功!🎉');
        }
    },
    
    // ③ 出错了
    onError: (err) => {
        console.error('PayPal Error:', err);
        alert('支付出现问题,请重试');
    },
    
    // ④ Emma 在 PayPal 弹窗中取消了
    onCancel: () => {
        alert('支付已取消');
    },
}).render('#paypal-button-container');

3.4 后端:创建订单 + 捕获

from fastapi import FastAPI, Request, HTTPException
import requests as http_requests
import os

app = FastAPI()
PAYPAL_BASE = os.getenv("PAYPAL_BASE_URL",
                         "https://api-m.sandbox.paypal.com")

@app.post("/api/pay/paypal/create-order")
async def create_order(request: Request):
    """Emma 点击 PayPal 按钮后,后端创建一个 PayPal 订单"""
    body = await request.json()
    token = get_access_token()
    
    order_data = {
        "intent": "CAPTURE",  # 意图:直接捕获(不是先授权再捕获)
        "purchase_units": [{
            "reference_id": body["order_id"],      # 你的订单号
            "description": body.get("description", ""),
            "amount": {
                "currency_code": body.get("currency", "USD"),
                "value": body["amount"],  # 金额——注意是元(字符串),不是分!
            }
        }],
        "application_context": {
            "brand_name": "Your Store",
            "user_action": "PAY_NOW",
        }
    }
    
    resp = http_requests.post(
        f"{PAYPAL_BASE}/v2/checkout/orders",
        headers={
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json",
        },
        json=order_data
    )
    
    result = resp.json()
    if resp.status_code == 201:
        # 保存你的订单号和 PayPal 订单号的映射关系
        await save_order_mapping(body["order_id"], result["id"])
        return {
            "paypal_order_id": result["id"],
            "status": result["status"],  # 此时应该是 "CREATED"
        }
    raise HTTPException(400, detail=result)


@app.post("/api/pay/paypal/capture-order")
async def capture_order(request: Request):
    """Emma 在 PayPal 弹窗中确认后,后端来捕获这笔款"""
    body = await request.json()
    token = get_access_token()
    
    resp = http_requests.post(
        f"{PAYPAL_BASE}/v2/checkout/orders/"
        f"{body['paypal_order_id']}/capture",
        headers={
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json",
        },
    )
    
    result = resp.json()
    
    if result["status"] == "COMPLETED":
        # 提取支付详情
        capture = result["purchase_units"][0]["payments"]["captures"][0]
        
        await update_order_status(
            order_id=result["purchase_units"][0]["reference_id"],
            status="PAID",
            transaction_id=capture["id"],
            amount=capture["amount"]["value"],
            currency=capture["amount"]["currency_code"],
        )
    
    return {"status": result["status"]}

注意金额单位:PayPal 和支付宝一样用元(字符串),不是分。"89.00" 而不是 8900。这一点和 Stripe/微信相反。

3.5 Webhook 验签

PayPal 的 Webhook 验签方式比较独特——它不是你自己算签名比对,而是调用 PayPal 的 API 来验证

@app.post("/api/pay/paypal/webhook")
async def paypal_webhook(request: Request):
    """PayPal Webhook 处理"""
    body = await request.json()
    headers = dict(request.headers)
    
    # 1. 验签——调用 PayPal 官方 API 验证
    token = get_access_token()
    verify_payload = {
        "auth_algo": headers.get("paypal-auth-algo"),
        "cert_url": headers.get("paypal-cert-url"),
        "transmission_id": headers.get("paypal-transmission-id"),
        "transmission_sig": headers.get("paypal-transmission-sig"),
        "transmission_time": headers.get("paypal-transmission-time"),
        "webhook_id": os.getenv("PAYPAL_WEBHOOK_ID"),
        "webhook_event": body,
    }
    
    verify_resp = http_requests.post(
        f"{PAYPAL_BASE}/v1/notifications/verify-webhook-signature",
        headers={
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json",
        },
        json=verify_payload
    )
    
    if verify_resp.json().get("verification_status") != "SUCCESS":
        raise HTTPException(400, "Invalid webhook signature")
    
    # 2. 业务处理
    event_type = body["event_type"]
    resource = body["resource"]
    
    if event_type == "PAYMENT.CAPTURE.COMPLETED":
        # 幂等检查 + 更新订单状态
        await handle_capture_completed(resource)
    elif event_type == "PAYMENT.CAPTURE.REFUNDED":
        await handle_refund(resource)
    elif event_type == "CUSTOMER.DISPUTE.CREATED":
        # 收到争议通知!需要及时处理
        await handle_dispute_created(resource)
    
    return {"status": "ok"}

四大渠道的验签方式对比:

渠道 验签方式 复杂度
支付宝 用支付宝公钥证书验证 RSA 签名 ★★★☆
微信支付 用平台证书验 RSA 签名 + AES-GCM 解密 ★★★★
Stripe 用 Webhook Secret 算 HMAC-SHA256 比对 ★★☆☆
PayPal 调用 PayPal API 验证(把签名信息转发给 PayPal) ★★★☆

四、PayPal 的争议保护:Emma 的安全网

Emma 之所以敢在陌生网站用 PayPal 付款,核心原因是 PayPal 的买家保护计划(Buyer Protection)

4.1 买家保护怎么运作?

如果 Emma 收到的手工包和描述不符(比如图片是真皮,收到的是人造革),或者根本没收到包裹,她可以在 PayPal 发起争议。

Emma 发起争议 → PayPal 冻结 $89 → 商户有 20 天响应
    │
    ├── 商户和 Emma 协商解决 → 结案
    │
    └── 协商失败 → PayPal 介入裁决
         │
         ├── 判商户赢 → 解冻 $89,退还给商户
         └── 判 Emma 赢 → $89 退给 Emma,商户承担损失 + $15 争议费

4.2 对商户的影响

作为商户,PayPal 的争议保护是一把双刃剑:

好处:它让更多"谨慎型"用户愿意下单。没有 PayPal,Emma 可能直接关掉页面——你连成交的机会都没有。

风险:PayPal 的裁决总体上倾向于保护买家。如果你没有充分的发货证明和交易记录,很可能败诉。

建议

  • 每笔订单都保存发货证明和物流追踪号
  • 使用 PayPal 的"卖家保护"功能——寄送到 PayPal 交易详情中的地址
  • 收到争议后尽快响应(20 天内),不要拖
  • 争议率控制在 1% 以下,否则 PayPal 会采取限制措施

五、PayPal 的特殊之处

对接过支付宝、微信、Stripe 之后,PayPal 有一些独特的特点值得单独说明。

5.1 退款政策:手续费不退

这是 PayPal 和其他三个渠道最大的区别。当你退款时:

渠道 退款后原交易手续费
支付宝 ✅ 退还
微信支付 ✅ 退还
Stripe ✅ 退还
PayPal ❌ 不退还

也就是说,如果 Emma 退了那笔 $89 的订单,商户不仅拿不到钱,还要亏 $3.59 的手续费。如果你的退货率很高,这笔成本会非常可观。

5.2 PayPal 支持部分退款

好消息是 PayPal 支持部分退款,最多 10 次。如果 Emma 对包的颜色不满意但不想退货,你可以退 $20 表示歉意,而不是全额退款。

5.3 汇率加价

如果你的结算币种和用户的支付币种不同,PayPal 会自动做货币转换——但它会在中间汇率上加 3~4% 的差价。这是跨境使用 PayPal 时一个经常被忽略的隐性成本。

5.4 退款时限

PayPal 的退款时限是 180 天(6 个月)。相比之下支付宝是 3 个月,微信是 1 年,Stripe 没有限制。


六、沙盒测试

PayPal 的沙盒环境和生产环境完全隔离。你需要在 developer.paypal.com 创建测试账号。

6.1 测试账号

PayPal 沙盒会为你自动创建两个测试账号:

  • Business 账号:模拟商户,用来收款
  • Personal 账号:模拟买家(就像 Emma),用来付款

6.2 测试卡号

如果你需要测试"用信用卡支付而不是 PayPal 余额"的场景:

卡组织 卡号 用途
Visa 4032039317984658 正常支付
Mastercard 5425233430109903 正常支付
Visa 4687380000000002 触发拒绝

6.3 沙盒 vs 生产

切换到生产环境只需要做两件事:

  1. 把 API 地址从 api-m.sandbox.paypal.com 改成 api-m.paypal.com
  2. 把沙盒的 Client ID / Secret 换成生产的

七、什么时候该选 PayPal?

经过这篇文章的学习,让我们总结一下 PayPal 的适用场景。

强烈推荐接入 PayPal 的场景

  • 你的目标用户在北美或欧洲
  • 你是一个新品牌/小品牌,用户对你的网站缺乏信任
  • 你卖的是数字商品或服务(PayPal 的即时交付验证机制成熟)
  • 你需要最快速度上线收款(注册即用,不需要企业审核)

可以不接 PayPal 的场景

  • 你只做中国市场(用户几乎不用 PayPal)
  • 你的退款率很高(手续费不退的成本太大)
  • 你已经有了知名品牌背书,用户信任度足够高
  • 你的客单价很低(固定费用 $0.49 的比例太高)

本系列文章导读

篇目 标题 你将学到
第 1 篇 概览篇——一笔订单触发的支付之旅 四方模型、支付生命周期、市场格局、成本分析、选型决策
第 2 篇 支付宝 & 微信支付——一杯咖啡的扫码之旅 签名机制、回调处理、AES-GCM 解密、完整对接代码
第 3 篇 Stripe & 信用卡——一件跨境商品的卡支付之旅 Payment Intents、3D Secure、PCI DSS、前后端代码
第 4 篇 PayPal——一位海外买家的安全支付之旅(本文) OAuth 认证、Smart Buttons、争议保护、Webhook
第 5 篇 统一支付网关——当四条河流汇入一片海 三层架构、策略模式、幂等设计、对账补偿

参考来源


欢迎关注公众号 coft,获取更多深度技术文章。下一篇也是最后一篇——当你同时接了支付宝、微信、Stripe、PayPal 之后,四套签名机制、四种回调格式、四个退款接口……维护成本开始指数增长。怎么办?答案是搭一个统一支付网关。