PAYATHON 2026

如何为 Google App Engine 应用配置或访问 SSL?

支付阿杰

结论

Google App Engine 应用可以通过 HTTPS 对外提供服务,也可以通过 HTTPS 调用支付服务商的 API。一般无须在 App Engine 实例中直接安装或读取 SSL 证书的私钥,也不应该这样做。

处理信用卡支付时,建议采用以下方式:

  1. 为 App Engine 默认域名或自定义域名启用 HTTPS。
  2. 使用 Stripe、Adyen、PayPal 等符合 PCI DSS 要求的支付服务商。
  3. 让用户直接在浏览器中将信用卡信息提交给支付服务商,或者跳转到服务商托管的结账页面。
  4. App Engine 后端只接收支付令牌、订单状态和通过签名验证的 webhook,不接触完整卡号和 CVV。

这种设计符合 App Engine 的托管运行模式,也能大幅缩小 PCI DSS 的合规范围。

为什么不能像普通服务器那样安装证书

App Engine 是托管平台。应用通常无法操作底层 Web 服务器,也不能自行修改 TLS 监听端口、证书文件路径或系统级网络配置。因此,传统服务器上的配置方式通常不适用,例如:

ssl_certificate /etc/ssl/example.com.crt;
ssl_certificate_key /etc/ssl/private/example.com.key;

TLS 连接应在 Google 管理的入口层终止,再由平台将请求转发给应用。应用代码只需处理 HTTP 请求,无须直接完成 TLS 握手。

如果使用自定义域名,可以通过平台提供的托管证书功能启用 HTTPS。具体配置入口和支持范围可能会随着 Google Cloud 控制台及 App Engine 产品策略调整,应以当前项目控制台和官方文档为准。

配置 HTTPS 的基本步骤

1. 使用 App Engine 默认域名

App Engine 应用通常会获得一个类似下面的默认地址:

https://PROJECT_ID.REGION_ID.r.appspot.com

实际域名格式可能与应用的创建时间和区域有关,请以 Google Cloud 控制台中显示的应用 URL 为准。

默认域名的 TLS 证书由 Google 管理,应用无须自行上传证书。

2. 为自定义域名启用 HTTPS

一般需要完成以下步骤:

  1. 在 Google Cloud 项目中验证域名所有权。
  2. 将自定义域名映射到 App Engine 应用。
  3. 按照控制台要求配置 DNS 记录。
  4. 等待托管 SSL 证书签发并生效。
  5. 使用浏览器或 TLS 检查工具确认域名匹配,并验证证书链。

DNS 配置和证书签发不一定会立即完成。在证书状态变为有效之前,不要将生产支付流量切换到该域名。

3. 强制使用 HTTPS

应用可以检查转发协议,并将 HTTP 请求重定向到 HTTPS。下面是一个 Flask 示例:

from flask import Flask, redirect, request

app = Flask(__name__)

@app.before_request
def require_https():
    forwarded_proto = request.headers.get("X-Forwarded-Proto", "")
    if forwarded_proto != "https":
        return redirect(
            request.url.replace("http://", "https://", 1),
            code=301,
        )

是否需要这段逻辑取决于具体的部署配置。使用 X-Forwarded-Proto 时,只能信任平台入口写入的请求头。在其他代理环境中,不能无条件信任客户端提供的同名请求头。

还可以发送 HSTS 请求头:

@app.after_request
def add_security_headers(response):
    response.headers["Strict-Transport-Security"] = (
        "max-age=31536000; includeSubDomains"
    )
    return response

启用 includeSubDomains 前,要确认所有子域名都支持 HTTPS。HSTS 配置错误可能导致用户在有效期内无法通过 HTTP 访问相关域名。

推荐的支付处理方式

更稳妥的做法是使用支付服务商托管的结账页面。支付流程可以设计为:

用户浏览器
    ├── HTTPS ──> App Engine:创建订单
    ├── HTTPS ──> 支付服务商:输入并提交信用卡信息
    └── HTTPS <── 支付结果页面

支付服务商
    └── 签名 webhook ──> App Engine:确认支付状态

完整卡号、有效期和 CVV 不会经过 App Engine 后端。后端只保存以下信息:

  • 内部订单号
  • 支付服务商生成的 payment ID 或 token
  • 支付金额和币种
  • 支付状态
  • 必要的审计记录

不要保存 CVV。其他银行卡数据能否保存以及如何保存,应遵守支付服务商的规则、PCI DSS 要求和业务所在地适用的法规。

