Files
points-mall/supplied-service/netlify/functions/order-create.ts
T

260 lines
7.7 KiB
TypeScript

import type { Context } from '@netlify/functions'
import { guardMethod } from '../utils/method.js'
import { fail, ok } from '../utils/http.js'
import { readJson } from '../utils/request.js'
import { prisma } from '../utils/prisma.js'
import { withErrorBoundary } from '../utils/handler.js'
type CheckoutGoods = {
id?: string | number
sku?: string
count?: number
number?: number
price?: string | number
name?: string
image?: string
imageUrl?: string
picInfo?: string[]
taxRate?: number
}
type OrderCreateRequest = {
supplierId?: string
token?: string
userId?: string
orderNo?: string
orderNumberId?: string
amount?: number
goods?: CheckoutGoods[]
}
type BridgeOrderResponse = {
resultCode?: number
message?: string
}
const normalizeBase = (base: string) => base.replace(/\/$/, '')
const nowText = () => {
const now = new Date()
const pad = (value: number) => String(value).padStart(2, '0')
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`
}
const createOrderNumberId = () => {
const now = new Date()
const pad = (value: number, size = 2) => String(value).padStart(size, '0')
return `TR${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}${pad(now.getMilliseconds(), 3)}`
}
const toYuan = (centAmount?: number) => Number(((Number(centAmount || 0)) / 100).toFixed(2))
const toOrderProducts = (goods: CheckoutGoods[]) => goods.map((item, index) => {
const image = item.image || item.imageUrl || item.picInfo?.[0] || ''
return {
sku: String(item.sku || item.id || `SKU_${index + 1}`),
number: Number(item.count || item.number || 1),
price: Number(item.price || 0),
name: String(item.name || ''),
taxRate: Number(item.taxRate || 0.13),
picInfo: item.picInfo?.length ? item.picInfo : image ? [image] : [],
fileId: image || null,
filePath: image || null
}
})
export default withErrorBoundary(async (request: Request, _context: Context) => {
const methodResponse = guardMethod(request, ['POST'])
if (methodResponse) return methodResponse
const body = await readJson<OrderCreateRequest>(request)
const supplierId = String(body.supplierId || '').trim()
const token = String(body.token || '').trim()
const userId = String(body.userId || '').trim()
const orderId = String(body.orderNo || '').trim()
const amount = toYuan(body.amount)
const goods = Array.isArray(body.goods) ? body.goods : []
if (!supplierId || !token || !userId || !orderId) {
return fail('供应商、用户、授权或订单信息不能为空')
}
if (amount <= 0) {
return fail('订单金额必须大于0')
}
const apiBase = normalizeBase(process.env.POINTS_MALL_OPEN_API_BASE || '')
if (!apiBase) {
return fail('平台积分商城接口地址未配置', 500, 'OPEN_API_BASE_MISSING')
}
const orderNumberId = String(body.orderNumberId || createOrderNumberId())
const subOrderId = `${orderId}_001`
const subOrderNumberId = `${orderNumberId}_001`
const orderTime = nowText()
const orderProInfos = toOrderProducts(goods)
const orderInfo = {
orderId,
orderNumberId,
supplierId,
aplName: '积分商城',
createTime: orderTime,
updateTime: orderTime,
deliveryTime: null,
orderFinishTime: null,
completeTime: null,
deliveryStatus: '0',
freightCost: 0,
wPayOrAPayFreightCost: 0,
refund: 0,
wPayOrAPayRefund: 0,
logisticsOrderId: null,
crrgBsnNm: null,
orderState: 1,
totalPrice: amount,
wPayOrAPay: 0,
pointsPrice: amount,
invoiceCode: null,
orderProInfos
}
await prisma.$transaction(async tx => {
await tx.supplierOrderMain.upsert({
where: { orderId },
update: {
orderNumberId,
supplierId,
userId,
aplName: orderInfo.aplName,
updateTime: new Date(),
deliveryStatus: orderInfo.deliveryStatus,
freightCost: orderInfo.freightCost,
wPayOrAPayFreightCost: orderInfo.wPayOrAPayFreightCost,
refund: orderInfo.refund,
wPayOrAPayRefund: orderInfo.wPayOrAPayRefund,
orderState: orderInfo.orderState,
totalPrice: orderInfo.totalPrice,
wPayOrAPay: orderInfo.wPayOrAPay,
pointsPrice: orderInfo.pointsPrice,
invoiceCode: orderInfo.invoiceCode,
deleted: 0
},
create: {
orderId,
orderNumberId,
supplierId,
userId,
aplName: orderInfo.aplName,
createTime: new Date(),
updateTime: new Date(),
deliveryStatus: orderInfo.deliveryStatus,
freightCost: orderInfo.freightCost,
wPayOrAPayFreightCost: orderInfo.wPayOrAPayFreightCost,
refund: orderInfo.refund,
wPayOrAPayRefund: orderInfo.wPayOrAPayRefund,
orderState: orderInfo.orderState,
totalPrice: orderInfo.totalPrice,
wPayOrAPay: orderInfo.wPayOrAPay,
pointsPrice: orderInfo.pointsPrice,
invoiceCode: orderInfo.invoiceCode
}
})
await tx.supplierOrderSub.upsert({
where: { orderId: subOrderId },
update: {
orderNumberId: subOrderNumberId,
mainOrderId: orderId,
supplierId,
token,
userId,
aplName: orderInfo.aplName,
updateTime: new Date(),
deliveryStatus: orderInfo.deliveryStatus,
freightCost: orderInfo.freightCost,
wPayOrAPayFreightCost: orderInfo.wPayOrAPayFreightCost,
refund: orderInfo.refund,
wPayOrAPayRefund: orderInfo.wPayOrAPayRefund,
orderState: orderInfo.orderState,
totalPrice: orderInfo.totalPrice,
wPayOrAPay: orderInfo.wPayOrAPay,
pointsPrice: orderInfo.pointsPrice,
invoiceCode: orderInfo.invoiceCode,
deleted: 0
},
create: {
orderId: subOrderId,
orderNumberId: subOrderNumberId,
mainOrderId: orderId,
supplierId,
token,
userId,
aplName: orderInfo.aplName,
createTime: new Date(),
updateTime: new Date(),
deliveryStatus: orderInfo.deliveryStatus,
freightCost: orderInfo.freightCost,
wPayOrAPayFreightCost: orderInfo.wPayOrAPayFreightCost,
refund: orderInfo.refund,
wPayOrAPayRefund: orderInfo.wPayOrAPayRefund,
orderState: orderInfo.orderState,
totalPrice: orderInfo.totalPrice,
wPayOrAPay: orderInfo.wPayOrAPay,
pointsPrice: orderInfo.pointsPrice,
invoiceCode: orderInfo.invoiceCode
}
})
await tx.supplierOrderProduct.deleteMany({
where: { subOrderId }
})
if (orderProInfos.length) {
await tx.supplierOrderProduct.createMany({
data: orderProInfos.map(item => ({
subOrderId,
sku: item.sku,
number: item.number,
price: item.price,
name: item.name,
taxRate: item.taxRate,
productImgUrl: item.picInfo[0] || null,
fileId: item.fileId,
filePath: item.filePath,
picInfo: item.picInfo
}))
})
}
})
const bridgeResponse = await fetch(`${apiBase}/pointOrder/pendingOrderInfo`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
token
},
body: JSON.stringify({
supplierId,
userId,
orderInfo,
token
})
})
const bridgePayload = (await bridgeResponse.json().catch(() => ({}))) as BridgeOrderResponse
if (!bridgeResponse.ok || bridgePayload.resultCode !== 0) {
return fail(bridgePayload.message || '提交待支付订单失败', 502, 'BRIDGE_PENDING_ORDER_FAILED')
}
return ok({
orderId,
orderNumberId,
subOrderId,
orderInfo
})
})