From bce99c4c54594914adc9df7d5021e8d839a75a10 Mon Sep 17 00:00:00 2001 From: zetaloop Date: Sat, 28 Feb 2026 07:25:52 +0800 Subject: [PATCH] refactor(order): createPaidOrder uses id references --- app/(order)/order/new/page.tsx | 8 +- lib/api/orders.ts | 9 +-- store/orders.ts | 140 ++++++++++++++++++++++++--------- 3 files changed, 109 insertions(+), 48 deletions(-) diff --git a/app/(order)/order/new/page.tsx b/app/(order)/order/new/page.tsx index ceeadd6..41b3a88 100644 --- a/app/(order)/order/new/page.tsx +++ b/app/(order)/order/new/page.tsx @@ -196,14 +196,10 @@ export default function NewOrderPage() { const result = createPaidOrder( { - consumerId: authUser.id, - consumerName: authUser.nickname, playerId: player.id, - playerName: player.user.nickname, + serviceId: service.id, shopId: player.shopId, - shopName: player.shopName, - service, - totalPrice, + quantity, note, }, actor, diff --git a/lib/api/orders.ts b/lib/api/orders.ts index 4654eae..12850fc 100644 --- a/lib/api/orders.ts +++ b/lib/api/orders.ts @@ -2,7 +2,6 @@ import type { Actor } from "@/lib/actor" import { allow, deny } from "@/lib/decision" import { resolveOwnerShop } from "@/lib/domain/resolve-current-shop" import type { ApiDecision } from "@/lib/errors" -import type { PlayerService } from "@/lib/types" import { useAuthStore } from "@/store/auth" import { useOrderStore } from "@/store/orders" import { useShopStore } from "@/store/shops" @@ -20,14 +19,10 @@ export function listOrdersByConsumer(consumerId: string) { } interface CreatePaidOrderInput { - consumerId: string - consumerName: string playerId: string - playerName: string + serviceId: string shopId?: string - shopName?: string - service: PlayerService - totalPrice: number + quantity: number note?: string } diff --git a/store/orders.ts b/store/orders.ts index 39e0ebc..9511b46 100644 --- a/store/orders.ts +++ b/store/orders.ts @@ -9,30 +9,25 @@ import { evaluateOrderTransition, type OrderAction } from "@/lib/domain/order-ma import type { ApiDecision } from "@/lib/errors" import { generateId } from "@/lib/id" import { mockOrders } from "@/lib/mock" -import type { Order, OrderStatus, PlayerService } from "@/lib/types" +import type { Order, OrderStatus } from "@/lib/types" import { useAuthStore } from "@/store/auth" import { useChatStore } from "@/store/chat" import { useNotificationStore } from "@/store/notifications" +import { usePlayerStore } from "@/store/players" +import { useServiceStore } from "@/store/services" import { useShopStore } from "@/store/shops" import { useWalletStore } from "@/store/wallet" import { create } from "zustand" interface CreateOrderInput { - consumerId: string - consumerName: string playerId: string - playerName: string + serviceId: string shopId?: string - shopName?: string - service: PlayerService - totalPrice: number + quantity: number note?: string - status?: OrderStatus } -interface CreatePaidOrderInput extends CreateOrderInput { - status?: never -} +type CreatePaidOrderInput = CreateOrderInput interface OrderMutationResult { decision: ApiDecision @@ -41,7 +36,7 @@ interface OrderMutationResult { interface OrderState { orders: Order[] - createOrder: (input: CreateOrderInput) => Order + createOrder: (input: CreateOrderInput, actor: Actor) => OrderMutationResult createPaidOrder: (input: CreatePaidOrderInput, actor: Actor) => OrderMutationResult payOrder: (orderId: string, actor: Actor) => OrderMutationResult acceptOrder: (orderId: string, actor: Actor) => OrderMutationResult @@ -307,18 +302,58 @@ export const useOrderStore = create((set, get) => { return { orders: mockOrders, - createOrder: (input) => { + createOrder: (input, actor) => { + if (actor.role !== "consumer") { + return { decision: deny(403, "仅客户可下单") } + } + + const consumer = useAuthStore.getState().user + if (!consumer || consumer.id !== actor.userId) { + return { decision: deny(403, "仅本人可下单") } + } + + const service = useServiceStore + .getState() + .services.find((item) => item.id === input.serviceId) + if (!service) { + return { decision: deny(404, "服务不存在") } + } + + const player = usePlayerStore.getState().players.find((item) => item.id === input.playerId) + if (!player) { + return { decision: deny(404, "打手不存在") } + } + + if (service.playerId !== player.id) { + return { decision: deny(400, "服务与打手不匹配") } + } + + const resolvedShopId = input.shopId ?? player.shopId + if (input.shopId && player.shopId && input.shopId !== player.shopId) { + return { decision: deny(400, "店铺信息与打手不匹配") } + } + + const resolvedShopName = resolvedShopId + ? useShopStore.getState().shops.find((item) => item.id === resolvedShopId)?.name + : undefined + + const quantity = Number.isFinite(input.quantity) ? Math.floor(input.quantity) : Number.NaN + if (!quantity || quantity < 1) { + return { decision: deny(400, "数量不合法") } + } + + const totalPrice = service.price * quantity const order: Order = { id: generateId("ord"), - consumerId: input.consumerId, - consumerName: input.consumerName, - playerId: input.playerId, - playerName: input.playerName, - shopId: input.shopId, - shopName: input.shopName, - service: input.service, - status: input.status ?? "pending_payment", - totalPrice: input.totalPrice, + consumerId: consumer.id, + consumerName: consumer.nickname, + playerId: player.id, + playerName: player.user.nickname, + shopId: resolvedShopId, + shopName: resolvedShopName, + service, + status: "pending_payment", + totalPrice, note: input.note?.trim() ? input.note.trim() : undefined, createdAt: new Date().toISOString(), } @@ -328,21 +363,56 @@ export const useOrderStore = create((set, get) => { })) scheduleOrderTimeout(order.id, order.status) - - return order + return { decision: allow(), order } }, createPaidOrder: (input, actor) => { - if (actor.role !== "consumer" || actor.userId !== input.consumerId) { + if (actor.role !== "consumer") { return { decision: deny(403, "仅客户可下单支付") } } - const dedupeKey = `${input.consumerId}-${input.service.id}` + const consumer = useAuthStore.getState().user + if (!consumer || consumer.id !== actor.userId) { + return { decision: deny(403, "仅本人可下单支付") } + } + + const service = useServiceStore + .getState() + .services.find((item) => item.id === input.serviceId) + if (!service) { + return { decision: deny(404, "服务不存在") } + } + + const player = usePlayerStore.getState().players.find((item) => item.id === input.playerId) + if (!player) { + return { decision: deny(404, "打手不存在") } + } + + if (service.playerId !== player.id) { + return { decision: deny(400, "服务与打手不匹配") } + } + + const resolvedShopId = input.shopId ?? player.shopId + if (input.shopId && player.shopId && input.shopId !== player.shopId) { + return { decision: deny(400, "店铺信息与打手不匹配") } + } + + const resolvedShopName = resolvedShopId + ? useShopStore.getState().shops.find((item) => item.id === resolvedShopId)?.name + : undefined + + const quantity = Number.isFinite(input.quantity) ? Math.floor(input.quantity) : Number.NaN + if (!quantity || quantity < 1) { + return { decision: deny(400, "数量不合法") } + } + + const totalPrice = service.price * quantity + const dedupeKey = `${consumer.id}-${service.id}` if (pendingCreations.has(dedupeKey)) { return { decision: deny(400, "订单正在创建中,请勿重复提交") } } pendingCreations.add(dedupeKey) const orderId = generateId("ord") - const paid = useWalletStore.getState().deductBalance(orderId, input.totalPrice) + const paid = useWalletStore.getState().deductBalance(orderId, totalPrice) if (!paid) { pendingCreations.delete(dedupeKey) return { decision: deny(400, "余额不足或订单已支付") } @@ -350,15 +420,15 @@ export const useOrderStore = create((set, get) => { const order: Order = { id: orderId, - consumerId: input.consumerId, - consumerName: input.consumerName, - playerId: input.playerId, - playerName: input.playerName, - shopId: input.shopId, - shopName: input.shopName, - service: input.service, + consumerId: consumer.id, + consumerName: consumer.nickname, + playerId: player.id, + playerName: player.user.nickname, + shopId: resolvedShopId, + shopName: resolvedShopName, + service, status: "pending_accept", - totalPrice: input.totalPrice, + totalPrice, note: input.note?.trim() ? input.note.trim() : undefined, createdAt: new Date().toISOString(), }