PAYATHON 2026

Django Oscar 结账时跳过 payment details 流程

支付阿杰

结论

302 本身并不代表异常。Django Oscar 会根据当前可用的支付方式和结账步骤的完成情况,决定是否显示 /checkout/payment-method/。

常见原因包括:

  • 系统没有配置需要用户选择的支付方式;
  • 当前只有一种可用的支付方式,Oscar 自动选中后跳过选择页面;
  • 自定义代码、配置或 checkout session 已经保存了支付方式;
  • 某个 checkout 前置条件尚未满足,Oscar 按流程重定向;
  • 项目覆盖了 Oscar 的 URL、视图或模板,但没有正式 fork checkout app。

因此,下面这条日志通常只表示发生了一次正常重定向:

"GET /checkout/payment-method/ HTTP/1.1" 302 0

排查时应重点确认响应中的 Location,以及 PaymentMethodView 对当前支付方式的判断结果。

为什么会直接进入 payment preview

payment-method 页面用于让用户选择支付方式,例如信用卡、PayPal 或银行转账。这个页面并不负责收集银行卡信息。

Oscar 的标准结账流程大致如下:

shipping-address
    ↓
shipping-method
    ↓
payment-method
    ↓
payment-details / preview
    ↓
place-order

如果用户无需选择,PaymentMethodView 可以直接重定向到下一步。比如当前只有一种支付方式,或者项目根本没有向 Oscar 提供支付方式列表。

这几个页面的职责不同:

  • payment-method:选择支付方式;
  • payment-details:输入或处理具体的支付信息;
  • preview:确认订单内容,准备提交订单。

Oscar 本身不提供完整的第三方支付集成。即使 checkout app 没有 fork,项目代码或支付插件仍需提供可用的支付方式,并在提交订单时完成授权、扣款或交易记录创建。

首先检查 302 的目标地址

可以在浏览器开发者工具的 Network 面板中查看响应头:

Location: /checkout/payment-details/

也可以暂时关闭自动重定向,通过 Django 测试确认:

response = self.client.get(
    "/checkout/payment-method/",
    follow=False,
)

print(response.status_code)
print(response.headers.get("Location"))

如果目标是 payment-details 或 preview 页面,通常表示 Oscar 主动跳过了支付方式选择步骤。如果请求被重定向到 shipping address、shipping method 或 basket,则更可能是某个前置条件尚未满足。

检查可用支付方式

先查看当前安装版本中 PaymentMethodView.get_available_payment_methods() 的实现,再确认项目是否覆盖了这个方法。

不同 Oscar 版本使用的支付方式对象和方法签名可能略有差异。下面是一种常见写法:

from oscar.apps.checkout import views
from oscar.apps.checkout.methods import PaymentMethod


class PaymentMethodView(views.PaymentMethodView):
    def get_available_payment_methods(self):
        return [
            PaymentMethod("card", "银行卡"),
            PaymentMethod("bank_transfer", "银行转账"),
        ]

部分旧版本可能使用元组,或从其他模块导入 PaymentMethod。具体写法应以当前安装版本的源码为准,不要直接假定导入路径相同。

如果该方法只返回一种支付方式,例如:

def get_available_payment_methods(self):
    return [
        PaymentMethod("card", "银行卡"),
    ]

Oscar 可能会认为用户没有选择的必要,于是自动保存该方式并进入下一步。是否跳过以及具体的判断条件,仍需结合当前 Oscar 版本的 PaymentMethodView 实现确认。

如果返回空列表:

def get_available_payment_methods(self):
    return []

说明项目没有提供可选的支付方式。某些版本会跳过这一步,另一些版本或自定义实现则可能进入错误处理分支。

正确扩展 checkout app

如果需要显示支付方式选择页,应通过 Oscar 的动态应用机制 fork checkout app,而不是只复制模板。

典型目录结构如下:

project/
├── checkout/
│   ├── __init__.py
│   ├── apps.py
│   └── views.py

apps.py:

from oscar.apps.checkout.apps import CheckoutConfig as OscarCheckoutConfig


class CheckoutConfig(OscarCheckoutConfig):
    name = "project.checkout"

views.py:

from oscar.apps.checkout import views
from oscar.apps.checkout.methods import PaymentMethod


class PaymentMethodView(views.PaymentMethodView):
    def get_available_payment_methods(self):
        return [
            PaymentMethod("card", "银行卡"),
            PaymentMethod("bank_transfer", "银行转账"),
        ]

