Satchmo 如何延迟捕获已授权的付款?
结论
可以实现“结账时只授权、稍后再捕获”,但要满足两个条件:
- Satchmo 使用的付款后端实现了
authorize_payment和capture_payment。 - 已关闭付款模块的即时捕获选项,并由后台业务流程主动调用
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。如果模块没有接好这部分逻辑,就需要补充付款后端配置和后续捕获流程。
备注:内容仅供参考。
