PAYATHON 2026

如何移除 Sylius checkout 的 Adressing 和 Shipping 步骤

支付小周

结论

这个报错与 checkout_resolver 无关。直接原因是路由配置引用了一个不存在的控制器方法:

_controller: sylius.controller.order:completeAction

Sylius 的 checkout 完成页仍由通用的资源更新动作处理,应改为:

_controller: sylius.controller.order:updateAction

结账状态迁移则由 _sylius.state_machine.transition: complete 执行。complete 页面还应使用适合订单确认阶段的 repository 方法,例如当前版本提供的 findCartForSummary。不要将购物车对象直接传给通常接收 ID 的 find()。

正确的路由配置

以下配置适用于使用 winzou_state_machine、sylius.controller.order:updateAction 和旧式 Bundle 模板路径的 Sylius 版本。

# app/Resources/SyliusShopBundle/config/routing/checkout.yml

sylius_shop_checkout_start:
    path: /
    methods: [GET]
    defaults:
        _controller: FrameworkBundle:Redirect:redirect
        route: sylius_shop_checkout_select_payment

sylius_shop_checkout_select_payment:
    path: /select-payment
    methods: [GET, PUT]
    defaults:
        _controller: sylius.controller.order:updateAction
        _sylius:
            event: payment
            flash: false
            template: SyliusShopBundle:Checkout:selectPayment.html.twig
            repository:
                method: findCartForSelectingPayment
                arguments:
                    - "expr:service('sylius.context.cart').getCart().getId()"
            state_machine:
                graph: sylius_order_checkout
                transition: select_payment
            redirect:
                route: sylius_shop_checkout_complete
            form:
                type: Sylius\Bundle\CoreBundle\Form\Type\Checkout\SelectPaymentType

sylius_shop_checkout_complete:
    path: /complete
    methods: [GET, PUT]
    defaults:
        _controller: sylius.controller.order:updateAction
        _sylius:
            event: complete
            flash: false
            template: SyliusShopBundle:Checkout:complete.html.twig
            repository:
                method: findCartForSummary
                arguments:
                    - "expr:service('sylius.context.cart').getCart().getId()"
            state_machine:
                graph: sylius_order_checkout
                transition: complete
            redirect:
                route: sylius_shop_order_pay
                parameters:
                    tokenValue: resource.tokenValue
            form:
                type: Sylius\Bundle\CoreBundle\Form\Type\Checkout\CompleteType
                options:
                    validation_groups: sylius_checkout_complete

其中最重要的是修改控制器:

_controller: sylius.controller.order:updateAction

同时调整 repository 配置:

repository:
    method: findCartForSummary
    arguments:
        - "expr:service('sylius.context.cart').getCart().getId()"

如果当前 Sylius 版本没有 findCartForSummary(),需要检查该版本的 OrderRepository,选择能够加载当前购物车及其支付、配送等关联数据的方法。不同 Sylius 版本的 repository API 并不完全相同。

checkout_resolver 配置

YAML 缩进必须正确,checkout_resolver 应位于 sylius_shop 下:

# app/config/config.yml

sylius_shop:
    checkout_resolver:
        pattern: /checkout/.+
        route_map:
            cart:
                route: sylius_shop_checkout_select_payment
            payment_selected:
                route: sylius_shop_checkout_complete
            payment_skipped:
                route: sylius_shop_checkout_complete

调整后的流程如下:

cart
  -> sylius_shop_checkout_select_payment
  -> select_payment transition
payment_selected
  -> sylius_shop_checkout_complete
  -> complete transition
completed

是否保留 payment_skipped,取决于项目是否允许零金额订单或无须支付的订单。如果订单可能进入 payment_skipped,就要将该状态映射到 complete 页面,否则 resolver 可能找不到下一步路由。

state machine 配置

删除 addressing 和 shipping 后,最小状态机可以保留以下状态和迁移:

# app/Resources/SyliusCoreBundle/config/app/state_machine/sylius_order_checkout.yml

winzou_state_machine:
    sylius_order_checkout:
        class: "%sylius.model.order.class%"
        property_path: checkoutState
        graph: sylius_order_checkout
        state_machine_class: "%sylius.state_machine.class%"

        states:
            cart: ~
            payment_selected: ~
            payment_skipped: ~
            completed: ~

        transitions:
            select_payment:
                from: [cart, payment_selected]
                to: payment_selected

            complete:
                from: [payment_selected, payment_skipped]
                to: completed

        callbacks:
            after:
                sylius_process_cart:
                    on: ["select_payment"]
                    do: ["@sylius.order_processing.order_processor", "process"]
                    args: ["object"]

                sylius_create_order:
                    on: ["complete"]
                    do: ["@sm.callback.cascade_transition", "apply"]
                    args: ["object", "event", "'create'", "'sylius_order'"]

                sylius_save_checkout_completion_date:
                    on: ["complete"]
                    do: ["object", "completeCheckout"]

最好以项目当前安装版本自带的 sylius_order_checkout 配置为基础,只调整 states、transitions 和必要的回调触发条件。各版本的回调名称和参数可能不同,直接复制其他版本的整份状态机配置,反而容易产生新的错误。

还要检查 completeCheckout() 的方法签名。如果该方法不接收参数,就不能配置:

args: ["object"]

否则即使控制器问题已经修复,完成订单时仍可能出现参数数量错误。

为什么必须使用 updateAction

OrderController 是资源控制器,checkout 的完成操作并不是由名为 completeAction() 的专用方法处理。实际流程是:

  1. updateAction 加载当前购物车;
  2. 创建并处理 CompleteType 表单;
  3. 表单有效后执行 complete 状态迁移;
  4. 触发状态机中的订单创建、完成时间等回调;
  5. 根据 _sylius.redirect 跳转到支付页或后续页面。

所以,complete 是状态机 transition 的名称,不是控制器 action 的名称。配置成 completeAction 后,Symfony 会尝试调用一个不存在的方法,因此报错:

Expected method "completeAction" on class
"Sylius\Bundle\CoreBundle\Controller\OrderController"

表单导航也要提交正确的方法

select-payment 页面上的“下一步”按钮应提交当前表单,由 PUT /select-payment 执行 select_payment transition,不能用普通链接直接打开 /complete。

示意写法如下:

<button type="submit" class="ui large primary icon labeled button">
    <i class="arrow right icon"></i>
    {{ 'sylius.ui.next'|trans }}
</button>

如果用户直接访问 /checkout/complete,但订单状态仍是 cart,resolver 应将用户送回 select-payment。只有支付方式成功提交,订单状态变为 payment_selected 后,complete 页面才是合法的下一步。

移除步骤不等于移除业务约束

修改路由和状态机只能跳过页面,并不会自动补齐缺少的地址和配送数据。还需要根据业务类型确认:

  • 订单是否包含需要配送的实体商品;
  • shipment 是否仍会由订单处理器创建;
  • 是否必须填写 billing/shipping address;
  • 支付方式规则是否依赖配送渠道或地址;
  • sylius_checkout_complete 验证组是否要求地址或配送方式。

如果销售虚拟商品,可以在创建购物车或处理订单时预先设置所需数据,也可以根据这种订单类型调整验证和处理逻辑。如果销售实体商品,通常不能只删除页面,还要提供默认地址、默认配送方式,或者实现自动选择配送方式的订单处理器。

最后要保证三部分互相匹配:resolver 引用的状态必须存在于 state machine 中,路由声明的 transition 必须能从当前状态执行,控制器则统一使用实际可调用的 updateAction。

备注:内容仅供参考。