PAYATHON 2026

如何取消 django-merchant 的 recurring payment?

支付小周

结论

merchant.recurring(amount, credit_card) 返回的 response 不是订阅对象,不能直接调用 cancel()。取消 recurring payment 时,通常需要使用支付网关在创建订阅时返回的标识,例如 subscription_id 或 profile_id,再调用该网关的取消接口。

django-merchant 统一封装了不同的支付网关,但没有统一的 recurring payment 取消方法可供所有网关使用。具体如何取消,取决于项目使用的网关和 django-merchant 版本。

处理步骤

1. 保存网关返回的订阅标识

创建 recurring payment 后,检查响应并持久化其中的数据:

response = merchant.recurring(amount, credit_card)

if response.success:
    print(response.message)
    print(response.params)

订阅标识通常在 response.params 中,具体字段名由支付网关决定,例如:

subscription_id = response.params.get("subscription_id")
profile_id = response.params.get("profile_id")

不要只保存信用卡信息或本地订单号。取消订阅时,支付网关通常需要它生成的远程订阅标识。

2. 检查当前网关是否实现了取消操作

检查项目使用的网关类和当前版本提供的接口。如果网关明确提供了取消 recurring payment 的方法,可以按其接口调用。不要默认存在以下通用方法:

merchant.cancel_recurring(subscription_id)

只有当前网关的实现明确声明了该方法,才能这样调用。它不是所有 django-merchant 网关都支持的统一 API。

3. 没有统一接口时,调用支付网关原生 API

如果 django-merchant 只能创建 recurring payment,没有实现取消操作,就需要调用相应支付网关的 SDK 或 HTTP API:

def cancel_subscription(subscription_id):
    # 下面只是调用结构示意。
    # 应替换成实际支付网关 SDK 提供的取消方法。
    result = gateway_client.cancel_subscription(
        subscription_id=subscription_id
    )

    if result.success:
        # 将本地订阅状态更新为 canceled
        return True

    raise RuntimeError(result.message)

网关确认取消成功后,再更新本地记录:

subscription.status = "canceled"
subscription.save(update_fields=["status"])

不要在网关确认前修改本地状态,否则可能出现本地显示已取消,但网关仍在继续扣款的情况。

注意事项

  • 取消 recurring payment 通常只会阻止后续扣款,不会自动退还已支付的款项。退款需要单独调用 refund API。
  • 删除本地记录不能代替取消操作。数据库中的记录被删除后,支付网关中的订阅仍然存在。
  • 创建订阅时要保存完整的网关响应,尤其是远程订阅 ID。
  • 某些网关会异步处理取消请求,需要通过 webhook 或后续状态查询确认最终结果。
  • 如果 response.params 中没有订阅标识,需要检查该网关的响应格式,并确认创建 recurring payment 时是否真正建立了远程订阅计划。
  • 不同 django-merchant 版本和网关实现之间可能存在较大差异。如果无法确认接口,应以当前项目中对应网关类的实现和支付服务商 API 文档为准。

备注:内容仅供参考。