refactor(order): createPaidOrder uses id references

This commit is contained in:
zetaloop
2026-02-28 07:25:52 +08:00
parent cc24a0cbc3
commit bce99c4c54
3 changed files with 109 additions and 48 deletions
+105 -35
View File
@@ -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<OrderState>((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<OrderState>((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<OrderState>((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(),
}