选择 offline payment 后如何将 Sylius 订单标记为 paid?
结论
不要直接修改订单的 paymentState,也不要依赖 sylius_order_payment 的 complete 回调来完成线下付款。
应当对订单关联的 Payment 实体执行 sylius_payment 状态机的 complete transition。付款完成后,Sylius 会根据订单中各笔付款的状态重新计算订单的付款状态。已完成的付款覆盖全部应付金额后,订单会变为 paid。
为什么原配置没有触发
这里有两个不同层次的状态机:
sylius_payment:管理单笔Payment,状态包括new、processing、completed、failed。sylius_order_payment:管理订单汇总后的付款状态,包括awaiting_payment、partially_paid、paid。
sylius_order_payment 会参与处理,但它负责维护订单汇总后的付款状态。选择 offline payment method 只会创建付款或确定付款方式,不会自动把对应的 Payment 标记为 completed,所以没有事件推动订单进入 paid。
付款完成所用的 transition 名称是 complete,它属于 sylius_payment graph。订单的汇总付款状态通常由 pay 等内部 transition 更新,不能把单笔付款状态机和订单付款汇总状态机混为一谈。
正确完成线下付款
确认收到线下款项后,取得订单对应的 Payment,再执行付款状态机的 complete transition:
<?php
namespace AppBundle\Payment;
use SM\Factory\FactoryInterface;
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Payment\PaymentTransitions;
final class OfflinePaymentCompleter
{
private $stateMachineFactory;
public function __construct(FactoryInterface $stateMachineFactory)
{
$this->stateMachineFactory = $stateMachineFactory;
}
public function complete(OrderInterface $order): void
{
$payment = $order->getLastPayment();
if (null === $payment) {
throw new \RuntimeException('The order does not have a payment.');
}
$stateMachine = $this->stateMachineFactory->get(
$payment,
PaymentTransitions::GRAPH
);
if (!$stateMachine->can(PaymentTransitions::TRANSITION_COMPLETE)) {
throw new \LogicException(sprintf(
'Payment cannot be completed from state "%s".',
$payment->getState()
));
}
$stateMachine->apply(PaymentTransitions::TRANSITION_COMPLETE);
}
}
服务配置可以这样写:
services:
app.payment.offline_payment_completer:
class: AppBundle\Payment\OfflinePaymentCompleter
arguments:
- '@sm.factory'
调用后,还需要通过当前请求中的 Doctrine manager 执行 flush():
$this->offlinePaymentCompleter->complete($order);
$this->orderManager->flush();
具体注入 ObjectManager、订单 manager,还是其他持久化服务,可按项目现有结构选择。
如果确实需要 state-machine callback
如果需要在某笔付款完成后执行额外的业务逻辑,应把 callback 配置在 sylius_payment,而不是 sylius_order_payment:
winzou_state_machine:
sylius_payment:
callbacks:
after:
app_after_offline_payment_completed:
on: ['complete']
do:
- '@AppBundle\Payment\StateMachine\Callback\CallbackClass'
- 'afterPaymentCompleted'
args: ['object']
这里传入的 object 是 Payment,不是 Order:
<?php
namespace AppBundle\Payment\StateMachine\Callback;
use Sylius\Component\Payment\Model\PaymentInterface;
final class CallbackClass
{
public function afterPaymentCompleted(PaymentInterface $payment): void
{
$order = $payment->getOrder();
// 在这里执行额外业务逻辑。
// 通常不需要手动调用 $order->setPaymentState('paid')。
}
}
这个 callback 只能响应已经发生的 complete transition,不能让付款自动进入 completed。控制器、命令、后台操作或事件监听器仍需显式执行该 transition。
应该在什么时候执行
offline payment 通常用于银行转账、现金、支票等需要异步确认的付款方式。客户选择这种支付方式,并不表示款项已经到账。更稳妥的流程是:
- 客户完成结账,订单进入等待付款状态。
- 后台人员或外部对账流程确认款项到账。
- 对相应的
Payment执行PaymentTransitions::TRANSITION_COMPLETE。 - Sylius 把订单的付款汇总状态更新为
paid。
如果业务明确规定订单创建后立即视为已付款,可以在结账完成后的事件监听器中调用上述 completer。但不建议监听过早触发的通用 Order created 事件,因为此时付款对象、支付方式或订单金额可能还没有完整确定。可用的结账完成事件名称,应以项目实际安装的 Sylius 1.2.x 配置为准。
注意事项
- 不要直接调用
$order->setPaymentState('paid'),否则会绕过付款状态机、状态校验及相关回调。 - 不要把选择 offline payment method 当作付款成功,除非业务规则明确如此规定。
- 一个订单可能有多笔付款。只有已完成的付款覆盖订单全部应付金额后,订单才应进入
paid。 - 执行 transition 前应调用
can(),以免重复完成付款,或从不允许的状态强制转换。 - 如果付款完成后订单没有更新为
paid,应检查付款金额、付款所属订单、其他付款记录,以及项目是否覆盖了 Sylius 默认的状态机 callback 配置。
备注:内容仅供参考。
