PAYATHON 2026

如何使用 python-payflowpro 实现按需支付?

支付老李

结论

可以实现。Payflow Pro 的 Reference Transaction(参考交易)机制允许商户保存首次交易返回的 PNREF。后续付款时,通过 ORIGID 引用这笔交易,无需再次提交信用卡号。

参考交易不只用于调整原订单。只要商户账户、支付处理机构和业务类型支持,它也可以处理用户主动发起的后续付款。Recurring Billing 更适合固定周期扣款,并非这个场景的首选。

如果 python-payflowpro 没有封装参考交易,可以在现有客户端中增加一个方法,也可以直接调用 Payflow Pro 的 NVP HTTP 接口,一般不必重新实现整套 SDK。

支付流程

可以按以下流程设计:

  1. 用户首次输入信用卡信息。
  2. 服务端向 Payflow Pro 发起 Sale 或 Authorization。
  3. 交易成功后保存响应中的 PNREF,不要在自己的数据库中保存完整卡号或 CVV2。
  4. 用户以后主动付款时,提交金额和业务订单号。
  5. 服务端将之前保存的 PNREF 作为 ORIGID,发起新的参考交易。
  6. 保存新交易返回的 PNREF、RESULT、RESPMSG 和内部订单号。
  7. 根据账户配置和 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 完成由用户主动触发的后续付款。

备注:内容仅供参考。