# 如何为 Flask API 添加结账功能

> Flask 应用支付处理集成指南，涵盖结账端点、Webhook 路由、订阅管理和 MoR（名义商户）合规。
- **Author**: Ayush Agarwal
- **Published**: 2026-03-29
- **Category**: Payments, Developer Tools, How-To
- **URL**: https://dodopayments.com/blogs/zh/add-checkout-flask-api

---

为 Flask API 添加结账功能，归根结底只需要两个路由：一个用于创建结账会话并返回其 URL 的 POST 端点，以及一个用于验证签名，并在付款成功后授予访问权限的 webhook 端点。其他所有工作，包括税费计算和卡片数据处理，都可以留在 Flask 应用之外。

对于构建 API 优先产品的开发者来说，Flask 仍然是首选微框架。它的极简理念让你可以从单文件原型扩展到复杂的微服务，同时无需承担大型框架的额外开销。然而，在通过这些 API 实现商业化时，相关文档往往不够完善。大多数支付集成指南都假定你正在构建带前端的传统服务端渲染应用，因此 API 开发者不得不自行解决无头结账、webhook 验证和全球税务合规等问题。

将 Dodo Payments 与 Flask 集成，可以通过提供 MoR（名义商户）层来解决这些问题，并处理全球支付中最繁重的工作。无论你销售的是 API 访问权限、SaaS 订阅，还是数字下载产品，将 Flask 的轻量级路由与 Dodo 的 Python SDK 结合起来，都能在几分钟内构建出可用于生产环境的计费系统。

## 为什么选择 Flask 和 Dodo Payments？

Flask 的设计理念就是便于扩展。它不会规定你应该如何处理数据库连接或身份验证，更不会强制你使用某个特定的支付服务商。这种灵活性使它非常适合与现代支付技术栈集成。

> 支付集成应该耗时数小时，而不是数周。如果开发者需要阅读数天文档才能写下第一行代码，那么这个平台就不是开发者优先，而是对开发者不友好。
>
> \- Ayush Agarwal，Dodo Payments 联合创始人兼 CPTO

### 轻量级 Webhook 路由

Webhook 是任何现代支付集成的基础。Flask 的路由系统让你可以非常简单地创建专用端点，用于接收支付服务商发送的异步通知。由于 Flask 默认没有复杂的中间件栈，你可以完全控制签名验证和传入 JSON 负载的处理方式。

### 面向无头 API 的支付链接

如果你正在构建纯后端服务或 CLI 工具，可能不想开发自定义结账界面。Dodo Payments 允许你直接从 Flask 路由生成托管支付链接。你可以将用户重定向到由 Dodo 托管的安全结账页面，并在交易完成后通过成功 URL 接收用户。这意味着你无需在自己的服务器上管理复杂的前端状态或 PCI 合规。

### 原生 Python SDK

Dodo Payments Python SDK 针对现代 Python 开发模式构建，同时支持同步和异步执行，因此既兼容标准 Flask，也兼容 Flask-SocketIO 或 Quart 等支持异步的扩展。SDK 提供完整的类型提示，这意味着你的 IDE 可以在代码运行前帮助你发现错误。

### MoR（名义商户）合规

全球软件销售中最大的难题不是代码，而是合规。向 220 多个国家和地区的客户销售产品，意味着要应对 180 个不同的税收管辖区。作为 MoR（名义商户），Dodo Payments 负责销售税和增值税的计算、收取与汇缴。当你将 Dodo 集成到 Flask API 后，你添加的不只是一个"支付"按钮，而是将整个全球税务与合规部门外包出去。

## 支付流程

