在 Pylons/Python 应用中安全处理 payment processing
结论
Pylons/Python 应用在技术上可以直接接收信用卡号,再由服务器提交给支付网关,但一般不建议采用这种方式。
只要信用卡号经过你的页面、应用服务器、日志或数据库,系统就会被纳入更大的 PCI DSS 合规范围。更简单也更安全的做法,是使用支付网关提供的 Hosted Fields、嵌入式 iframe 或前端 Tokenization SDK。用户仍然可以留在你的页面中完成支付,但卡号会由浏览器直接发送给支付网关。你的服务器只接收一次性 token 或 payment method ID。
推荐的支付流程
常见的处理流程是:
- 后端创建订单,并生成内部
order_id。 - 支付页面加载支付网关的官方 JavaScript SDK。
- 卡号输入框由网关托管,或者由网关 SDK 采集卡片信息。
- 浏览器把卡片信息直接提交给支付网关。
- 网关返回一次性 token,例如
payment_method_id。 - 浏览器将 token 和
order_id提交给 Pylons 应用。 - 后端读取数据库中的订单金额,并调用支付网关完成付款。
- 后端通过 webhook 确认最终支付状态。
用户不需要跳转到网关网站,信用卡号也不会经过你的 Python 应用。
前端示意
各家支付网关的 SDK 名称和参数不同。下面的代码只用于说明整体结构,不能直接当作某个网关的正式接口使用:
<form id="payment-form">
<div id="gateway-card-field"></div>
<button type="submit">立即支付</button>
</form>
<script src="https://gateway.example/sdk.js"></script>
<script>
const gateway = PaymentGateway.init({
publicKey: "PUBLIC_KEY"
});
const cardField = gateway.createCardField();
cardField.mount("#gateway-card-field");
document
.getElementById("payment-form")
.addEventListener("submit", async (event) => {
event.preventDefault();
const result = await gateway.createPaymentMethod({
card: cardField
});
if (result.error) {
alert(result.error.message);
return;
}
const response = await fetch("/payments/confirm", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": window.csrfToken
},
body: JSON.stringify({
order_id: "ORDER_ID",
payment_method_id: result.paymentMethod.id
})
});
const payment = await response.json();
if (payment.status === "succeeded") {
window.location.href = "/payments/success";
} else {
alert("付款尚未完成,请按页面提示处理。");
}
});
</script>
其中最重要的一点是,gateway-card-field 必须由支付网关托管。不要通过普通的 <input name="card_number"> 收集卡号,再把它发送到自己的服务器,否则卡片数据仍然没有得到隔离。
Pylons/Python 后端示意
后端只接收 token,不接收卡号、有效期或 CVV:
import hmac
import json
import os
import requests
from pylons import request, response
class PaymentsController:
def confirm(self):
payload = request.json
order_id = payload.get("order_id")
payment_method_id = payload.get("payment_method_id")
if not order_id or not payment_method_id:
response.status_int = 400
return json.dumps({"error": "invalid_request"})
order = load_unpaid_order(order_id)
if order is None:
response.status_int = 404
return json.dumps({"error": "order_not_found"})
# 金额和币种必须从服务端订单读取,不能相信浏览器提交的数据。
gateway_response = requests.post(
"https://api.gateway.example/v1/payments",
headers={
"Authorization": "Bearer " + os.environ["PAYMENT_GATEWAY_SECRET"],
"Idempotency-Key": order.payment_attempt_id,
"Content-Type": "application/json",
},
json={
"payment_method": payment_method_id,
"amount": order.amount_minor,
"currency": order.currency,
"reference": order.id,
},
timeout=(3.05, 15),
)
gateway_response.raise_for_status()
result = gateway_response.json()
return json.dumps({
"status": result["status"],
"payment_id": result["id"],
})
代码中的 URL、字段和认证方式都是占位示例。实际开发时,必须以所选支付网关的官方 SDK 或 API 文档为准。amount_minor 通常表示最小货币单位,例如人民币的分或美元的美分,具体规则仍需查看网关文档。
Webhook 才是最终状态依据
支付成功后,用户可能直接关闭页面,前端响应也可能被伪造。因此,不能只根据支付结果页将订单标记为已付款。后端应接收网关发来的 webhook,并验证签名:
import hashlib
import hmac
import json
import os
from pylons import request, response
class PaymentWebhooksController:
def receive(self):
raw_body = request.body
received_signature = request.headers.get("X-Gateway-Signature", "")
expected_signature = hmac.new(
os.environ["PAYMENT_WEBHOOK_SECRET"].encode("utf-8"),
raw_body,
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(
received_signature,
expected_signature,
):
response.status_int = 401
return "invalid signature"
event = json.loads(raw_body.decode("utf-8"))
if event["type"] == "payment.succeeded":
mark_order_paid_once(
order_id=event["data"]["reference"],
payment_id=event["data"]["payment_id"],
)
response.status_int = 200
return "ok"
不同网关使用的签名算法、签名内容、时间戳校验方式和请求头名称并不相同,不能直接照搬这个示例。实现时应严格遵循网关文档,同时处理重复通知、乱序通知和重放攻击。
如果必须由应用接收卡号
只有在业务确实无法使用 tokenization 或托管字段时,才应考虑让服务器直接处理卡片数据。至少需要做到以下几点:
- 全链路使用正确配置的 HTTPS,禁止通过 URL、查询字符串或非加密连接传输卡号。
- 按适用的 PCI DSS 要求完成评估、扫描和安全控制。
- 对应用、反向代理、监控平台和异常追踪系统的日志进行脱敏。
- 不在数据库、缓存、消息队列、Session、备份或分析系统中保留完整卡号。
- 授权完成后不得保存 CVV/CVC/CID,即使经过加密也不能保存。
- 如果确实需要保存 PAN,应使用专业密钥管理、字段级加密和严格的访问控制。展示时只保留必要的掩码信息,例如
**** **** **** 1234。 - 为支付请求设置幂等键,避免网络重试导致重复扣款。
- 在服务端校验订单金额、币种、商品和订单状态。
- 使用 CSRF 防护、Content Security Policy、安全 Cookie,并做好依赖漏洞管理。
- 限制后台和运维人员接触支付数据,同时保留可审计的访问记录。
仅仅加密数据库并不能解决问题,因为卡号在写入数据库之前,已经经过 Web 服务器、Python 运行时和日志链路。
还需要注意的边界
Hosted Fields 或 tokenization 通常能显著缩小 PCI DSS 的适用范围,但不会自动免除全部合规义务。究竟适用 SAQ A、SAQ A-EP 还是其他评估类型,取决于页面集成方式、支付流程、服务商资质和收单机构的要求。具体情况应向支付服务商或合格的 PCI 顾问确认。
Pylons 是较早期的 Python Web 技术栈。支付模块需要特别检查 Python、Pylons 相关组件、TLS 库和其他依赖包是否仍处于安全支持周期。如果现有系统难以升级,可以将支付后端拆分为一个规模较小、依赖可控的独立服务。
对大多数应用来说,更合适的方案是让用户留在自己的页面中,同时让卡片数据直接进入支付网关。Pylons 后端只处理 token、订单和支付状态。
备注:内容仅供参考。
