feat: 查积分、支付、拆弹、取消订单
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
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 OrderCancelMainRequest = {
|
||||
supplierId?: string
|
||||
token?: string
|
||||
userId?: string
|
||||
orderId?: string
|
||||
mainOrderId?: string
|
||||
}
|
||||
|
||||
type BridgeOrderResponse = {
|
||||
resultCode?: number
|
||||
message?: string
|
||||
}
|
||||
|
||||
const normalizeBase = (base: string) => base.replace(/\/$/, '')
|
||||
|
||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['POST'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
const body = await readJson<OrderCancelMainRequest>(request)
|
||||
const supplierId = String(body.supplierId || '').trim()
|
||||
const token = String(body.token || '').trim()
|
||||
const userId = String(body.userId || '').trim()
|
||||
const mainOrderId = String(body.mainOrderId || body.orderId || '').trim()
|
||||
const orderId = String(body.orderId || mainOrderId).trim()
|
||||
|
||||
if (!supplierId || !token || !userId || !mainOrderId) {
|
||||
return fail('供应商、用户、授权或订单信息不能为空')
|
||||
}
|
||||
|
||||
const apiBase = normalizeBase(process.env.POINTS_MALL_OPEN_API_BASE || '')
|
||||
if (!apiBase) {
|
||||
return fail('平台积分商城接口地址未配置', 500, 'OPEN_API_BASE_MISSING')
|
||||
}
|
||||
|
||||
const order = await prisma.supplierOrderMain.findFirst({
|
||||
where: {
|
||||
orderId: mainOrderId,
|
||||
supplierId,
|
||||
userId,
|
||||
deleted: 0
|
||||
}
|
||||
})
|
||||
|
||||
if (!order) {
|
||||
return fail('主单不存在或已取消', 404, 'MAIN_ORDER_NOT_FOUND')
|
||||
}
|
||||
|
||||
const bridgeResponse = await fetch(`${apiBase}/pointOrder/pushOrderInfoMsg`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
token
|
||||
},
|
||||
body: JSON.stringify({
|
||||
supplierId,
|
||||
orderType: 1,
|
||||
orderId,
|
||||
mainOrderId,
|
||||
userId,
|
||||
time: Date.now(),
|
||||
token
|
||||
})
|
||||
})
|
||||
|
||||
const bridgePayload = (await bridgeResponse.json().catch(() => ({}))) as BridgeOrderResponse
|
||||
|
||||
if (!bridgeResponse.ok || bridgePayload.resultCode !== 0) {
|
||||
return fail(bridgePayload.message || '取消主单推送失败', 502, 'BRIDGE_CANCEL_MAIN_FAILED')
|
||||
}
|
||||
|
||||
const subOrders = await prisma.supplierOrderSub.findMany({
|
||||
where: {
|
||||
mainOrderId,
|
||||
supplierId,
|
||||
userId,
|
||||
deleted: 0
|
||||
},
|
||||
select: {
|
||||
orderId: true
|
||||
}
|
||||
})
|
||||
const subOrderIds = subOrders.map(item => item.orderId)
|
||||
|
||||
await prisma.$transaction(async tx => {
|
||||
await tx.supplierOrderMain.update({
|
||||
where: { orderId: mainOrderId },
|
||||
data: {
|
||||
deleted: 1,
|
||||
updateTime: new Date()
|
||||
}
|
||||
})
|
||||
|
||||
await tx.supplierOrderSub.updateMany({
|
||||
where: {
|
||||
mainOrderId,
|
||||
supplierId,
|
||||
userId,
|
||||
deleted: 0
|
||||
},
|
||||
data: {
|
||||
deleted: 1,
|
||||
updateTime: new Date()
|
||||
}
|
||||
})
|
||||
|
||||
await tx.supplierOrderProduct.updateMany({
|
||||
where: {
|
||||
subOrderId: { in: subOrderIds },
|
||||
deleted: 0
|
||||
},
|
||||
data: {
|
||||
deleted: 1
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return ok({
|
||||
supplierId,
|
||||
orderType: 1,
|
||||
orderId,
|
||||
mainOrderId,
|
||||
userId
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,107 @@
|
||||
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 OrderCancelSubRequest = {
|
||||
supplierId?: string
|
||||
token?: string
|
||||
userId?: string
|
||||
orderId?: string
|
||||
mainOrderId?: string
|
||||
}
|
||||
|
||||
type BridgeOrderResponse = {
|
||||
resultCode?: number
|
||||
message?: string
|
||||
}
|
||||
|
||||
const normalizeBase = (base: string) => base.replace(/\/$/, '')
|
||||
|
||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['POST'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
const body = await readJson<OrderCancelSubRequest>(request)
|
||||
const supplierId = String(body.supplierId || '').trim()
|
||||
const token = String(body.token || '').trim()
|
||||
const userId = String(body.userId || '').trim()
|
||||
const orderId = String(body.orderId || '').trim()
|
||||
|
||||
if (!supplierId || !token || !userId || !orderId) {
|
||||
return fail('供应商、用户、授权或子单信息不能为空')
|
||||
}
|
||||
|
||||
const apiBase = normalizeBase(process.env.POINTS_MALL_OPEN_API_BASE || '')
|
||||
if (!apiBase) {
|
||||
return fail('平台积分商城接口地址未配置', 500, 'OPEN_API_BASE_MISSING')
|
||||
}
|
||||
|
||||
const subOrder = await prisma.supplierOrderSub.findFirst({
|
||||
where: {
|
||||
orderId,
|
||||
supplierId,
|
||||
userId,
|
||||
deleted: 0
|
||||
}
|
||||
})
|
||||
|
||||
if (!subOrder) {
|
||||
return fail('子单不存在或已取消', 404, 'SUB_ORDER_NOT_FOUND')
|
||||
}
|
||||
|
||||
const mainOrderId = String(body.mainOrderId || subOrder.mainOrderId).trim()
|
||||
|
||||
const bridgeResponse = await fetch(`${apiBase}/pointOrder/pushOrderInfoMsg`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
token
|
||||
},
|
||||
body: JSON.stringify({
|
||||
supplierId,
|
||||
orderType: 3,
|
||||
orderId,
|
||||
mainOrderId,
|
||||
userId,
|
||||
time: Date.now(),
|
||||
token
|
||||
})
|
||||
})
|
||||
|
||||
const bridgePayload = (await bridgeResponse.json().catch(() => ({}))) as BridgeOrderResponse
|
||||
|
||||
if (!bridgeResponse.ok || bridgePayload.resultCode !== 0) {
|
||||
return fail(bridgePayload.message || '取消子单推送失败', 502, 'BRIDGE_CANCEL_SUB_FAILED')
|
||||
}
|
||||
|
||||
await prisma.$transaction(async tx => {
|
||||
await tx.supplierOrderSub.update({
|
||||
where: { orderId },
|
||||
data: {
|
||||
deleted: 1,
|
||||
updateTime: new Date()
|
||||
}
|
||||
})
|
||||
|
||||
await tx.supplierOrderProduct.updateMany({
|
||||
where: {
|
||||
subOrderId: orderId,
|
||||
deleted: 0
|
||||
},
|
||||
data: {
|
||||
deleted: 1
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return ok({
|
||||
supplierId,
|
||||
orderType: 3,
|
||||
orderId,
|
||||
mainOrderId,
|
||||
userId
|
||||
})
|
||||
})
|
||||
@@ -1,11 +1,259 @@
|
||||
import type { Context } from '@netlify/functions'
|
||||
import { guardMethod } from '../utils/method.js'
|
||||
import { ok } from '../utils/http.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
|
||||
|
||||
return ok({ message: '创建订单接口待实现' })
|
||||
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
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
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 OrderListRequest = {
|
||||
supplierId?: string
|
||||
userId?: string
|
||||
}
|
||||
|
||||
type OrderStatusView = {
|
||||
text: string
|
||||
statusClass: string
|
||||
actionText: string
|
||||
buttonClass: string
|
||||
}
|
||||
|
||||
const statusMap: Record<number, OrderStatusView> = {
|
||||
0: { text: '待付款', statusClass: 'pending', actionText: '去支付', buttonClass: 'primary-action' },
|
||||
1: { text: '待发货', statusClass: 'pending', actionText: '查看订单', buttonClass: 'primary-action' },
|
||||
2: { text: '待发货', statusClass: 'pending', actionText: '查看订单', buttonClass: 'primary-action' },
|
||||
3: { text: '待收货', statusClass: 'pending', actionText: '确认收货', buttonClass: 'dark-action' },
|
||||
4: { text: '已完成', statusClass: 'done', actionText: '再次购买', buttonClass: 'dark-action' },
|
||||
5: { text: '已完成', statusClass: 'done', actionText: '再次购买', buttonClass: 'dark-action' }
|
||||
}
|
||||
|
||||
const toYuanText = (value: { toFixed: (digits: number) => string } | number | string | null | undefined) => {
|
||||
if (value && typeof value === 'object' && 'toFixed' in value) {
|
||||
return value.toFixed(2)
|
||||
}
|
||||
|
||||
return Number(value || 0).toFixed(2)
|
||||
}
|
||||
|
||||
const firstImage = (picInfo: unknown) => {
|
||||
if (Array.isArray(picInfo)) {
|
||||
return String(picInfo[0] || '')
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['POST'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
const body = await readJson<OrderListRequest>(request)
|
||||
const supplierId = String(body.supplierId || '').trim()
|
||||
const userId = String(body.userId || '').trim()
|
||||
|
||||
if (!supplierId || !userId) {
|
||||
return fail('供应商或用户信息不能为空')
|
||||
}
|
||||
|
||||
const subOrders = await prisma.supplierOrderSub.findMany({
|
||||
where: {
|
||||
supplierId,
|
||||
userId,
|
||||
deleted: 0
|
||||
},
|
||||
include: {
|
||||
mainOrder: true,
|
||||
products: {
|
||||
where: { deleted: 0 },
|
||||
orderBy: { createAt: 'asc' }
|
||||
}
|
||||
},
|
||||
orderBy: [
|
||||
{ createTime: 'desc' },
|
||||
{ createAt: 'desc' }
|
||||
]
|
||||
})
|
||||
|
||||
const orders = subOrders.map(order => {
|
||||
const status = statusMap[order.orderState] || statusMap[1]
|
||||
const products = order.products.map(product => ({
|
||||
id: product.id,
|
||||
sku: product.sku,
|
||||
name: product.name,
|
||||
number: product.number,
|
||||
price: toYuanText(product.price),
|
||||
image: product.productImgUrl || firstImage(product.picInfo)
|
||||
}))
|
||||
|
||||
return {
|
||||
id: order.orderId,
|
||||
orderId: order.orderId,
|
||||
orderNumberId: order.orderNumberId || '',
|
||||
mainOrderId: order.mainOrderId,
|
||||
mainOrderNumberId: order.mainOrder?.orderNumberId || '',
|
||||
supplierId: order.supplierId,
|
||||
status: status.text,
|
||||
statusClass: status.statusClass,
|
||||
actionText: status.actionText,
|
||||
buttonClass: status.buttonClass,
|
||||
orderState: order.orderState,
|
||||
amount: toYuanText(order.totalPrice),
|
||||
count: products.reduce((total, item) => total + Number(item.number || 0), 0),
|
||||
products
|
||||
}
|
||||
})
|
||||
|
||||
return ok({ orders })
|
||||
})
|
||||
@@ -1,11 +1,148 @@
|
||||
import type { Context } from '@netlify/functions'
|
||||
import { guardMethod } from '../utils/method.js'
|
||||
import { ok } from '../utils/http.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 OrderPayRequest = {
|
||||
supplierId?: string
|
||||
token?: string
|
||||
orderId?: string
|
||||
amount?: number
|
||||
totalPrice?: number
|
||||
wPayOrAPay?: number
|
||||
pointsPrice?: number
|
||||
}
|
||||
|
||||
type BridgePayResponse = {
|
||||
resultCode?: number
|
||||
message?: string
|
||||
}
|
||||
|
||||
const normalizeBase = (base: string) => base.replace(/\/$/, '')
|
||||
const centsToYuan = (value?: number) => Number((Number(value || 0) / 100).toFixed(2))
|
||||
|
||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['POST'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
return ok({ message: '订单支付接口待实现' })
|
||||
const body = await readJson<OrderPayRequest>(request)
|
||||
const supplierId = String(body.supplierId || '').trim()
|
||||
const token = String(body.token || '').trim()
|
||||
const orderId = String(body.orderId || '').trim()
|
||||
const totalPrice = Number(body.totalPrice ?? centsToYuan(body.amount))
|
||||
const wPayOrAPay = Number(body.wPayOrAPay ?? 0)
|
||||
const pointsPrice = Number(body.pointsPrice ?? totalPrice)
|
||||
|
||||
if (!supplierId || !token || !orderId) {
|
||||
return fail('供应商、授权或订单信息不能为空')
|
||||
}
|
||||
|
||||
if (totalPrice <= 0) {
|
||||
return fail('支付金额必须大于0')
|
||||
}
|
||||
|
||||
const apiBase = normalizeBase(process.env.POINTS_MALL_OPEN_API_BASE || '')
|
||||
if (!apiBase) {
|
||||
return fail('平台积分商城接口地址未配置', 500, 'OPEN_API_BASE_MISSING')
|
||||
}
|
||||
|
||||
const order = await prisma.supplierOrderMain.findUnique({
|
||||
where: { orderId }
|
||||
})
|
||||
|
||||
if (!order || order.deleted === 1) {
|
||||
return fail('待支付订单不存在', 404, 'ORDER_NOT_FOUND')
|
||||
}
|
||||
|
||||
const bridgeResponse = await fetch(`${apiBase}/pointOrder/confirmPurchase`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
token
|
||||
},
|
||||
body: JSON.stringify({
|
||||
supplierId,
|
||||
orderId,
|
||||
totalPrice,
|
||||
wPayOrAPay,
|
||||
pointsPrice,
|
||||
token
|
||||
})
|
||||
})
|
||||
|
||||
const bridgePayload = (await bridgeResponse.json().catch(() => ({}))) as BridgePayResponse
|
||||
|
||||
if (!bridgeResponse.ok || bridgePayload.resultCode !== 0) {
|
||||
return fail(bridgePayload.message || '确认购买失败', 502, 'BRIDGE_CONFIRM_PURCHASE_FAILED')
|
||||
}
|
||||
|
||||
await prisma.$transaction(async tx => {
|
||||
await tx.supplierOrderMain.update({
|
||||
where: { orderId },
|
||||
data: {
|
||||
orderState: 1,
|
||||
totalPrice,
|
||||
wPayOrAPay,
|
||||
pointsPrice,
|
||||
updateTime: new Date()
|
||||
}
|
||||
})
|
||||
|
||||
await tx.supplierOrderSub.updateMany({
|
||||
where: { mainOrderId: orderId, deleted: 0 },
|
||||
data: {
|
||||
orderState: 1,
|
||||
totalPrice,
|
||||
wPayOrAPay,
|
||||
pointsPrice,
|
||||
updateTime: new Date()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const subOrders = await prisma.supplierOrderSub.findMany({
|
||||
where: {
|
||||
mainOrderId: orderId,
|
||||
deleted: 0
|
||||
},
|
||||
select: {
|
||||
orderId: true
|
||||
}
|
||||
})
|
||||
const subOrderIds = subOrders.map(item => item.orderId).filter(Boolean).join(',')
|
||||
|
||||
if (subOrderIds) {
|
||||
const splitResponse = await fetch(`${apiBase}/pointOrder/pushOrderInfoMsg`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
token
|
||||
},
|
||||
body: JSON.stringify({
|
||||
supplierId,
|
||||
orderType: 2,
|
||||
orderId: subOrderIds,
|
||||
mainOrderId: orderId,
|
||||
userId: order.userId,
|
||||
time: Date.now(),
|
||||
token
|
||||
})
|
||||
})
|
||||
|
||||
const splitPayload = (await splitResponse.json().catch(() => ({}))) as BridgePayResponse
|
||||
|
||||
if (!splitResponse.ok || splitPayload.resultCode !== 0) {
|
||||
return fail(splitPayload.message || '推送拆单信息失败', 502, 'BRIDGE_SPLIT_ORDER_FAILED')
|
||||
}
|
||||
}
|
||||
|
||||
return ok({
|
||||
orderId,
|
||||
subOrderIds,
|
||||
totalPrice,
|
||||
wPayOrAPay,
|
||||
pointsPrice
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,11 +1,63 @@
|
||||
import type { Context } from '@netlify/functions'
|
||||
import { guardMethod } from '../utils/method.js'
|
||||
import { ok } from '../utils/http.js'
|
||||
import { fail, ok } from '../utils/http.js'
|
||||
import { prisma } from '../utils/prisma.js'
|
||||
import { withErrorBoundary } from '../utils/handler.js'
|
||||
|
||||
const toProductDetail = (product: NonNullable<Awaited<ReturnType<typeof prisma.supplierProduct.findFirst>>>) => {
|
||||
const picInfo = Array.isArray(product.picInfo) ? product.picInfo : []
|
||||
const image = product.imageUrl || String(picInfo[0] || '')
|
||||
const price = product.price.toFixed(2)
|
||||
|
||||
return {
|
||||
id: product.id,
|
||||
sku: product.sku,
|
||||
number: product.number,
|
||||
price,
|
||||
name: product.name,
|
||||
taxRate: Number(product.taxRate),
|
||||
picInfo,
|
||||
desc: product.description || '',
|
||||
detail: product.detail || product.description || '',
|
||||
originPrice: product.originPrice ? product.originPrice.toFixed(2) : price,
|
||||
image,
|
||||
imageUrl: image,
|
||||
categoryId: product.categoryId || '',
|
||||
category: product.category || '',
|
||||
tag: product.tag || '',
|
||||
sales: product.sales || '',
|
||||
stock: product.stock
|
||||
}
|
||||
}
|
||||
|
||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['GET'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
return ok({ message: '商品详情接口待实现' })
|
||||
const url = new URL(request.url)
|
||||
const id = (url.searchParams.get('id') || '').trim()
|
||||
const sku = (url.searchParams.get('sku') || '').trim()
|
||||
const numericId = Number(id)
|
||||
const querySku = sku || (id && !Number.isInteger(numericId) ? id : '')
|
||||
|
||||
if (!id && !sku) {
|
||||
return fail('商品参数不能为空')
|
||||
}
|
||||
|
||||
const product = await prisma.supplierProduct.findFirst({
|
||||
where: {
|
||||
enabled: true,
|
||||
deleted: 0,
|
||||
OR: [
|
||||
...(Number.isInteger(numericId) && numericId > 0 ? [{ id: numericId }] : []),
|
||||
...(querySku ? [{ sku: querySku }] : [])
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
if (!product) {
|
||||
return fail('商品不存在或已下架', 404, 'PRODUCT_NOT_FOUND')
|
||||
}
|
||||
|
||||
return ok(toProductDetail(product))
|
||||
})
|
||||
|
||||
@@ -1,11 +1,69 @@
|
||||
import type { Context } from '@netlify/functions'
|
||||
import { guardMethod } from '../utils/method.js'
|
||||
import { ok } from '../utils/http.js'
|
||||
import { prisma } from '../utils/prisma.js'
|
||||
import { withErrorBoundary } from '../utils/handler.js'
|
||||
|
||||
const categories = [
|
||||
{ id: 'recommend', name: '推荐' },
|
||||
{ id: 'homeLiving', name: '居家生活' },
|
||||
{ id: 'digitalAppliance', name: '数码家电' },
|
||||
{ id: 'beautyCare', name: '美妆个护' },
|
||||
{ id: 'foodDrink', name: '食品饮料' },
|
||||
{ id: 'motherBaby', name: '母婴玩具' },
|
||||
{ id: 'fashionBag', name: '服饰鞋包' },
|
||||
{ id: 'sportsOutdoor', name: '运动户外' }
|
||||
]
|
||||
|
||||
const toProductItem = (product: Awaited<ReturnType<typeof prisma.supplierProduct.findMany>>[number]) => {
|
||||
const picInfo = Array.isArray(product.picInfo) ? product.picInfo : []
|
||||
const image = product.imageUrl || String(picInfo[0] || '')
|
||||
const price = product.price.toFixed(2)
|
||||
|
||||
return {
|
||||
id: product.id,
|
||||
sku: product.sku,
|
||||
number: product.number,
|
||||
price,
|
||||
name: product.name,
|
||||
taxRate: Number(product.taxRate),
|
||||
picInfo,
|
||||
desc: product.description || '',
|
||||
detail: product.detail || product.description || '',
|
||||
originPrice: product.originPrice ? product.originPrice.toFixed(2) : price,
|
||||
image,
|
||||
imageUrl: image,
|
||||
categoryId: product.categoryId || '',
|
||||
category: product.category || '',
|
||||
tag: product.tag || '',
|
||||
sales: product.sales || '',
|
||||
stock: product.stock
|
||||
}
|
||||
}
|
||||
|
||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['GET'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
return ok({ message: '商品列表接口待实现' })
|
||||
const url = new URL(request.url)
|
||||
const keyword = (url.searchParams.get('keyword') || '').trim()
|
||||
const categoryId = (url.searchParams.get('categoryId') || '').trim()
|
||||
|
||||
const products = await prisma.supplierProduct.findMany({
|
||||
where: {
|
||||
enabled: true,
|
||||
deleted: 0,
|
||||
...(keyword ? { name: { contains: keyword } } : {}),
|
||||
...(categoryId && categoryId !== 'recommend' ? { categoryId } : {})
|
||||
},
|
||||
orderBy: [
|
||||
{ sortOrder: 'asc' },
|
||||
{ createdAt: 'desc' }
|
||||
]
|
||||
})
|
||||
|
||||
return ok({
|
||||
categories,
|
||||
products: products.map(toProductItem)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -32,7 +32,6 @@ export default withErrorBoundary(async (request: Request, _context: Context) =>
|
||||
id: supplier.id,
|
||||
supplierIdentification: supplier.supplierIdentification,
|
||||
supplierId: supplier.supplierId,
|
||||
supplierName: supplier.supplierName,
|
||||
clientId: supplier.clientId
|
||||
supplierName: supplier.supplierName
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
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 { withErrorBoundary } from '../utils/handler.js'
|
||||
|
||||
type SupplierPointsRequest = {
|
||||
supplierId?: string
|
||||
token?: string
|
||||
userId?: string
|
||||
}
|
||||
|
||||
type BridgePointsResponse = {
|
||||
resultCode?: number
|
||||
message?: string
|
||||
userPoints?: number | string
|
||||
}
|
||||
|
||||
const normalizeBase = (base: string) => base.replace(/\/$/, '')
|
||||
|
||||
export default withErrorBoundary(async (request: Request, _context: Context) => {
|
||||
const methodResponse = guardMethod(request, ['POST'])
|
||||
if (methodResponse) return methodResponse
|
||||
|
||||
const body = await readJson<SupplierPointsRequest>(request)
|
||||
const supplierId = String(body.supplierId || '').trim()
|
||||
const token = String(body.token || '').trim()
|
||||
const userId = String(body.userId || '').trim()
|
||||
|
||||
if (!supplierId || !token || !userId) {
|
||||
return fail('供应商、用户或授权信息不能为空')
|
||||
}
|
||||
|
||||
const apiBase = normalizeBase(process.env.POINTS_MALL_OPEN_API_BASE || '')
|
||||
|
||||
if (!apiBase) {
|
||||
return fail('平台积分商城接口地址未配置', 500, 'OPEN_API_BASE_MISSING')
|
||||
}
|
||||
|
||||
const response = await fetch(`${apiBase}/pointOrder/queryPoints`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
token
|
||||
},
|
||||
body: JSON.stringify({
|
||||
supplierId,
|
||||
token,
|
||||
userId
|
||||
})
|
||||
})
|
||||
|
||||
const pointsPayload = (await response.json().catch(() => ({}))) as BridgePointsResponse
|
||||
|
||||
if (!response.ok || pointsPayload.resultCode !== 0) {
|
||||
return fail(pointsPayload.message || '查询用户积分失败', 502, 'BRIDGE_POINTS_FAILED')
|
||||
}
|
||||
|
||||
return ok({
|
||||
userPoints: Number(pointsPayload.userPoints || 0)
|
||||
})
|
||||
})
|
||||
@@ -59,7 +59,8 @@ export default withErrorBoundary(async (request: Request, _context: Context) =>
|
||||
body: JSON.stringify({
|
||||
supplierId: supplier.supplierId,
|
||||
clientId: supplier.clientId,
|
||||
clientSecret: signature,
|
||||
clientSecret: supplier.clientSecret,
|
||||
sendSha256: signature,
|
||||
code: '',
|
||||
timestamp
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user