创建托管支付会话的示例

以下示例使用 Stripe Checkout 展示常见的实现结构。API 版本、SDK 初始化方式和参数可能发生变化,生产环境应按照支付服务商的当前文档进行调整:

import os

import stripe
from flask import Flask, jsonify

app = Flask(__name__)
stripe.api_key = os.environ["STRIPE_SECRET_KEY"]

@app.post("/api/checkout")
def create_checkout():
    session = stripe.checkout.Session.create(
        mode="payment",
        line_items=[
            {
                "price": os.environ["STRIPE_PRICE_ID"],
                "quantity": 1,
            }
        ],
        success_url="https://example.com/payment/success?session_id={CHECKOUT_SESSION_ID}",
        cancel_url="https://example.com/payment/cancel",
    )

    return jsonify({"checkout_url": session.url})

浏览器收到 checkout_url 后,跳转到支付服务商的页面:

async function startCheckout() {
  const response = await fetch("/api/checkout", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    }
  });

  if (!response.ok) {
    throw new Error("Unable to create checkout session");
  }

  const data = await response.json();
  window.location.assign(data.checkout_url);
}

密钥应通过 Secret Manager、平台密钥配置或受保护的环境变量注入,不能写入源码,也不能提交到版本库。

必须验证支付 webhook

不能只根据用户被重定向到”支付成功”页面,就认定订单已经付款。用户可能伪造 URL、关闭页面,或者在支付完成前中断跳转。

订单状态应由通过签名验证的 webhook 更新:

import os

import stripe
from flask import Flask, request

app = Flask(__name__)
webhook_secret = os.environ["STRIPE_WEBHOOK_SECRET"]

@app.post("/webhooks/stripe")
def stripe_webhook():
    payload = request.get_data()
    signature = request.headers.get("Stripe-Signature", "")

    try:
        event = stripe.Webhook.construct_event(
            payload,
            signature,
            webhook_secret,
        )
    except (ValueError, stripe.error.SignatureVerificationError):
        return "invalid webhook", 400

    if event["type"] == "checkout.session.completed":
        session = event["data"]["object"]
        payment_id = session["payment_intent"]

        # 使用事务或唯一约束,保证重复 webhook 不会重复发货。
        mark_order_as_paid(payment_id)

    return "", 200

mark_order_as_paid() 必须进行幂等处理。支付服务商可能重复发送同一个事件,因此需要保存 event ID,或者为 payment ID 建立唯一约束,避免重复记账和重复发货。

App Engine 调用支付 API

App Engine 后端可以通过 HTTPS 请求支付服务商的 API。例如:

import os
import requests

response = requests.post(
    "https://api.payment-provider.example/v1/payments",
    headers={
        "Authorization": f"Bearer {os.environ['PAYMENT_API_KEY']}",
        "Idempotency-Key": "order-20260901-0001",
    },
    json={
        "amount": 1999,
        "currency": "CNY",
        "payment_token": "token_from_provider",
    },
    timeout=15,
)

response.raise_for_status()
payment = response.json()

生产代码还要处理连接超时、重试、限流和结果不确定等情况。创建扣款等非幂等操作应使用支付服务商提供的 idempotency key,网络超时后不能直接无条件重试。

安全注意事项

  • 除非团队已经具备相应的 PCI DSS 合规能力,否则不要在自己的支付表单中接收完整卡号和 CVV。
  • 不要记录信用卡信息、支付 token、签名请求头、API secret 或完整的 webhook body。
  • 金额必须由服务端根据商品和订单重新计算,不能直接采用浏览器提交的金额。
  • 金额应使用整数形式的最小货币单位。例如,1999 表示 19.99。不要使用二进制浮点数计算货币。
  • 验证 webhook 签名,并根据需要限制事件的时间窗口,防止伪造和重放。
  • 支付成功后的发货、充值或权益开通必须支持幂等处理。
  • 测试环境和生产环境的密钥、webhook endpoint 及商品 ID 必须完全隔离。
  • 定期轮换密钥,并限制可以读取生产密钥的账号和服务身份。
  • HTTPS 只能保护传输过程,不能代替身份认证、权限控制、输入校验、审计和 PCI DSS 合规。

App Engine 的限制不会妨碍应用安全地处理支付。无须将 SSL 证书安装到应用实例中。应使用平台管理的 HTTPS,并由合规的支付服务商采集和处理敏感的银行卡数据。

备注:内容仅供参考。