Files

140 lines
3.8 KiB
TypeScript

import { allow, deny } from "@/lib/decision"
import { isApiError, toApiError, type ApiDecision } from "@/lib/errors"
import type { Dispute } from "@/lib/types"
import { httpJson } from "./http"
export type ListDisputesOptions = {
offset?: number
limit?: number
}
type Paginated<T> = {
items: T[] | null
meta?: {
total: number
offset: number
limit: number
}
}
function withOffsetLimit(path: string, options?: ListDisputesOptions): string {
const offset = options?.offset ?? 0
const limit = options?.limit ?? 100
const searchParams = new URLSearchParams({
offset: String(offset),
limit: String(limit),
})
return `${path}?${searchParams.toString()}`
}
function unwrapItems<T>(value: unknown): T[] {
if (Array.isArray(value)) return value as T[]
if (typeof value !== "object" || value === null) throw new Error("Invalid response")
if ("items" in value) {
const envelope = value as { items?: unknown }
if (Array.isArray(envelope.items)) return envelope.items as T[]
if (envelope.items === null) return []
}
throw new Error("Invalid response")
}
function unwrapDispute(value: unknown): unknown {
if (typeof value !== "object" || value === null) return value
if ("dispute" in value) {
const envelope = value as { dispute?: unknown }
return envelope.dispute
}
return value
}
function normalizeDispute(value: unknown): Dispute {
const dispute = unwrapDispute(value) as Dispute
return {
...dispute,
evidence: Array.isArray(dispute.evidence) ? dispute.evidence : [],
respondentEvidence: Array.isArray(dispute.respondentEvidence) ? dispute.respondentEvidence : [],
}
}
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 listDisputes(options?: ListDisputesOptions): Promise<Dispute[]> {
const res = await httpJson<Paginated<Dispute> | Dispute[]>(
withOffsetLimit("/api/v1/disputes", options),
{ cache: "no-store" },
)
return unwrapItems<Dispute>(res).map((item) => normalizeDispute(item))
}
export async function getDisputeByOrderId(orderId: string): Promise<Dispute | undefined> {
try {
const res = await httpJson<unknown>(`/api/v1/orders/${encodeURIComponent(orderId)}/dispute`, {
cache: "no-store",
})
return normalizeDispute(res)
} catch (error) {
const apiError = isApiError(error) ? error : toApiError(error)
if (apiError.code === 404) return undefined
throw error
}
}
export async function submitDispute(input: {
orderId: string
reason: string
evidence: string[]
}): Promise<{ decision: ApiDecision }> {
try {
await httpJson<unknown>(`/api/v1/orders/${encodeURIComponent(input.orderId)}/dispute`, {
method: "POST",
cache: "no-store",
json: { reason: input.reason, evidence: input.evidence },
})
return { decision: allow() }
} catch (error) {
return { decision: denyFromError(error) }
}
}
export async function submitDisputeResponse(input: {
disputeId: string
reason: string
evidence: string[]
}): Promise<ApiDecision> {
try {
await httpJson<unknown>(`/api/v1/disputes/${encodeURIComponent(input.disputeId)}/response`, {
method: "POST",
cache: "no-store",
json: { reason: input.reason, evidence: input.evidence },
})
return allow()
} catch (error) {
return denyFromError(error)
}
}
export async function submitDisputeAppeal(input: {
disputeId: string
reason: string
}): Promise<ApiDecision> {
try {
await httpJson<unknown>(`/api/v1/disputes/${encodeURIComponent(input.disputeId)}/appeal`, {
method: "POST",
cache: "no-store",
json: { reason: input.reason },
})
return allow()
} catch (error) {
return denyFromError(error)
}
}