Files
juwan-frontend/lib/api/orders.ts
T

159 lines
4.2 KiB
TypeScript

import { allow, deny } from "@/lib/decision"
import { isApiError, toApiError, type ApiDecision } from "@/lib/errors"
import type { Order, OrderStatus } from "@/lib/types"
import { httpJson } from "./http"
type Paginated<T> = {
items: T[] | null
meta: {
total: number
offset: number
limit: number
}
}
export type ListOrdersOptions = {
role?: "consumer" | "player" | "owner"
status?: OrderStatus
offset?: number
limit?: number
}
type ActionResult = { decision: ApiDecision; order?: Order }
function withOffsetLimit(path: string, options?: ListOrdersOptions): string {
const offset = options?.offset ?? 0
const limit = options?.limit ?? 100
const searchParams = new URLSearchParams({
offset: String(offset),
limit: String(limit),
})
if (options?.role) searchParams.set("role", options.role)
if (options?.status) searchParams.set("status", options.status)
return `${path}?${searchParams.toString()}`
}
function unwrapOrder(value: unknown): Order | undefined {
if (typeof value !== "object" || value === null) return undefined
if ("order" in value) {
const envelope = value as { order?: Order }
return envelope.order
}
return value as Order
}
function denyFromError(error: unknown): ApiDecision {
if (error instanceof Error && error.message === "UNAUTHORIZED") {
return deny(401, "请先登录")
}
const apiError = toApiError(error)
return deny(apiError.code, apiError.msg)
}
export async function listOrders(options?: ListOrdersOptions): Promise<Order[]> {
const res = await httpJson<Paginated<Order>>(withOffsetLimit("/api/v1/orders", options), {
cache: "no-store",
})
return res.items ?? []
}
export async function getOrderById(orderId: string): Promise<Order | undefined> {
try {
const res = await httpJson<unknown>(`/api/v1/orders/${encodeURIComponent(orderId)}`, {
cache: "no-store",
})
return unwrapOrder(res)
} catch (error) {
if (error instanceof Error && error.message === "UNAUTHORIZED") {
throw error
}
if (isApiError(error) && error.code === 404) {
return undefined
}
throw error
}
}
interface CreatePaidOrderInput {
playerId: string
serviceId: string
shopId?: string
quantity: number
note?: string
}
function createOrderJson(input: CreatePaidOrderInput) {
return {
playerId: input.playerId,
serviceId: input.serviceId,
...(input.shopId ? { shopId: input.shopId } : {}),
quantity: input.quantity,
...(input.note ? { note: input.note } : {}),
}
}
export async function createPaidOrder(input: CreatePaidOrderInput): Promise<ActionResult> {
try {
const res = await httpJson<unknown>("/api/v1/orders/paid", {
method: "POST",
cache: "no-store",
json: createOrderJson(input),
})
const order = unwrapOrder(res)
if (!order) {
return { decision: deny(500, "订单创建失败") }
}
return { decision: allow(), order }
} catch (error) {
return { decision: denyFromError(error) }
}
}
async function postOrderAction(orderId: string, action: string): Promise<ActionResult> {
try {
const res = await httpJson<unknown>(`/api/v1/orders/${encodeURIComponent(orderId)}/${action}`, {
method: "POST",
cache: "no-store",
})
const order = unwrapOrder(res)
if (order) {
return { decision: allow(), order }
}
const refetched = await getOrderById(orderId).catch(() => undefined)
if (refetched) {
return { decision: allow(), order: refetched }
}
return { decision: allow() }
} catch (error) {
return { decision: denyFromError(error) }
}
}
export async function payOrder(orderId: string): Promise<ActionResult> {
return postOrderAction(orderId, "pay")
}
export async function acceptOrder(orderId: string): Promise<ActionResult> {
return postOrderAction(orderId, "accept")
}
export async function requestClose(orderId: string): Promise<ActionResult> {
return postOrderAction(orderId, "request-close")
}
export async function confirmClose(orderId: string): Promise<ActionResult> {
return postOrderAction(orderId, "confirm-close")
}
export async function cancelPreAccept(orderId: string): Promise<ActionResult> {
return postOrderAction(orderId, "cancel")
}