你的 Flask 应用、客户和 Dodo Payments 之间的数据流程如下所示。如果你使用其他 Python 框架，同样的流程也适用于[使用 FastAPI 接受付款](https://dodopayments.com/blogs/accept-payments-fastapi)或[在 Django 应用中接受付款](https://dodopayments.com/blogs/accept-payments-django-app)。

```mermaid
flowchart TD
    A[Customer] -->|Request Access| B[Flask API]
    B -->|Create Checkout Session| C[Dodo Payments API]
    C -->|Return Checkout URL| B
    B -->|Redirect| D[Hosted Checkout Page]
    D -->|Complete Payment| E[Dodo Payments MoR]
    E -->|Webhook Notification| B
    B -->|Update Database| F[User Access Granted]
    E -->|Redirect Success| A
```

## 第 1 步：安装 SDK

首先，你需要安装 Dodo Payments SDK。我们建议使用虚拟环境，以隔离项目依赖。

```bash
pip install dodopayments python-dotenv
```

我们还会安装 `python-dotenv`，以安全地管理 API 密钥。在项目根目录创建 `.env` 文件，并添加你的 Dodo Payments API 密钥。

```text
DODO_PAYMENTS_API_KEY=your_live_or_test_key_here
APP_URL=your_app_base_url
FLASK_ENV=development
```

## 第 2 步：初始化客户端

在 Flask 应用中，你需要初始化 Dodo Payments 客户端。你可以在应用顶层完成此操作，也可以在工厂函数中完成。

```python
import os
from flask import Flask, request, jsonify, redirect
from dodopayments import DodoPayments
from dotenv import load_dotenv

load_dotenv()

app = Flask(__name__)

# Base URL of your own app, used for return URLs

APP_URL = os.environ["APP_URL"]

# Initialize the client

# It automatically looks for DODO_PAYMENTS_API_KEY in environment variables

client = DodoPayments(
    environment="test_mode" if os.getenv("FLASK_ENV") == "development" else "live_mode"
)
```

## 第 3 步：创建结账端点

现在，让我们创建一个用于生成结账会话的路由。当用户想要购买订阅或一次性产品时，就会调用这个端点。

```python
@app.route('/buy/<product_id>', methods=['POST'])
def create_checkout(product_id):
    try:
        # In a real app, you would get the user's email from their session
        user_email = "customer@example.com"

        session = client.checkout_sessions.create(
            product_cart=[
                {
                    "product_id": product_id,
                    "quantity": 1
                }
            ],
            customer={
                "email": user_email,
                "name": "John Doe"
            },
            return_url=f"{APP_URL}/dashboard",
            metadata={
                "user_id": "user_12345"
            }
        )

        return jsonify({
            "checkout_url": session.checkout_url,
            "session_id": session.session_id
        })

    except Exception as e:
        app.logger.error(f"Checkout error: {str(e)}")
        return jsonify({"error": "Could not create checkout session"}), 500
```

## 第 4 步：创建 Webhook 路由

Webhook 对于在付款成功后更新数据库至关重要。每当发生事件（例如 `payment.succeeded` 或 `subscription.active`）时，Dodo Payments 都会向你的 webhook URL 发送 POST 请求。

安全性在这里至关重要。你必须验证 webhook 签名，以确保请求确实来自 Dodo Payments。我们的[webhook 与支付通知指南](https://dodopayments.com/blogs/webhooks-payment-notifications)对此步骤进行了更深入的介绍。

```python
@app.route('/webhooks/dodo', methods=['POST'])
def handle_webhook():
    payload = request.data
    headers = {
        "webhook-id": request.headers.get("webhook-id", ""),
        "webhook-signature": request.headers.get("webhook-signature", ""),
        "webhook-timestamp": request.headers.get("webhook-timestamp", ""),
    }

    try:
        # The SDK provides a utility to verify and unwrap the webhook
        event = client.webhooks.unwrap(payload, headers=headers)

        event_type = event.type
        data = event.data

        if event_type == "payment.succeeded":
            handle_payment_success(data)
        elif event_type == "subscription.active":
            handle_subscription_active(data)
        elif event_type == "subscription.cancelled":
            handle_subscription_cancelled(data)

        return "", 200

    except Exception as e:
        app.logger.error(f"Webhook verification failed: {str(e)}")
        return "Invalid signature", 400

def handle_payment_success(payment_data):
    # Update your database to grant access
    user_id = payment_data.metadata.get('user_id')
    amount = payment_data.total_amount
    print(f"Payment succeeded for user {user_id}: {amount}")

def handle_subscription_active(subscription_data):
    # Store the subscription ID in your database
    user_id = subscription_data.metadata.get('user_id')
    sub_id = subscription_data.subscription_id
    print(f"Subscription {sub_id} active for user {user_id}")
```

## 第 5 步：使用装饰器保护端点

Flask 的一大优势是可以轻松使用装饰器包装路由。你可以创建一个 `@require_payment` 装饰器，在允许用户访问特定 API 资源前，检查其是否拥有有效订阅。

```python
from functools import wraps

def require_payment(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        # In a real app, get the user from the session or JWT
        user_id = "user_12345"

        # Check your local database for an active subscription
        # Or query Dodo Payments directly (though local DB is faster)
        is_active = check_user_subscription_status(user_id)

        if not is_active:
            return jsonify({
                "error": "Payment required",
                "checkout_url": "/buy/prod_premium"
            }), 402

        return f(*args, **kwargs)
    return decorated_function

@app.route('/api/premium-data')
@require_payment
def get_premium_data():
    return jsonify({"data": "This is protected content"})
```

## 高级集成：Flask + Celery

对于高流量 API，同步处理 webhook 可能会拖慢响应速度；如果数据库操作繁重，还可能导致超时。将 Celery 与 Flask 搭配使用，可以把 webhook 处理交给后台工作进程。

当 webhook 请求到达 Flask 路由时，你只需验证签名，然后分派 Celery 任务来处理业务逻辑。

```python
from celery import Celery

def make_celery(app):
    celery = Celery(
        app.import_name,
        backend=app.config['CELERY_RESULT_BACKEND'],
        broker=app.config['CELERY_BROKER_URL']
    )
    celery.conf.update(app.config)
    return celery

celery = make_celery(app)

@celery.task
def process_webhook_task(event_type, data_dict):
    # Heavy database operations here
    if event_type == "payment.succeeded":
        # Logic for payment success
        pass

@app.route('/webhooks/dodo', methods=['POST'])
def handle_webhook_async():
    payload = request.data
    headers = {
        "webhook-id": request.headers.get("webhook-id", ""),
        "webhook-signature": request.headers.get("webhook-signature", ""),
        "webhook-timestamp": request.headers.get("webhook-timestamp", ""),
    }

    try:
        event = client.webhooks.unwrap(payload, headers=headers)
        # Convert Pydantic model to dict for Celery serialization
        process_webhook_task.delay(event.type, event.data.to_dict())
        return "", 200
    except Exception:
        return "Error", 400
```

## 使用 Flask 测试客户端进行测试

测试支付流程可能比较棘手。幸运的是，Flask 测试客户端可以轻松模拟 webhook 请求。你可以使用 Dodo Payments 测试模式，为单元测试生成有效签名。

```python
import unittest
from app import app

class WebhookTestCase(unittest.TestCase):
    def setUp(self):
        self.app = app.test_client()

    def test_webhook_verification(self):
        # Simulate a POST request to the webhook endpoint
        # In a real test, you would generate a valid signature using the SDK
        response = self.app.post(
            '/webhooks/dodo',
            data='{"type": "payment.succeeded", "data": {}}',
            headers={
                'webhook-id': 'msg_test',
                'webhook-timestamp': '0',
                'webhook-signature': 'v1,invalid_sig',
            }
        )
        self.assertEqual(response.status_code, 400)
```

## 订阅管理

在 Flask 中管理订阅，不只是处理最初的结账流程。你还需要处理升级、降级和取消。Dodo Payments 提供客户门户，你可以从 Flask 应用链接到该门户，让用户自行管理计费，而无需自己构建自定义界面。

```python
@app.route('/billing-portal', methods=['GET'])
def get_portal_url():
    # Get the customer ID from your database
    customer_id = "cust_12345"

    portal_session = client.customers.customer_portal.create(
        customer_id=customer_id,
        return_url=f"{APP_URL}/settings"
    )

    return redirect(portal_session.link)
```

## 处理争议和退款

在任何支付集成中，你都必须考虑争议（拒付）以及发起退款的需求。Dodo Payments 通过控制面板和 API 提供了简化的事件处理方式。

### 通过 Flask API 发起退款

如果你需要直接从 Flask 应用发起退款（例如响应客户支持请求），可以使用 `refunds.create` 方法。

```python
@app.route('/refund/<payment_id>', methods=['POST'])
def issue_refund(payment_id):
    try:
        # In a real app, verify the user has permission to refund
        refund = client.refunds.create(
            payment_id=payment_id,
            reason="Customer request"
        )
        return jsonify({"status": "Refund initiated", "refund_id": refund.refund_id})
    except Exception as e:
        return jsonify({"error": str(e)}), 500
```

### 使用 Webhook 监控争议

当客户向其银行提出扣款争议时，Dodo Payments 会发送 `dispute.opened` webhook 事件。处理此事件至关重要：你需要暂停用户访问权限，并准备争议流程所需的证据。

```python
@app.route('/webhooks/dodo', methods=['POST'])
def handle_webhook_with_disputes():
    # ... verification logic ...
    if event_type == "dispute.opened":
        handle_dispute_opened(data)
    # ...
    return "", 200

def handle_dispute_opened(dispute_data):
    user_id = dispute_data.metadata.get('user_id')
    # Pause user access in your database
    deactivate_user_account(user_id)
    app.logger.warning(f"Dispute opened for user {user_id}. Access revoked.")
```

## 监控与分析

成功运营 API 业务需要持续监控其财务状况。Dodo Payments 在控制面板中提供详细分析，但你也可以将这些数据提取到自己的 Flask 管理后台。

### 获取交易历史

你可以使用 SDK 列出最近的付款记录，并在管理界面中显示。

```python
@app.route('/admin/payments')
def list_payments():
    # List the last 10 payments
    payments = client.payments.list(limit=10)
    return render_template('admin_payments.html', payments=payments)
```

### 跟踪收入指标

将 Flask 应用的使用数据与 Dodo 的支付数据结合起来，你可以计算每用户平均收入（ARPU）和流失率等指标。这些数据对于制定产品定价和功能路线图决策至关重要。

## Flask 支付 API 最佳实践

1. **使用环境变量**：绝不要将 API 密钥硬编码。请在本地开发中使用 `.env` 文件，并在生产环境中使用安全的密钥管理方案。
2. **幂等性**：确保 webhook 处理程序具备幂等性。如果 Dodo Payments 两次发送相同的 `payment.succeeded` 事件，你的代码应能妥善处理，而不会重复授予访问权限。
3. **日志记录**：记录每次 webhook 尝试，包括失败的尝试。这对于调试集成问题非常有价值。
4. **优雅失败**：如果 Dodo Payments API 暂时无法访问，请确保 Flask 应用返回有帮助的错误消息，而不是通用的 500 错误。
5. **数据库事务**：在 webhook 处理程序中更新用户访问权限时，使用数据库事务，确保即使更新过程中发生崩溃，数据仍保持一致。

## 总结

为 Flask API 添加结账功能不必是一项复杂的工作。借助 Dodo Payments Python SDK 和 MoR（名义商户）模式，你可以专注于构建核心产品，同时将全球支付和税务合规的复杂性外包出去。

无论你构建的是简单的微服务还是全球 SaaS 平台，Flask 的极简设计结合 Dodo 强大的计费功能，都能为你的业务提供可扩展的基础。

如需了解更多使用 Dodo Payments 构建产品的信息，请查看我们的[如何接受在线付款](https://dodopayments.com/blogs/how-to-accept-online-payments)指南，或阅读[面向 SaaS 的嵌入式支付](https://dodopayments.com/blogs/embedded-payments-saas)文章。如果你已经准备好开始构建，请前往[Dodo Payments 文档](https://docs.dodopayments.com)，查看完整的 API 参考和集成指南。

## 常见问题

### Dodo Payments 支持 Flask 的异步路由吗？

支持。Dodo Payments Python SDK 包含 `AsyncDodoPayments` 客户端，可以与 Flask 的异步支持完美协作（Flask 2.0 及更高版本提供）。你可以在路由中使用 `await` 处理 API 调用，而不会阻塞主线程。

### 如何在 Flask 应用中处理销售税？

你不需要自行处理。Dodo Payments 作为 MoR（名义商户），会为每笔交易计算、收取并汇缴销售税和增值税。你的 Flask 应用只需要处理结账会话以及由此产生的 webhook。

### 我可以将 Dodo Payments 与 Flask-SQLAlchemy 搭配使用吗？

当然可以。大多数开发者都会使用 Flask-SQLAlchemy 管理用户和订阅数据。你可以在 webhook 处理程序中更新数据库模型，以反映用户付款或订阅的当前状态。

### 结账会话和支付链接有什么区别？

结账会话是用于完成单笔交易的临时对象，通常会根据特定客户数据动态创建。支付链接则是可以多次分享的长期 URL。两者都可以通过 Flask API 生成和管理。

### 如何在不花费真实资金的情况下测试 Flask 集成？

Dodo Payments 提供完整的测试模式。你可以使用测试 API 密钥和测试信用卡号，模拟成功付款、失败交易和订阅周期，而不会发生任何真实资金流转。

## 延伸阅读

- [如何接受在线付款](https://dodopayments.com/blogs/how-to-accept-online-payments)
- [面向 SaaS 的嵌入式支付](https://dodopayments.com/blogs/embedded-payments-saas)
- [SaaS 支付架构](https://dodopayments.com/blogs/payments-architecture-saas)
- [面向 SaaS 的 MoR（名义商户）](https://dodopayments.com/blogs/merchant-of-record-for-saas)
- [如何在线销售软件](https://dodopayments.com/blogs/how-to-sell-software-online)
- [订阅定价模式](https://dodopayments.com/blogs/subscription-pricing-models)
- [API 商业化](https://dodopayments.com/blogs/api-monetization)
- [Dodo Payments 定价](https://dodopayments.com/pricing)

## 文档链接

- [Python SDK 文档](https://docs.dodopayments.com/developer-resources/dodo-payments-sdks)
- [Webhook 集成指南](https://docs.dodopayments.com/developer-resources/webhooks)
- [结账会话 API](https://docs.dodopayments.com/developer-resources/checkout-session)
- [客户门户指南](https://docs.dodopayments.com/features/customer-portal)
- [集成概览](https://docs.dodopayments.com/developer-resources/integration-guide)
---
- [More Payments articles](https://dodopayments.com/blogs/category/payments)
- [All articles](https://dodopayments.com/blogs)