PAYATHON 2026

Satchmo 如何延迟捕获已授权的付款?

支付阿杰

结论

可以实现“结账时只授权、稍后再捕获”,但要满足两个条件:

  1. Satchmo 使用的付款后端实现了 authorize_payment 和 capture_payment。
  2. 已关闭付款模块的即时捕获选项,并由后台业务流程主动调用 capture_payment。

这类配置通常由 django-livesettings 管理,不一定出现在 settings.py 中。先进入 Satchmo 的站点设置页面,查看对应付款模块或全局付款设置中是否有 CAPTURE、CAPTURE_PAYMENT、Immediately capture payment 等选项,然后将其关闭。

配置项的名称会因 Satchmo 版本和付款模块而异。如果管理页面中没有相关选项,说明当前后端没有开放授权和捕获配置。即使能看到这两个 API 方法,也不能据此判断结账流程已经接入它们。

为什么找不到普通的 Django 设置

Satchmo 的付款配置大多保存在运行时配置系统中。配置可能存储在数据库里,通过管理后台的设置页面显示,而不会以这种形式出现在项目配置中:

# 不一定存在这样的 Django 配置
SATCHMO_CAPTURE_PAYMENT = False

因此,应该先检查以下位置:

  • Satchmo 的 Payment Settings;
  • 当前启用的付款模块配置;
  • 付款模块的 config.py;
  • 结账流程实际调用的是 authorize_payment、capture_payment,还是统一的 process 方法。

如果模块的 config.py 注册了类似下面的选项,就可以在 Satchmo 设置页面中切换:

BooleanValue(
    PAYMENT_GROUP,
    'CAPTURE',
    default=True,
)

这只是常见的代码结构,实际键名要以项目正在使用的付款模块为准。

实现步骤

1. 确认网关支持分离授权和捕获

付款服务商必须支持两阶段交易:

authorize -> capture

授权成功后,资金通常只会被冻结,还没有真正结算。之后执行捕获时,需要使用授权交易返回的交易编号,例如 transaction_id、authorization_id,或网关规定的同类字段。

如果付款模块实际上把 authorize_payment 映射为“授权并立即捕获”,那么只改配置无法实现分离授权和捕获。

2. 关闭即时捕获

在 Satchmo 设置页面中找到当前付款模块,关闭即时捕获选项。常见名称包括:

CAPTURE
CAPTURE_PAYMENT
Immediate capture
Capture immediately

具体名称取决于 Satchmo 版本和付款后端,不能只根据 API 文档假定某个固定键必然存在。

修改配置后,应在网关的测试环境中下单,并到服务商后台确认交易状态为“已授权”,而不是“已捕获”或“已结算”。

3. 保存授权交易信息

授权完成后,至少要保存:

  • Satchmo 订单编号;
  • 网关授权交易编号;
  • 已授权金额和币种;
  • 授权时间;
  • 当前付款状态;
  • 捕获结果或失败原因。

不能只依靠订单状态判断交易能否捕获。后续调用网关时,通常还需要提供原始授权交易编号。

4. 在明确指令到达后执行捕获

捕获操作可以放在后台管理操作、发货流程或独立任务中。下面是示意代码,具体的方法签名和处理器加载方式需要根据当前 Satchmo 版本调整:

from decimal import Decimal
from django.db import transaction


@transaction.atomic
def capture_order_payment(order, processor):
    payment = (
        order.payments
        .select_for_update()
        .filter(success=True)
        .latest("time_stamp")
    )

    if payment.capture_transaction_id:
        return payment.capture_transaction_id

    amount = Decimal(str(payment.amount))

    result = processor.capture_payment(
        order=order,
        amount=amount,
        transaction_id=payment.transaction_id,
    )

    if not result.success:
        raise RuntimeError(result.reason)

    payment.capture_transaction_id = result.transaction_id
    payment.save(update_fields=["capture_transaction_id"])

    return result.transaction_id

这段代码不是适用于所有 Satchmo 版本的固定接口。transaction_id 参数名、result.success 等字段都要以实际付款后端的实现为准。部分后端可能要求先调用 prepareData:

processor.prepareData(order)
result = processor.capture_payment()

还有一些后端会把原始授权编号保存在处理器实例或付款记录中,而不是通过参数传入。

如果模块没有提供配置项

如果当前付款后端实现了这两个方法,却没有在 config.py 中注册即时捕获开关,通常需要修改付款模块或结账流程:

if capture_immediately:
    result = processor.capture_payment(
        order=order,
        amount=order.balance,
    )
else:
    result = processor.authorize_payment(
        order=order,
        amount=order.balance,
    )

还需要增加一个后台入口,让运营人员或发货流程能够调用 capture_payment。只把结账时的调用改为 authorize_payment 并不完整,否则订单会一直处于已授权状态。

这部分最好通过自定义付款后端或项目级扩展实现,不要直接修改已安装的 Satchmo 包,以免升级或重新部署时丢失改动。

注意事项

  • 授权有有效期,过期后可能无法捕获,需要重新授权。
  • 捕获金额通常不能超过授权金额。
  • 是否支持部分捕获、多次捕获和撤销授权,取决于付款网关。
  • 捕获操作必须做好幂等保护,防止重复请求造成重复扣款。
  • 网络超时不能直接判定为捕获失败,应先向网关查询原交易状态。
  • 订单的“已授权”“已付款”和“已发货”应当使用不同状态。
  • 后台捕获操作需要限制权限,并记录操作人、时间、金额和网关响应。
  • 必须在付款服务商的测试环境中核对实际状态,不能仅凭 Satchmo 返回成功就认定资金尚未捕获。

所以,不要寻找一个适用于所有 Satchmo 后端的统一 Django 设置。先确认当前付款模块是否注册了即时捕获选项,再检查结账代码会不会根据该选项调用 authorize_payment。如果模块没有接好这部分逻辑,就需要补充付款后端配置和后续捕获流程。

备注:内容仅供参考。