拆分feifei配置并修复商品查询
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
import { Router } from "express";
|
||||
|
||||
import {
|
||||
createAdminKuaishouFeifeiTestOrder,
|
||||
getAdminKuaishouFeifeiConfig,
|
||||
listAdminKuaishouFeifeiProducts,
|
||||
matchAdminKuaishouFeifeiProduct,
|
||||
queryAdminKuaishouFeifeiTestOrder,
|
||||
updateAdminKuaishouFeifeiConfig,
|
||||
} from "../../../services/admin/platform-config/kuaishou-feifei-service.js";
|
||||
import { createJsonHandler } from "../session.js";
|
||||
@@ -59,4 +62,55 @@ router.post(
|
||||
)
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/kuaishou-feifei/products",
|
||||
createJsonHandler(
|
||||
(req) => listAdminKuaishouFeifeiProducts(req.body as JsonRecord),
|
||||
{
|
||||
successMessage: "ok",
|
||||
errorMessage: "查询 kuaishou-feifei 商品失败",
|
||||
scope: "[admin/platform-config/kuaishou-feifei/products]",
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/kuaishou-feifei/test-order",
|
||||
createJsonHandler(
|
||||
(req) => createAdminKuaishouFeifeiTestOrder(req.body as JsonRecord),
|
||||
{
|
||||
successMessage: "kuaishou-feifei 测试单已创建",
|
||||
errorMessage: "创建 kuaishou-feifei 测试单失败",
|
||||
scope: "[admin/platform-config/kuaishou-feifei/test-order]",
|
||||
audit: (_req, data) => {
|
||||
const result = data as JsonRecord;
|
||||
|
||||
return {
|
||||
action: "platform_kuaishou_feifei_test_order_created",
|
||||
targetType: "platform_config",
|
||||
targetId: "kuaishou_feifei",
|
||||
data: {
|
||||
orderNo: String(result.orderNo || "").trim(),
|
||||
platformOrderNo: String(result.platformOrderNo || "").trim(),
|
||||
productCode: String(result.productCode || "").trim(),
|
||||
rechargeStatus: Number(result.rechargeStatus || 0) || 0,
|
||||
},
|
||||
};
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
router.post(
|
||||
"/kuaishou-feifei/query-order",
|
||||
createJsonHandler(
|
||||
(req) => queryAdminKuaishouFeifeiTestOrder(req.body as JsonRecord),
|
||||
{
|
||||
successMessage: "ok",
|
||||
errorMessage: "查询 kuaishou-feifei 订单失败",
|
||||
scope: "[admin/platform-config/kuaishou-feifei/query-order]",
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { getKuaishouFeifeiConfig } from '../../platforms/kuaishou-feifei/config.js'
|
||||
import {
|
||||
createKuaishouFeifeiOrder,
|
||||
listKuaishouFeifeiProducts,
|
||||
queryKuaishouFeifeiOrder,
|
||||
} from '../../platforms/kuaishou-feifei/order-service.js'
|
||||
import { resolveKuaishouFeifeiProductByName } from '../../platforms/kuaishou-feifei/product-rule-service.js'
|
||||
import {
|
||||
getKuaishouFeifeiConfigFilePath,
|
||||
@@ -8,6 +13,7 @@ import {
|
||||
saveKuaishouFeifeiSourceConfig,
|
||||
} from '../../platforms/kuaishou-feifei/source-config-service.js'
|
||||
import { normalizeCloudtentaclesMatchName } from '../../order/cloudtentacles-match-utils.js'
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
@@ -49,6 +55,70 @@ export function matchAdminKuaishouFeifeiProduct(payload: JsonObject = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function listAdminKuaishouFeifeiProducts(payload: JsonObject = {}) {
|
||||
return listKuaishouFeifeiProducts({
|
||||
page: Number(payload.page || 1) || 1,
|
||||
perPage: Number(payload.perPage || payload.per_page || 20) || 20,
|
||||
status: String(payload.status || 'on_sale').trim(),
|
||||
supplyProductName: String(payload.supplyProductName || payload.supply_product_name || '').trim(),
|
||||
})
|
||||
}
|
||||
|
||||
export async function createAdminKuaishouFeifeiTestOrder(payload: JsonObject = {}) {
|
||||
const platformOrderNo = String(payload.platformOrderNo || payload.platform_order_no || '').trim()
|
||||
const productCode = String(payload.productCode || payload.product_code || '').trim()
|
||||
|
||||
if (!platformOrderNo) {
|
||||
throw createHttpError('测试业务单号不能为空', {
|
||||
statusCode: 400,
|
||||
errorCode: 'kuaishou_feifei_missing_platform_order_no',
|
||||
})
|
||||
}
|
||||
|
||||
if (!productCode) {
|
||||
throw createHttpError('feifei 商品编码不能为空', {
|
||||
statusCode: 400,
|
||||
errorCode: 'kuaishou_feifei_missing_product_code',
|
||||
})
|
||||
}
|
||||
|
||||
const orderInput: Parameters<typeof createKuaishouFeifeiOrder>[0] = {
|
||||
platformOrderNo,
|
||||
productCode,
|
||||
platformBuyNum: Number(payload.platformBuyNum || payload.platform_buy_num || 1) || 1,
|
||||
playerAccount: String(payload.playerAccount || payload.player_account || '').trim(),
|
||||
playerGameRegion: String(payload.playerGameRegion || payload.player_game_region || '').trim(),
|
||||
playerGameSrv: String(payload.playerGameSrv || payload.player_game_srv || '').trim(),
|
||||
playerGameRole: String(payload.playerGameRole || payload.player_game_role || '').trim(),
|
||||
submitPlayer: payload.submitPlayer === true || payload.submit_player === true,
|
||||
notifyUrl: String(payload.notifyUrl || payload.notify_url || '').trim(),
|
||||
}
|
||||
const platformAmount = normalizeOptionalNumber(payload.platformAmount ?? payload.platform_amount)
|
||||
|
||||
if (platformAmount !== undefined) {
|
||||
orderInput.platformAmount = platformAmount
|
||||
}
|
||||
|
||||
return createKuaishouFeifeiOrder(orderInput)
|
||||
}
|
||||
|
||||
export async function queryAdminKuaishouFeifeiTestOrder(payload: JsonObject = {}) {
|
||||
const platformOrderNo = String(payload.platformOrderNo || payload.platform_order_no || '').trim()
|
||||
const orderNo = String(payload.orderNo || payload.order_no || '').trim()
|
||||
|
||||
if (!platformOrderNo && !orderNo) {
|
||||
throw createHttpError('请填写 feifei 平台单号或测试业务单号', {
|
||||
statusCode: 400,
|
||||
errorCode: 'kuaishou_feifei_missing_order_query',
|
||||
})
|
||||
}
|
||||
|
||||
return queryKuaishouFeifeiOrder({
|
||||
platformOrderNo,
|
||||
orderNo,
|
||||
})
|
||||
}
|
||||
|
||||
function getAdminEditableKuaishouFeifeiConfig() {
|
||||
if (hasKuaishouFeifeiConfigFile()) {
|
||||
return getKuaishouFeifeiSourceConfig()
|
||||
@@ -68,3 +138,12 @@ function mapEffectiveKuaishouFeifeiConfig(config: ReturnType<typeof getKuaishouF
|
||||
productRuleCount: config.productRules.length,
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeOptionalNumber(value: unknown) {
|
||||
if (value == null || String(value).trim() === '') {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const numberValue = Number(value)
|
||||
return Number.isFinite(numberValue) ? numberValue : undefined
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ import crypto from 'node:crypto'
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
import { signKuaishouFeifeiPayload } from './http-client.js'
|
||||
import {
|
||||
isKuaishouFeifeiSuccessResponse,
|
||||
signKuaishouFeifeiPayload,
|
||||
} from './http-client.js'
|
||||
|
||||
test('signKuaishouFeifeiPayload signs app key, timestamp and raw body with HMAC SHA256', () => {
|
||||
const input = {
|
||||
@@ -19,3 +22,10 @@ test('signKuaishouFeifeiPayload signs app key, timestamp and raw body with HMAC
|
||||
|
||||
assert.equal(signKuaishouFeifeiPayload(input), expected)
|
||||
})
|
||||
|
||||
test('isKuaishouFeifeiSuccessResponse accepts documented and actual success codes', () => {
|
||||
assert.equal(isKuaishouFeifeiSuccessResponse({ code: 0, message: 'success' }), true)
|
||||
assert.equal(isKuaishouFeifeiSuccessResponse({ code: 10000, message: 'success' }), true)
|
||||
assert.equal(isKuaishouFeifeiSuccessResponse({ code: 10000, message: '商品不存在' }), false)
|
||||
assert.equal(isKuaishouFeifeiSuccessResponse({ code: 1, message: 'success' }), false)
|
||||
})
|
||||
|
||||
@@ -44,7 +44,7 @@ export async function kuaishouFeifeiRequest(pathname: string, payload: JsonObjec
|
||||
})
|
||||
}
|
||||
|
||||
if (Number(json.code || 0) !== 0) {
|
||||
if (!isKuaishouFeifeiSuccessResponse(json)) {
|
||||
throw createHttpError(String(json.message || 'kuaishou-feifei 业务失败'), {
|
||||
statusCode: 502,
|
||||
errorCode: 'kuaishou_feifei_business_failed',
|
||||
@@ -85,6 +85,13 @@ export function signKuaishouFeifeiPayload({
|
||||
.toLowerCase()
|
||||
}
|
||||
|
||||
export function isKuaishouFeifeiSuccessResponse(json: JsonObject) {
|
||||
const code = Number(json.code ?? 0)
|
||||
const message = String(json.message || json.msg || '').trim().toLowerCase()
|
||||
|
||||
return code === 0 || (code === 10000 && message === 'success')
|
||||
}
|
||||
|
||||
function parseJsonObject(text: string): JsonObject {
|
||||
try {
|
||||
const parsed = JSON.parse(text || '{}')
|
||||
|
||||
@@ -3,6 +3,30 @@ import { kuaishouFeifeiRequest } from './http-client.js'
|
||||
|
||||
type JsonObject = Record<string, any>
|
||||
|
||||
export async function listKuaishouFeifeiProducts(input: {
|
||||
page?: number
|
||||
perPage?: number
|
||||
status?: string
|
||||
supplyProductName?: string
|
||||
} = {}) {
|
||||
const page = Math.max(1, Number(input.page || 1) || 1)
|
||||
const perPage = Math.min(100, Math.max(1, Number(input.perPage || 20) || 20))
|
||||
const filters: JsonObject = {}
|
||||
const status = String(input.status || 'on_sale').trim()
|
||||
const supplyProductName = String(input.supplyProductName || '').trim()
|
||||
|
||||
if (status) filters.status = status
|
||||
if (supplyProductName) filters.supply_product_name = supplyProductName
|
||||
|
||||
const response = await kuaishouFeifeiRequest('/api/open/v1/products/index', {
|
||||
page,
|
||||
per_page: perPage,
|
||||
filters,
|
||||
})
|
||||
|
||||
return mapKuaishouFeifeiProductList(response.data)
|
||||
}
|
||||
|
||||
export async function createKuaishouFeifeiOrder(input: {
|
||||
platformOrderNo: string
|
||||
productCode: string
|
||||
@@ -73,3 +97,40 @@ export function mapKuaishouFeifeiOrder(value: unknown) {
|
||||
raw: source,
|
||||
}
|
||||
}
|
||||
|
||||
export function mapKuaishouFeifeiProductList(value: unknown) {
|
||||
const source = value && typeof value === 'object' && !Array.isArray(value)
|
||||
? value as JsonObject
|
||||
: {}
|
||||
const list = Array.isArray(source.list) ? source.list : []
|
||||
|
||||
return {
|
||||
items: list.map((item) => mapKuaishouFeifeiProduct(item)),
|
||||
total: Number(source.total || list.length) || 0,
|
||||
page: Number(source.page || 1) || 1,
|
||||
perPage: Number(source.per_page || source.perPage || list.length || 20) || 20,
|
||||
raw: source,
|
||||
}
|
||||
}
|
||||
|
||||
export function mapKuaishouFeifeiProduct(value: unknown) {
|
||||
const source = value && typeof value === 'object' && !Array.isArray(value)
|
||||
? value as JsonObject
|
||||
: {}
|
||||
|
||||
return {
|
||||
productCode: String(source.product_code || '').trim(),
|
||||
name: String(source.name || '').trim(),
|
||||
channel: String(source.channel || '').trim(),
|
||||
externalProductId: String(source.external_product_id || '').trim(),
|
||||
imageUrl: String(source.image_url || '').trim(),
|
||||
status: String(source.status || '').trim(),
|
||||
supplyStatus: String(source.supply_status || '').trim(),
|
||||
supplyPricePoints: Number(source.supply_price_points || 0) || 0,
|
||||
feePoints: Number(source.fee_points || 0) || 0,
|
||||
unitCostPoints: Number(source.unit_cost_points || 0) || 0,
|
||||
maxOrderQuantity: Number(source.max_order_quantity || 0) || 0,
|
||||
salePricePoints: source.sale_price_points == null ? null : Number(source.sale_price_points || 0) || 0,
|
||||
raw: source,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user