接着在 INSTALLED_APPS 中使用 fork 后的应用配置,并确保它替代 Oscar 原有的 checkout app,而不是同时注册两个 checkout app:

INSTALLED_APPS = [
    # ...
    "project.checkout.apps.CheckoutConfig",
    # ...
]

Oscar 的应用加载方式会因版本和项目初始化方式而有所不同。修改后可以检查实际加载的是否为自定义类:

from django.apps import apps

config = apps.get_app_config("checkout")
print(config.name)
print(config.__class__)

如果输出仍然指向 oscar.apps.checkout,说明 fork 后的应用没有生效。

确认自定义视图已经注册

只创建 PaymentMethodView 子类不一定能接管请求。某些 Oscar 版本要求在自定义 CheckoutConfig.ready() 中导入视图,或者覆盖应用配置使用的视图类。

可以通过 Django URL resolver 检查最终的解析结果:

from django.urls import resolve

match = resolve("/checkout/payment-method/")
print(match.func)
print(match.func.view_class)

如果 view_class 仍然是 Oscar 原始的 PaymentMethodView,说明自定义视图尚未接管该 URL。

不要直接修改 Oscar 安装目录中的源码。应当 fork app,或通过项目 URL 配置替换对应视图,否则升级依赖后,这些修改会丢失。

检查 checkout session

Oscar 会在 session 中保存结账状态。旧的 session 数据可能使测试结果前后不一致,例如之前已经选过支付方式。

排查时可以:

  • 使用无痕窗口重新开始结账;
  • 清除站点 cookie;
  • 清空购物篮后重新添加商品;
  • 临时记录 checkout session 中与支付方式、配送地址和配送方式有关的数据。

开发环境中也可以直接刷新 session,但不要在生产环境随意清除所有用户会话。

如果清除会话后页面恢复正常,问题通常不在 URL,而在结账状态的保存或恢复逻辑。

检查前置步骤是否完整

Oscar 的 checkout 视图通常会校验流程状态。访问 payment-method 前,至少可能要求以下信息已经确定:

  • 购物篮有效且非空;
  • 商品仍然可以购买;
  • 配送地址已经保存;
  • 配送方式已经选择;
  • 当前用户或匿名结账状态有效。

只要其中一项缺失,Oscar 就可能通过 302 把用户送回需要先完成的页面。仍然可以根据 Location 判断具体原因:

Location: /checkout/shipping-address/

这种情况不是 payment method 被跳过,而是当前请求还不允许进入该步骤。

如果只有一种支付方式

只有一种支付方式时,跳过选择页面通常是合理的。此时应重点确认后续支付处理是否完整:

from oscar.apps.checkout import views


class PaymentDetailsView(views.PaymentDetailsView):
    def handle_payment(self, order_number, total, **kwargs):
        # 调用支付网关进行授权或扣款。
        # 失败时应抛出当前 Oscar 版本支持的支付异常。
        pass

没有必要为了显示一个不存在选择意义的页面而强行关闭跳过逻辑。更稳妥的处理流程是:

  1. 自动选中唯一的支付方式;
  2. 在 payment details 页面收集必要信息;
  3. 在 handle_payment() 中调用支付服务;
  4. 正确创建 Oscar 的支付来源和交易记录;
  5. 支付失败时,阻止订单被错误地标记为成功。

handle_payment() 的参数、异常类型和支付模型用法存在版本差异,应按照当前 Oscar 版本和支付插件的文档实现。

建议的排查顺序

  1. 查看 302 响应的 Location。
  2. 使用 resolve() 确认实际执行的视图类。
  3. 检查 get_available_payment_methods() 返回的是零个、一个还是多个支付方式。
  4. 清除浏览器 session,再重新完成整个结账流程。
  5. 确认配送地址和配送方式已经写入 checkout session。
  6. 检查是否有项目 URL 提前匹配了 /checkout/。
  7. 检查 fork 后的 checkout app 是否真正替换了 Oscar 原应用。
  8. 最后检查支付插件及 PaymentDetailsView 的实现。

如果重定向目标是 payment details 或 preview,而且系统只有一种支付方式,这通常符合设计。如果确实需要用户选择支付方式,就应提供至少两个有效方式,并确认自定义 PaymentMethodView 已被 Oscar 实际加载。

备注:内容仅供参考。