如何使用 python-payflowpro 实现按需支付?
结论
可以实现。Payflow Pro 的 Reference Transaction(参考交易)机制允许商户保存首次交易返回的 PNREF。后续付款时,通过 ORIGID 引用这笔交易,无需再次提交信用卡号。
参考交易不只用于调整原订单。只要商户账户、支付处理机构和业务类型支持,它也可以处理用户主动发起的后续付款。Recurring Billing 更适合固定周期扣款,并非这个场景的首选。
如果 python-payflowpro 没有封装参考交易,可以在现有客户端中增加一个方法,也可以直接调用 Payflow Pro 的 NVP HTTP 接口,一般不必重新实现整套 SDK。
支付流程
可以按以下流程设计:
- 用户首次输入信用卡信息。
- 服务端向 Payflow Pro 发起
Sale或Authorization。 - 交易成功后保存响应中的
PNREF,不要在自己的数据库中保存完整卡号或CVV2。 - 用户以后主动付款时,提交金额和业务订单号。
- 服务端将之前保存的
PNREF作为ORIGID,发起新的参考交易。 - 保存新交易返回的
PNREF、RESULT、RESPMSG和内部订单号。 - 根据账户配置和 Payflow 的有效期限制,必要时使用最近一次成功交易的
PNREF作为下次付款的引用。
首次交易未必需要实际扣款。有些处理机构支持零金额验证或仅授权,具体取决于收单机构、处理平台和 Payflow Pro 账户配置,不能假定所有账户都支持同一种验证方式。
参考交易请求
后续付款的主要参数如下:
TRXTYPE=S
TENDER=C
ORIGID=<首次或最近一次交易返回的 PNREF>
AMT=25.00
CURRENCY=USD
参数含义:
TRXTYPE=S表示 Sale。TENDER=C表示信用卡交易。ORIGID是之前成功交易返回的PNREF。AMT是本次独立付款的金额,并非对原交易金额的修改。CURRENCY应与商户账户及原交易支持的币种一致。
如果需要对原授权执行扣款、撤销或退款,应分别使用 Delayed Capture、Void 或 Credit,不要将参考交易当作订单调整接口。
Python 示例
以下代码直接调用 Payflow Pro HTTP 接口,可以作为现有 python-payflowpro 客户端的补充。参数名称遵循 Payflow Pro NVP 接口,但正式上线前仍需按照当前账户对应的 Payflow 文档核对必填字段。
from decimal import Decimal
from urllib.parse import parse_qs
from uuid import uuid4
import requests
class PayflowError(RuntimeError):
pass
class PayflowClient:
PRODUCTION_URL = "https://payflowpro.paypal.com"
PILOT_URL = "https://pilot-payflowpro.paypal.com"
def __init__(
self,
*,
user,
vendor,
partner,
password,
production=False,
timeout=30,
):
self.credentials = {
"USER": user,
"VENDOR": vendor,
"PARTNER": partner,
"PWD": password,
}
self.url = self.PRODUCTION_URL if production else self.PILOT_URL
self.timeout = timeout
def request(self, parameters, request_id=None):
payload = {
**self.credentials,
**parameters,
}
headers = {
"Content-Type": "application/x-www-form-urlencoded",
"X-VPS-Request-ID": request_id or str(uuid4()),
"X-VPS-CLIENT-TIMEOUT": str(self.timeout),
}
response = requests.post(
self.url,
data=payload,
headers=headers,
timeout=self.timeout + 5,
)
response.raise_for_status()
parsed = parse_qs(response.text, keep_blank_values=True)
result = {
key: values[-1] if values else ""
for key, values in parsed.items()
}
if result.get("RESULT") != "0":
raise PayflowError(
f"Payflow transaction failed: "
f"RESULT={result.get('RESULT')}, "
f"RESPMSG={result.get('RESPMSG')}"
)
return result
def reference_sale(
self,
*,
original_pnref,
amount,
currency="USD",
comment=None,
request_id=None,
):
normalized_amount = Decimal(amount).quantize(Decimal("0.01"))
parameters = {
"TRXTYPE": "S",
"TENDER": "C",
"ORIGID": original_pnref,
"AMT": str(normalized_amount),
"CURRENCY": currency,
}
if comment:
parameters["COMMENT1"] = comment
return self.request(parameters, request_id=request_id)
调用方式如下:
client = PayflowClient(
user="payflow-user",
vendor="payflow-vendor",
partner="PayPal",
password="payflow-password",
production=False,
)
result = client.reference_sale(
original_pnref="ABC123456789",
amount="29.95",
currency="USD",
comment="Order 84721",
request_id="payment-84721-attempt-1",
)
new_pnref = result["PNREF"]
print(new_pnref)
示例中的凭据应从密钥管理服务或环境变量中读取,不能直接写入源代码。测试阶段使用 Pilot 地址,确认完整流程无误后再切换到生产地址。
与 python-payflowpro 集成
如果现有包已经负责认证、请求编码和响应解析,通常只需增加一个参考交易方法:
def reference_sale(client, original_pnref, amount, currency="USD"):
params = {
"TRXTYPE": "S",
"TENDER": "C",
"ORIGID": original_pnref,
"AMT": format(Decimal(amount), ".2f"),
"CURRENCY": currency,
}
return client.send(params)
这里的 client.send() 仅用于说明接口形式,实际方法名应以所用的 python-payflowpro 版本为准。如果该项目已经停止维护,可以直接编写一层很薄的 Payflow HTTP 适配代码。与大幅修改旧库相比,这种做法通常更容易测试和维护。
必须确认的账户配置
参考交易能否成功,不仅取决于请求格式,还要确认以下条件:
- Payflow Pro 账户是否启用了 Reference Transactions。
- 收单机构和支付处理平台是否支持这种交易。
- 原始交易能否作为参考交易来源。
PNREF可以被引用的时间范围。- 是否允许跨金额、跨币种或连续多次引用。
- 商户所在地区适用的 stored credential、CIT/MIT 和强客户认证规则。
这些条件可能因处理机构和商户合同而不同。如果测试环境返回“不允许参考交易”一类结果,应先联系 PayPal 或收单机构确认账户是否具备相应能力,不要只修改 Python 代码。
安全与业务注意事项
不要将 PNREF 当作普通的公开订单号。它不是信用卡号,但仍是能够触发付款流程的重要支付标识,需要加密保存并限制访问。
还应注意以下事项:
- 不得保存
CVV2,即使经过加密也不能留存。 - 页面应明确取得用户对保存付款方式及后续扣款的授权。
- 每次按需付款都要创建独立的内部订单和审计记录。
- 使用稳定且唯一的
X-VPS-Request-ID,避免网络重试造成重复扣款。 - HTTP 超时不能直接视为交易失败。应先查询或核对交易结果,再决定是否重试。
- 只有
RESULT=0时才能将交易视为成功,同时保存新的PNREF。 - 为拒付、退款、撤销和卡片失效分别建立处理流程。
- 不要只使用浏览器传入的金额,付款金额必须根据服务端的订单数据计算。
这个需求不必使用 Recurring Billing,也不需要自行保存信用卡信息。应当启用并封装 Payflow Pro 的 Reference Transaction,再通过 PNREF 和 ORIGID 完成由用户主动触发的后续付款。
备注:内容仅供参考。
