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
没有必要为了显示一个不存在选择意义的页面而强行关闭跳过逻辑。更稳妥的处理流程是:
- 自动选中唯一的支付方式;
- 在 payment details 页面收集必要信息;
- 在
handle_payment()中调用支付服务; - 正确创建 Oscar 的支付来源和交易记录;
- 支付失败时,阻止订单被错误地标记为成功。
handle_payment() 的参数、异常类型和支付模型用法存在版本差异,应按照当前 Oscar 版本和支付插件的文档实现。
建议的排查顺序
- 查看
302响应的Location。 - 使用
resolve()确认实际执行的视图类。 - 检查
get_available_payment_methods()返回的是零个、一个还是多个支付方式。 - 清除浏览器 session,再重新完成整个结账流程。
- 确认配送地址和配送方式已经写入 checkout session。
- 检查是否有项目 URL 提前匹配了
/checkout/。 - 检查 fork 后的 checkout app 是否真正替换了 Oscar 原应用。
- 最后检查支付插件及
PaymentDetailsView的实现。
如果重定向目标是 payment details 或 preview,而且系统只有一种支付方式,这通常符合设计。如果确实需要用户选择支付方式,就应提供至少两个有效方式,并确认自定义 PaymentMethodView 已被 Oscar 实际加载。
备注:内容仅供参考。
