import { getApiBaseUrl, getApiEndpoint } from '../config/api.uts'
|
import { request, HttpError } from './http.uts'
|
|
export type MerchantOrder = {
|
id: string
|
orderNo: string
|
category: string
|
status: string
|
statusText: string
|
title: string
|
customerName: string
|
customerMobile: string
|
cardLevel: string
|
orderAmount: number
|
basePayAmount?: number
|
couponAmount?: number
|
talentDiscountAmount?: number
|
payAmount: number
|
subsidyAmount: number
|
createdAt: string
|
verifyTime: string
|
}
|
|
export type MerchantOrderPage = {
|
records: Array<MerchantOrder>
|
page: number
|
size: number
|
total: number
|
}
|
|
export type MerchantOrderDetail = MerchantOrder & {
|
basePayAmount: number
|
couponAmount: number
|
talentDiscountAmount: number
|
benefitName: string
|
merchantName: string
|
/** 商品图(无商品图时回落商家封面图),已补成可直接渲染的地址;都没有则为空。 */
|
productImage: string
|
canRevoke: boolean
|
revokeDeadline: string
|
revokeReason: string
|
}
|
|
export type MerchantOrderQuery = {
|
page: number
|
size: number
|
category: string
|
status: string
|
startDate: string
|
endDate: string
|
}
|
|
export type RevokeVerificationResult = {
|
orderId: string
|
orderStatus: string
|
orderStatusText: string
|
revokedAt: string
|
}
|
|
export const loadMerchantBusinessDate = (
|
onSuccess: (businessDate: string) => void,
|
onError: (error: HttpError) => void
|
): (() => void) => request<any>({ path: getApiEndpoint('merchantBusinessDate'), method: 'GET' }, (data: any) => {
|
const value = data != null && data.businessDate != null ? '' + data.businessDate : ''
|
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) onSuccess(value)
|
else onError({ code: 'INVALID_BUSINESS_DATE', message: '业务日期响应无效', statusCode: 0, traceId: '' })
|
}, onError)
|
|
export const revokeOrderVerification = (
|
orderId: string,
|
idempotencyKey: string,
|
onSuccess: (result: RevokeVerificationResult, traceId: string) => void,
|
onError: (error: HttpError) => void
|
): (() => void) => {
|
return request<any>({
|
path: getApiEndpoint('orderRevokeVerification'),
|
method: 'POST',
|
idempotencyKey: idempotencyKey,
|
data: {
|
orderId: orderId
|
}
|
}, (data: any, traceId: string) => {
|
const result: RevokeVerificationResult = {
|
orderId: data != null && data.orderId != null ? data.orderId as string : orderId,
|
orderStatus: data != null && data.orderStatus != null ? data.orderStatus as string : 'pending_verification',
|
orderStatusText: data != null && data.orderStatusText != null ? data.orderStatusText as string : '待核销',
|
revokedAt: data != null && data.revokedAt != null ? data.revokedAt as string : ''
|
}
|
onSuccess(result, traceId)
|
}, onError)
|
}
|
|
const fieldValue = (value: any, camelCaseName: string, snakeCaseName: string): any => {
|
if (value == null) return null
|
if (value[camelCaseName] != null) return value[camelCaseName]
|
return value[snakeCaseName] != null ? value[snakeCaseName] : null
|
}
|
|
const numberValue = (value: any): number => {
|
if (value == null || value === '') return 0
|
const number = Number(value)
|
return isNaN(number) ? 0 : number
|
}
|
|
const textValue = (value: any, camelCaseName: string, snakeCaseName: string, fallback: string = ''): string => {
|
const field = fieldValue(value, camelCaseName, snakeCaseName)
|
return field != null ? '' + field : fallback
|
}
|
|
/** 后台存的是相对路径(/uploads/xxx.jpg),小程序要绝对地址才能显示;空值原样返回。 */
|
const mediaUrl = (value: string): string => {
|
if (value.length == 0 || value.indexOf('http://') == 0 || value.indexOf('https://') == 0 || value.indexOf('/static/') == 0) return value
|
return value.indexOf('/') == 0 ? getApiBaseUrl().replace(/\/$/, '') + value : value
|
}
|
|
const toOrder = (value: any): MerchantOrder => ({
|
id: textValue(value, 'id', 'id'),
|
orderNo: textValue(value, 'orderNo', 'order_no'),
|
category: textValue(value, 'category', 'category', 'group'),
|
status: textValue(value, 'status', 'status', 'pending_verification'),
|
statusText: textValue(value, 'statusText', 'status_text', '待核销'),
|
title: textValue(value, 'title', 'title', '订单'),
|
customerName: textValue(value, 'customerName', 'customer_name'),
|
customerMobile: textValue(value, 'customerMobile', 'customer_mobile'),
|
cardLevel: textValue(value, 'cardLevel', 'card_level', '卓越人才卡'),
|
orderAmount: numberValue(fieldValue(value, 'orderAmount', 'order_amount')),
|
basePayAmount: numberValue(fieldValue(value, 'basePayAmount', 'base_pay_amount')),
|
couponAmount: numberValue(fieldValue(value, 'couponAmount', 'coupon_amount')),
|
talentDiscountAmount: numberValue(fieldValue(value, 'talentDiscountAmount', 'talent_discount_amount')),
|
payAmount: numberValue(fieldValue(value, 'payAmount', 'pay_amount')),
|
subsidyAmount: numberValue(fieldValue(value, 'subsidyAmount', 'subsidy_amount')),
|
createdAt: textValue(value, 'createdAt', 'created_at'),
|
verifyTime: textValue(value, 'verifyTime', 'verify_time')
|
})
|
|
const toOrderDetail = (value: any): MerchantOrderDetail => {
|
const order = toOrder(value)
|
const productCover = textValue(value, 'productCoverUrl', 'product_cover_url')
|
const merchantCover = textValue(value, 'merchantCoverUrl', 'merchant_cover_url')
|
return {
|
...order,
|
basePayAmount: order.basePayAmount != null ? order.basePayAmount : order.orderAmount,
|
couponAmount: order.couponAmount != null ? order.couponAmount : 0,
|
talentDiscountAmount: order.talentDiscountAmount != null ? order.talentDiscountAmount : 0,
|
benefitName: textValue(value, 'benefitName', 'benefit_name', order.title),
|
merchantName: textValue(value, 'merchantName', 'merchant_name'),
|
productImage: mediaUrl(productCover.length > 0 ? productCover : merchantCover),
|
canRevoke: fieldValue(value, 'canRevoke', 'can_revoke') == true,
|
revokeDeadline: textValue(value, 'revokeDeadline', 'revoke_deadline'),
|
revokeReason: textValue(value, 'revokeReason', 'revoke_reason')
|
}
|
}
|
|
export const loadMerchantOrders = (
|
query: MerchantOrderQuery,
|
onSuccess: (page: MerchantOrderPage, traceId: string) => void,
|
onError: (error: HttpError) => void
|
): (() => void) => request<any>({
|
path: getApiEndpoint('merchantOrders'),
|
method: 'GET',
|
data: query
|
}, (data: any, traceId: string) => {
|
const source = data != null ? data : {}
|
const records = source.records != null ? source.records as Array<any> : []
|
onSuccess({
|
records: records.map((item: any) => toOrder(item)),
|
page: source.page != null ? Number(source.page) : query.page,
|
size: source.size != null ? Number(source.size) : query.size,
|
total: source.total != null ? Number(source.total) : records.length
|
}, traceId)
|
}, onError)
|
|
export const loadMerchantOrderDetail = (
|
orderNo: string,
|
onSuccess: (order: MerchantOrderDetail, traceId: string) => void,
|
onError: (error: HttpError) => void
|
): (() => void) => request<any>({
|
path: getApiEndpoint('merchantOrderDetail') + encodeURIComponent(orderNo),
|
method: 'GET'
|
}, (data: any, traceId: string) => onSuccess(toOrderDetail(data), traceId), onError)
|
|
/**
|
* 订单核销:核销成功会写核销记录并确认延迟入账的资金。
|
*
|
* verificationId 是扫码尝试编号;从订单列表直接核销时不传。
|
* verifyType 会写进核销记录用于审计:扫码核销记 ORDER_CODE,订单列表核销记 MERCHANT_ORDER。
|
*/
|
export const confirmMerchantOrderVerification = (
|
orderNo: string,
|
verificationId: string,
|
verifyType: string,
|
idempotencyKey: string,
|
onSuccess: (order: MerchantOrderDetail, traceId: string) => void,
|
onError: (error: HttpError) => void
|
): (() => void) => request<any>({
|
path: getApiEndpoint('merchantOrderVerify') + encodeURIComponent(orderNo) + '/verify',
|
method: 'POST',
|
idempotencyKey: idempotencyKey,
|
data: { verifyType: verifyType, remark: '', verificationId: verificationId != null ? verificationId : '' }
|
}, (data: any, traceId: string) => onSuccess(toOrderDetail(data), traceId), onError)
|
|
const demoOrders: Array<MerchantOrder> = [
|
{ id: 'demo-group-1', orderNo: 'DEMO-G-001', category: 'group', status: 'pending_verification', statusText: '待核销', title: '【川味盛宴】川菜双人餐', customerName: '刘先生', customerMobile: '183****4568', cardLevel: '卓越人才卡', orderAmount: 25600, payAmount: 25600, subsidyAmount: 0, createdAt: '2026-07-30 09:20:00', verifyTime: '' },
|
{ id: 'demo-group-2', orderNo: 'DEMO-G-002', category: 'group', status: 'completed', statusText: '已完成', title: '【江南小馆】双人精选套餐', customerName: '张女士', customerMobile: '186****2096', cardLevel: '卓越人才卡', orderAmount: 5800, payAmount: 2800, subsidyAmount: 3000, createdAt: '2026-07-29 13:20:00', verifyTime: '2026-07-29 18:05:00' },
|
{ id: 'demo-group-3', orderNo: 'DEMO-G-003', category: 'group', status: 'pending_verification', statusText: '待核销', title: '【悦宾餐厅】特色四人餐', customerName: '王先生', customerMobile: '135****8831', cardLevel: '卓越人才卡', orderAmount: 9800, payAmount: 6800, subsidyAmount: 3000, createdAt: '2026-07-28 10:45:00', verifyTime: '' },
|
{ id: 'demo-group-4', orderNo: 'DEMO-G-004', category: 'group', status: 'completed', statusText: '已完成', title: '【城市咖啡】下午茶套餐', customerName: '陈女士', customerMobile: '137****6678', cardLevel: '卓越人才卡', orderAmount: 6800, payAmount: 3800, subsidyAmount: 3000, createdAt: '2026-07-27 16:30:00', verifyTime: '2026-07-27 17:20:00' },
|
{ id: 'demo-store-1', orderNo: 'DEMO-S-001', category: 'store', status: 'pending_payment', statusText: '待支付', title: '到店消费订单', customerName: '刘先生', customerMobile: '183****4568', cardLevel: '卓越人才卡', orderAmount: 25600, payAmount: 25600, subsidyAmount: 0, createdAt: '2026-07-30 11:20:00', verifyTime: '' },
|
{ id: 'demo-store-2', orderNo: 'DEMO-S-002', category: 'store', status: 'completed', statusText: '已完成', title: '到店消费订单', customerName: '李女士', customerMobile: '188****7620', cardLevel: '卓越人才卡', orderAmount: 20800, payAmount: 10900, subsidyAmount: 9900, createdAt: '2026-07-29 10:10:00', verifyTime: '2026-07-29 12:00:00' },
|
{ id: 'demo-store-3', orderNo: 'DEMO-S-003', category: 'store', status: 'pending_payment', statusText: '待支付', title: '到店消费订单', customerName: '周先生', customerMobile: '139****4512', cardLevel: '卓越人才卡', orderAmount: 39800, payAmount: 29800, subsidyAmount: 10000, createdAt: '2026-07-28 14:10:00', verifyTime: '' },
|
{ id: 'demo-store-4', orderNo: 'DEMO-S-004', category: 'store', status: 'completed', statusText: '已完成', title: '到店消费订单', customerName: '赵女士', customerMobile: '136****7223', cardLevel: '卓越人才卡', orderAmount: 15800, payAmount: 9800, subsidyAmount: 6000, createdAt: '2026-07-27 15:00:00', verifyTime: '2026-07-27 17:15:00' },
|
{ id: 'demo-stay-1', orderNo: 'DEMO-H-001', category: 'stay', status: 'completed', statusText: '已完成', title: '1天免费入住', customerName: '刘先生', customerMobile: '183****4568', cardLevel: '卓越人才卡', orderAmount: 0, payAmount: 0, subsidyAmount: 0, createdAt: '2026-07-29 12:00:00', verifyTime: '2026-07-29 14:00:00' },
|
{ id: 'demo-stay-2', orderNo: 'DEMO-H-002', category: 'stay', status: 'completed', statusText: '已完成', title: '2天免费入住', customerName: '张女士', customerMobile: '186****2096', cardLevel: '卓越人才卡', orderAmount: 0, payAmount: 0, subsidyAmount: 0, createdAt: '2026-07-28 12:00:00', verifyTime: '2026-07-28 15:30:00' },
|
{ id: 'demo-stay-3', orderNo: 'DEMO-H-003', category: 'stay', status: 'completed', statusText: '已完成', title: '1天免费入住', customerName: '王先生', customerMobile: '135****8831', cardLevel: '卓越人才卡', orderAmount: 0, payAmount: 0, subsidyAmount: 0, createdAt: '2026-07-27 12:00:00', verifyTime: '2026-07-27 16:00:00' },
|
{ id: 'demo-stay-4', orderNo: 'DEMO-H-004', category: 'stay', status: 'completed', statusText: '已完成', title: '3天免费入住', customerName: '陈女士', customerMobile: '137****6678', cardLevel: '卓越人才卡', orderAmount: 0, payAmount: 0, subsidyAmount: 0, createdAt: '2026-07-26 12:00:00', verifyTime: '2026-07-26 14:00:00' }
|
]
|
|
export const loadDemoMerchantOrders = (query: MerchantOrderQuery): MerchantOrderPage => {
|
const matched = demoOrders.filter((order: MerchantOrder) => {
|
if (order.category != query.category) return false
|
if (query.status != 'all' && order.status != query.status) return false
|
const date = order.createdAt.substring(0, 10)
|
if (query.startDate.length > 0 && date < query.startDate) return false
|
return query.endDate.length == 0 || date <= query.endDate
|
})
|
const start = (query.page - 1) * query.size
|
return { records: matched.slice(start, start + query.size), page: query.page, size: query.size, total: matched.length }
|
}
|