shinnosuke
2026-09-18 c2f0ec3b3891bc177bdc58c2a111ab9c4fea911d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
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 }
}