mirror of
https://github.com/crmeb/CRMEB.git
synced 2026-10-28 21:40:50 +00:00
fix(网盘商品): 更新订单状态流程,资源发放后进入待评价,调整退款规则以反映新状态
This commit is contained in:
parent
46ab4571e5
commit
28aab2e006
@ -25,7 +25,7 @@ use modules\product\virtual\dao\VirtualOrderDao;
|
||||
/**
|
||||
* 网盘产品处理器
|
||||
* 网盘产品无需物流,支付后自动发放网盘链接与提取码
|
||||
* 状态流程:待付款 → 待发货(自动) → 已完成
|
||||
* 状态流程:待付款 → 自动发放 → 待评价 → 已完成
|
||||
*/
|
||||
class Handler extends BaseProductTypeHandler
|
||||
{
|
||||
@ -292,7 +292,7 @@ class Handler extends BaseProductTypeHandler
|
||||
|
||||
/**
|
||||
* 支付成功履约
|
||||
* 网盘商品:自动发货,更新状态为已完成,并同步中台状态
|
||||
* 网盘商品:自动发货,更新状态为待评价,并同步中台状态
|
||||
*/
|
||||
public function afterPay(string $orderNo, array $cartInfoList): void
|
||||
{
|
||||
@ -307,23 +307,32 @@ class Handler extends BaseProductTypeHandler
|
||||
if (!$order || in_array($order['status'], [VirtualOrderAdapter::EXT_STATUS_CANCELED, VirtualOrderAdapter::EXT_STATUS_REFUNDED], true)) {
|
||||
throw new \RuntimeException('订单当前状态不可发放网盘资源');
|
||||
}
|
||||
if ($order['status'] === VirtualOrderAdapter::EXT_STATUS_COMPLETED) {
|
||||
// 支付回调可能重复到达;资源已发放的订单保持当前状态不重复处理。
|
||||
if (in_array($order['status'], [
|
||||
VirtualOrderAdapter::EXT_STATUS_PENDING_REVIEW,
|
||||
VirtualOrderAdapter::EXT_STATUS_COMPLETED,
|
||||
], true)) {
|
||||
return;
|
||||
}
|
||||
if (empty($cartInfoList)) {
|
||||
throw new \RuntimeException('订单网盘资源快照缺失,暂不能完成订单');
|
||||
throw new \RuntimeException('订单网盘资源快照缺失,暂不能发放资源');
|
||||
}
|
||||
foreach ($cartInfoList as $item) {
|
||||
$attrInfo = $item['attr_info'] ?? [];
|
||||
if (empty($attrInfo['download_url'])) {
|
||||
throw new \RuntimeException('网盘商品未配置下载链接,暂不能完成订单');
|
||||
throw new \RuntimeException('网盘商品未配置下载链接,暂不能发放资源');
|
||||
}
|
||||
}
|
||||
$fulfilledAt = time();
|
||||
$orderDao->update(['order_no' => $orderNo], [
|
||||
'status' => VirtualOrderAdapter::EXT_STATUS_COMPLETED,
|
||||
'complete_time' => time(),
|
||||
'status' => VirtualOrderAdapter::EXT_STATUS_PENDING_REVIEW,
|
||||
'send_time' => (int)($order['send_time'] ?? 0) ?: $fulfilledAt,
|
||||
]);
|
||||
// 虚拟商品自动发放等同于发货和收货,写入时间后自动评价任务才能识别待评价订单。
|
||||
$centerServices->syncStatus($orderNo, VirtualOrderAdapter::STATUS_PENDING_REVIEW, [
|
||||
'delivery_time' => (int)($center['delivery_time'] ?? 0) ?: $fulfilledAt,
|
||||
'take_time' => (int)($center['take_time'] ?? 0) ?: $fulfilledAt,
|
||||
]);
|
||||
$centerServices->syncStatus($orderNo, 'completed');
|
||||
});
|
||||
}
|
||||
|
||||
@ -578,7 +587,7 @@ class Handler extends BaseProductTypeHandler
|
||||
|
||||
/**
|
||||
* 订单履约
|
||||
* 网盘商品:更新状态为已完成,并同步中台状态
|
||||
* 网盘商品:更新状态为待评价,并同步中台状态
|
||||
* @param string $orderNo 订单号
|
||||
* @param array $orderInfo 订单完整信息
|
||||
* @return bool
|
||||
@ -587,14 +596,18 @@ class Handler extends BaseProductTypeHandler
|
||||
{
|
||||
try {
|
||||
$orderDao = app(VirtualOrderDao::class);
|
||||
$fulfilledAt = time();
|
||||
$orderDao->update(['order_no' => $orderNo], [
|
||||
'status' => VirtualOrderAdapter::EXT_STATUS_COMPLETED,
|
||||
'complete_time' => time(),
|
||||
'status' => VirtualOrderAdapter::EXT_STATUS_PENDING_REVIEW,
|
||||
'send_time' => $fulfilledAt,
|
||||
]);
|
||||
|
||||
// 网盘商品履约即完成,同步中台订单状态
|
||||
// 网盘商品履约后进入待评价,同步中台订单状态。
|
||||
$centerServices = app(\app\services\order\OrderCenterServices::class);
|
||||
$centerServices->syncStatus($orderNo, 'completed');
|
||||
$centerServices->syncStatus($orderNo, VirtualOrderAdapter::STATUS_PENDING_REVIEW, [
|
||||
'delivery_time' => $fulfilledAt,
|
||||
'take_time' => $fulfilledAt,
|
||||
]);
|
||||
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
|
||||
@ -18,7 +18,7 @@ use modules\product\virtual\dao\VirtualOrderDao;
|
||||
* 网盘商品售后处理器
|
||||
* 提供售后规则定义与生命周期回调
|
||||
*
|
||||
* 规则:仅支持退款(refund_only),支付后 24 小时内可申请,已发放(完成)后不支持
|
||||
* 规则:仅支持退款(refund_only),支付后 24 小时内可申请,资源已发放(待评价/已完成)后不支持
|
||||
*/
|
||||
class VirtualAfterSaleHandler extends BaseAfterSaleHandler
|
||||
{
|
||||
@ -39,7 +39,7 @@ class VirtualAfterSaleHandler extends BaseAfterSaleHandler
|
||||
|
||||
/**
|
||||
* 获取售后规则
|
||||
* 网盘商品仅支持退款:未发放前可退,已发放(completed)不可退
|
||||
* 网盘商品仅支持退款:未发放前可退,已发放(pending_review/completed)不可退
|
||||
* @param array $orderCenter 中台订单数据
|
||||
* @param array $sourceOrder 源表订单数据
|
||||
* @return array
|
||||
|
||||
@ -11,14 +11,17 @@ use modules\product\virtual\model\VirtualOrderModel;
|
||||
/**
|
||||
* 网盘商品订单流转定义
|
||||
*
|
||||
* 网盘商品:无需物流,支付后自动发货即完成
|
||||
* 状态流程:pending_payment → completed(直达终态,无中间环节)
|
||||
* 网盘商品:无需物流,支付后自动发货,进入待评价
|
||||
* 状态流程:pending_payment → pending_review → completed
|
||||
* 任意节点可触发:canceled(取消)/ refunded(退款)
|
||||
*
|
||||
* ═══════════════ 完整状态流转图 ═══════════════
|
||||
*
|
||||
* pending_payment 待支付 ──────→ 中台 pending_payment
|
||||
* │ 支付成功 → Event → Listener → Flow::advance('completed')
|
||||
* │ 支付成功 → Event → Listener → Flow::advance('pending_review')
|
||||
* ▼
|
||||
* pending_review 待评价 ──────→ 中台 pending_review
|
||||
* │ 评价完成 → OrderCenterServices::checkOrderOver()
|
||||
* ▼
|
||||
* completed 已完成 ──────→ 中台 completed
|
||||
*
|
||||
@ -33,7 +36,9 @@ class VirtualOrderAdapter extends BaseOrderTypeAdapter
|
||||
|
||||
/** 待支付 — 订单刚创建 */
|
||||
public const EXT_STATUS_PENDING_PAYMENT = 'pending_payment';
|
||||
/** 已完成 — 支付后即完成(网盘商品无物流环节) */
|
||||
/** 待评价 — 资源已自动发放,等待用户评价 */
|
||||
public const EXT_STATUS_PENDING_REVIEW = 'pending_review';
|
||||
/** 已完成 — 用户完成评价 */
|
||||
public const EXT_STATUS_COMPLETED = 'completed';
|
||||
/** 已取消 — 订单已取消 */
|
||||
public const EXT_STATUS_CANCELED = 'canceled';
|
||||
@ -79,10 +84,20 @@ class VirtualOrderAdapter extends BaseOrderTypeAdapter
|
||||
Button::primary('去支付', 'payOrder'),
|
||||
]),
|
||||
|
||||
// ───── 待评价 ─────
|
||||
OrderFlowNode::make(self::EXT_STATUS_PENDING_REVIEW)
|
||||
->centerStatus(self::STATUS_PENDING_REVIEW)
|
||||
->label('待评价')
|
||||
->adminButtons([])
|
||||
->userButtons([
|
||||
Button::primary('去评价', 'goComment'),
|
||||
]),
|
||||
|
||||
// ───── 已完成(终态) ─────
|
||||
OrderFlowNode::make(self::EXT_STATUS_COMPLETED)
|
||||
->centerStatus(self::STATUS_COMPLETED)
|
||||
->label('已完成')
|
||||
->onEnter('onCompleted')
|
||||
->adminButtons([])
|
||||
->userButtons([
|
||||
Button::make('申请售后', 'applyAfterSale'),
|
||||
@ -125,7 +140,7 @@ class VirtualOrderAdapter extends BaseOrderTypeAdapter
|
||||
|
||||
/**
|
||||
* 推导统一状态码
|
||||
* 优先级:取消 > 退款完成 > 售后中 > 未支付 > 已支付→已完成
|
||||
* 优先级:取消 > 退款完成 > 售后中 > 未支付 > 扩展状态映射
|
||||
*/
|
||||
public function resolveStatusCode(array $sourceOrder): string
|
||||
{
|
||||
@ -139,13 +154,16 @@ class VirtualOrderAdapter extends BaseOrderTypeAdapter
|
||||
if ($refundStatus === 1) {
|
||||
return self::STATUS_AFTER_SALE;
|
||||
}
|
||||
// 扩展表不一定携带 paid 字段,网盘商品 status=completed 即视为已支付
|
||||
$status = (string)($sourceOrder['status'] ?? '');
|
||||
// 扩展表不一定携带 paid 字段,资源已发放的状态均视为已支付
|
||||
$isPaid = (bool)($sourceOrder['paid'] ?? 0)
|
||||
|| ($sourceOrder['status'] ?? '') === self::EXT_STATUS_COMPLETED;
|
||||
|| in_array($status, [self::EXT_STATUS_PENDING_REVIEW, self::EXT_STATUS_COMPLETED], true);
|
||||
if (!$isPaid) {
|
||||
return self::STATUS_PENDING_PAYMENT;
|
||||
}
|
||||
return self::STATUS_COMPLETED;
|
||||
return $status === self::EXT_STATUS_PENDING_REVIEW
|
||||
? self::STATUS_PENDING_REVIEW
|
||||
: self::STATUS_COMPLETED;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -159,8 +177,9 @@ class VirtualOrderAdapter extends BaseOrderTypeAdapter
|
||||
$payPrice = (float)($sourceOrder['pay_price'] ?? 0);
|
||||
return $refundPrice >= $payPrice ? self::PAY_FULL_REFUND : self::PAY_PARTIAL_REFUND;
|
||||
}
|
||||
$status = (string)($sourceOrder['status'] ?? '');
|
||||
$isPaid = (bool)($sourceOrder['paid'] ?? 0)
|
||||
|| ($sourceOrder['status'] ?? '') === self::EXT_STATUS_COMPLETED;
|
||||
|| in_array($status, [self::EXT_STATUS_PENDING_REVIEW, self::EXT_STATUS_COMPLETED], true);
|
||||
return $isPaid ? self::PAY_PAID : self::PAY_UNPAID;
|
||||
}
|
||||
|
||||
@ -174,7 +193,7 @@ class VirtualOrderAdapter extends BaseOrderTypeAdapter
|
||||
|
||||
/**
|
||||
* 推导业务状态
|
||||
* 网盘商品无中间业务状态,已支付即已完成
|
||||
* 网盘商品通过统一状态码表达待评价/已完成,不额外注入业务 Badge
|
||||
*/
|
||||
public function resolveBusinessStatus(array $sourceOrder): ?array
|
||||
{
|
||||
@ -191,7 +210,7 @@ class VirtualOrderAdapter extends BaseOrderTypeAdapter
|
||||
|
||||
/**
|
||||
* 网盘商品订单生命周期进度条
|
||||
* 节点:提交订单 → 支付成功 → 订单完成(网盘商品无物流)
|
||||
* 节点:提交订单 → 支付成功 → 待评价 → 订单完成
|
||||
*/
|
||||
public function getProgress(array $detail): array
|
||||
{
|
||||
@ -199,12 +218,14 @@ class VirtualOrderAdapter extends BaseOrderTypeAdapter
|
||||
$isCancel = $statusCode === self::STATUS_CANCELED;
|
||||
$isRefunded = $statusCode === self::STATUS_REFUNDED;
|
||||
$isAfterSale = $statusCode === self::STATUS_AFTER_SALE;
|
||||
$isPendingReview = $statusCode === self::STATUS_PENDING_REVIEW;
|
||||
$isCompleted = $statusCode === self::STATUS_COMPLETED;
|
||||
$isPendingPayment = $statusCode === self::STATUS_PENDING_PAYMENT;
|
||||
|
||||
$nodes = [
|
||||
['title' => '提交订单', 'key' => 'submit', 'time_field' => 'add_time'],
|
||||
['title' => '支付成功', 'key' => 'pay', 'time_field' => 'pay_time'],
|
||||
['title' => '待评价', 'key' => 'review', 'time_field' => 'take_time'],
|
||||
['title' => '订单完成', 'key' => 'complete', 'time_field' => ''],
|
||||
];
|
||||
|
||||
@ -217,9 +238,16 @@ class VirtualOrderAdapter extends BaseOrderTypeAdapter
|
||||
unset($node);
|
||||
|
||||
// 已完成节点由状态码决定,不能仅凭 pay_time 判断
|
||||
// 待评价节点时间取自动发放时间;兼容历史订单没有 take_time 的情况,使用发货时间/支付时间兜底
|
||||
$reviewTime = (int)($detail['take_time'] ?? 0)
|
||||
?: (int)($detail['delivery_time'] ?? 0)
|
||||
?: (int)($detail['send_time'] ?? 0)
|
||||
?: (int)($detail['pay_time'] ?? 0);
|
||||
$nodes[2]['time'] = $reviewTime ? date('Y-m-d H:i:s', $reviewTime) : '';
|
||||
$nodes[2]['completed'] = $isCompleted;
|
||||
$nodes[3]['completed'] = $isCompleted;
|
||||
if ($isCompleted) {
|
||||
$nodes[2]['time'] = $detail['pay_time'] ? date('Y-m-d H:i:s', (int)$detail['pay_time']) : '';
|
||||
$nodes[3]['time'] = $reviewTime ? date('Y-m-d H:i:s', $reviewTime) : '';
|
||||
}
|
||||
|
||||
// 待支付状态:支付/完成节点不应显示已完成,避免脏数据误导
|
||||
@ -228,6 +256,8 @@ class VirtualOrderAdapter extends BaseOrderTypeAdapter
|
||||
$nodes[1]['time'] = '';
|
||||
$nodes[2]['completed'] = false;
|
||||
$nodes[2]['time'] = '';
|
||||
$nodes[3]['completed'] = false;
|
||||
$nodes[3]['time'] = '';
|
||||
}
|
||||
|
||||
$current = -1;
|
||||
@ -236,6 +266,8 @@ class VirtualOrderAdapter extends BaseOrderTypeAdapter
|
||||
} elseif ($isRefunded || $isAfterSale) {
|
||||
$current = 1;
|
||||
} elseif ($isCompleted) {
|
||||
$current = 3;
|
||||
} elseif ($isPendingReview) {
|
||||
$current = 2;
|
||||
} elseif ($isPendingPayment) {
|
||||
// 待支付状态以状态码为准,不依赖时间字段
|
||||
@ -271,10 +303,10 @@ class VirtualOrderAdapter extends BaseOrderTypeAdapter
|
||||
|
||||
public function getStatusButtons(string $statusCode, array $detail = []): array
|
||||
{
|
||||
// 网盘商品理论上不存在 processing 状态,但过渡期内部分旧数据/脏数据可能残留
|
||||
// 将 processing 状态按 completed 处理,保证用户端能看到操作按钮
|
||||
// 网盘商品理论上不存在 processing 状态,但过渡期内部分旧数据/脏数据可能残留。
|
||||
// 已支付的旧 processing 订单按待评价处理,避免跳过评价。
|
||||
if ($statusCode === self::STATUS_PROCESSING) {
|
||||
$statusCode = self::STATUS_COMPLETED;
|
||||
$statusCode = self::STATUS_PENDING_REVIEW;
|
||||
}
|
||||
|
||||
$node = $this->centerStatusMap[$statusCode][0] ?? null;
|
||||
@ -283,4 +315,16 @@ class VirtualOrderAdapter extends BaseOrderTypeAdapter
|
||||
}
|
||||
return $this->extStatusMap[$statusCode]['user_buttons'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 评价完成后记录扩展表完成时间。
|
||||
* 状态推进由订单中台统一触发,这里只维护网盘扩展表自己的时间字段。
|
||||
*/
|
||||
protected function onCompleted(string $orderNo, array $extra = []): void
|
||||
{
|
||||
app()->make(VirtualOrderModel::class)->db()
|
||||
->where('order_no', $orderNo)
|
||||
->where('complete_time', 0)
|
||||
->update(['complete_time' => time()]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,8 +41,8 @@ class OrderPaidListener
|
||||
}
|
||||
|
||||
$statusCode = $meta['status_code'] ?? '';
|
||||
// 幂等:已经是终态的订单不再重复处理
|
||||
if (in_array($statusCode, ['completed', 'refunded', 'canceled'], true)) {
|
||||
// 幂等:资源已发放或订单已结束时不再重复处理
|
||||
if (in_array($statusCode, ['pending_review', 'completed', 'refunded', 'canceled'], true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@ class OrderPaidListener
|
||||
$lifecycleServices = app(\app\services\order\OrderLifecycleServices::class);
|
||||
$lifecycleServices->handlePaySuccess($orderInfo);
|
||||
|
||||
// 3. 构建购物车信息并调用类型 Handler 完成履约(扩展表更新为 completed,并同步中台为 completed)
|
||||
// 3. 构建购物车信息并调用类型 Handler 完成履约(扩展表更新为 pending_review,并同步中台)
|
||||
$cartInfoList = $this->buildCartInfoList($center, $orderInfo);
|
||||
$handler = app(Handler::class);
|
||||
$handler->afterPay($orderNo, $cartInfoList);
|
||||
|
||||
@ -49,6 +49,6 @@ class OrderCreatePipeline extends AbstractOrderCreatePipeline
|
||||
// TODO: 迁移网盘商品自动发货逻辑
|
||||
// 1. 获取商品下载链接
|
||||
// 2. 发送给用户(站内信/模板消息)
|
||||
// 3. 更新订单状态为已完成
|
||||
// 3. 更新订单状态为待评价,评价完成后再进入已完成
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ use modules\product\virtual\dao\VirtualOrderDao;
|
||||
/**
|
||||
* 网盘商品退款服务
|
||||
* 退款处理业务逻辑
|
||||
* 网盘商品退款规则:已完成不可退
|
||||
* 网盘商品退款规则:资源发放后不可退
|
||||
*/
|
||||
class RefundServices
|
||||
{
|
||||
@ -32,7 +32,7 @@ class RefundServices
|
||||
/**
|
||||
* 退款校验
|
||||
* 网盘商品退款规则:
|
||||
* - 已完成不可退(网盘商品支付后自动发货完成)
|
||||
* - 待评价、已完成不可退(网盘商品支付后自动发放资源)
|
||||
* - 已取消、已退款不可退
|
||||
* @param string $orderNo
|
||||
* @return bool
|
||||
@ -44,7 +44,7 @@ class RefundServices
|
||||
return false;
|
||||
}
|
||||
$status = $order['status'] ?? '';
|
||||
return !in_array($status, ['canceled', 'refunded', 'completed']);
|
||||
return !in_array($status, ['canceled', 'refunded', 'pending_review', 'completed'], true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user