2026-06-02 13:14:10 +08:00
"use strict" ;
const ENTERPRISE _VIDEO _ALLOWED _MODELS = new Set ( [
"happyhorse-1.0" ,
"happyhorse-1.0-t2v" ,
"happyhorse-1.0-i2v" ,
"happyhorse-1.0-r2v" ,
"wan2.7-i2v" ,
"wan2.2-animate-mix" ,
"wan2.2-s2v" ,
"kling-3.0-dashscope" ,
"kling-v3-omni-dashscope" ,
"kling/kling-v3-omni-video-generation" ,
2026-06-04 18:58:45 +08:00
"vidu-q3-turbo" ,
"vidu-q3-turbo-t2v" ,
"vidu-q3-turbo-i2v" ,
"pixverse-c1" ,
"pixverse-c1-t2v" ,
"pixverse-c1-i2v" ,
2026-06-02 13:14:10 +08:00
] ) ;
2026-06-08 16:03:49 +08:00
const CREDITS _PER _CNY = 100 ;
const CREDIT _UNITS _PER _CREDIT = 100 ;
2026-06-10 14:27:42 +08:00
const ENTERPRISE _VIDEO _RESOLUTIONS = [ "720P" , "1080P" ] ;
const ENTERPRISE _VIDEO _DEFAULT _RESOLUTION = "1080P" ;
const ENTERPRISE _VIDEO _PRICING _RULES = [
{
id : "happyhorse" ,
modelIncludes : [ "happyhorse" ] ,
rates : { "720P" : 0.72 , "1080P" : 1.28 } ,
} ,
{
id : "wanxiang-i2v" ,
modelIncludes : [ "wan2.7-i2v" , "wanxiang" ] ,
rates : { "720P" : 0.6 , "1080P" : 1 } ,
} ,
{
id : "wan-animate-s2v" ,
modelIncludes : [ "animate-mix" , "s2v" ] ,
rates : { "720P" : 0.6 , "1080P" : 1 } ,
} ,
{
id : "kling-muted-reference" ,
modelIncludes : [ "kling" ] ,
when : { muted : true , hasReferenceVideo : true } ,
rates : { "720P" : 0.9 , "1080P" : 1.2 } ,
} ,
{
id : "kling-muted" ,
modelIncludes : [ "kling" ] ,
when : { muted : true , hasReferenceVideo : false } ,
rates : { "720P" : 0.6 , "1080P" : 0.8 } ,
} ,
{
id : "kling-default" ,
modelIncludes : [ "kling" ] ,
rates : { "720P" : 0.9 , "1080P" : 1.2 } ,
} ,
{
id : "vidu" ,
modelIncludes : [ "vidu" ] ,
rates : { "720P" : 0.6 , "1080P" : 1 } ,
} ,
{
id : "pixverse" ,
modelIncludes : [ "pixverse" ] ,
rates : { "720P" : 0.6 , "1080P" : 1 } ,
} ,
] ;
2026-06-08 16:03:49 +08:00
2026-06-02 13:14:10 +08:00
function normalizeModel ( value ) {
return String ( value || "" ) . trim ( ) . toLowerCase ( ) ;
}
function normalizeEnterpriseVideoResolution ( value ) {
return String ( value || "" ) . trim ( ) . toUpperCase ( ) === "720P" ? "720P" : "1080P" ;
}
function normalizeEnterpriseVideoDuration ( value ) {
const numeric = Number ( value ) ;
if ( ! Number . isFinite ( numeric ) ) return 1 ;
return Math . max ( 1 , Math . ceil ( numeric ) ) ;
}
2026-06-10 14:27:42 +08:00
function enterpriseVideoPricingRuleMatches ( rule , input , model ) {
if ( ! rule . modelIncludes . some ( ( pattern ) => model . includes ( pattern ) ) ) return false ;
if ( ! rule . when ) return true ;
if ( Object . prototype . hasOwnProperty . call ( rule . when , "muted" ) && Boolean ( input . muted ) !== rule . when . muted ) {
return false ;
}
if (
Object . prototype . hasOwnProperty . call ( rule . when , "hasReferenceVideo" ) &&
Boolean ( input . hasReferenceVideo ) !== rule . when . hasReferenceVideo
) {
return false ;
}
return true ;
}
2026-06-02 13:14:10 +08:00
function isEnterpriseVideoBillingUser ( user ) {
return Boolean ( user ? . enterpriseId ) ;
}
function isEnterpriseVideoModelAllowed ( providerConfig , model ) {
const requestedModel = normalizeModel ( model || providerConfig ? . requestedModel || providerConfig ? . model ) ;
const resolvedModel = normalizeModel ( providerConfig ? . model ) ;
const protocol = String ( providerConfig ? . protocol || "" ) . toLowerCase ( ) ;
if ( ENTERPRISE _VIDEO _ALLOWED _MODELS . has ( requestedModel ) ) return true ;
if ( ENTERPRISE _VIDEO _ALLOWED _MODELS . has ( resolvedModel ) ) return true ;
if ( protocol . startsWith ( "happyhorse-" ) ) return true ;
if ( protocol === "wan-i2v" ) return true ;
if ( protocol === "wan-animate-mix" ) return true ;
if ( protocol === "wan-s2v" ) return true ;
if ( protocol === "kling-dashscope" ) return true ;
2026-06-04 18:58:45 +08:00
if ( protocol === "vidu" ) return true ;
if ( protocol === "pixverse" ) return true ;
2026-06-02 13:14:10 +08:00
return false ;
}
function assertEnterpriseVideoModelAllowed ( providerConfig , model ) {
if ( isEnterpriseVideoModelAllowed ( providerConfig , model ) ) return ;
const error = new Error ( "该企业账号不可使用当前视频模型" ) ;
error . status = 403 ;
error . code = "ENTERPRISE_VIDEO_MODEL_NOT_ALLOWED" ;
throw error ;
}
function getEnterpriseVideoCreditRate ( input ) {
const resolution = normalizeEnterpriseVideoResolution ( input . resolution || input . quality ) ;
const model = normalizeModel ( input . model || input . requestedModel ) ;
2026-06-10 14:27:42 +08:00
const rule = ENTERPRISE _VIDEO _PRICING _RULES . find ( ( candidate ) =>
enterpriseVideoPricingRuleMatches ( candidate , input , model ) ,
) ;
if ( rule ) return rule . rates [ resolution ] ? ? rule . rates [ ENTERPRISE _VIDEO _DEFAULT _RESOLUTION ] ;
2026-06-04 18:58:45 +08:00
2026-06-02 13:14:10 +08:00
const error = new Error ( ` Unsupported enterprise video model: ${ input . model || input . requestedModel } ` ) ;
error . status = 403 ;
error . code = "ENTERPRISE_VIDEO_MODEL_NOT_ALLOWED" ;
throw error ;
}
2026-06-10 14:27:42 +08:00
function getEnterpriseVideoPricingConfig ( ) {
return {
currency : "CNY" ,
creditsPerCny : CREDITS _PER _CNY ,
billingUnit : "per_second" ,
defaultResolution : ENTERPRISE _VIDEO _DEFAULT _RESOLUTION ,
resolutions : [ ... ENTERPRISE _VIDEO _RESOLUTIONS ] ,
rules : ENTERPRISE _VIDEO _PRICING _RULES . map ( ( rule ) => ( {
id : rule . id ,
modelIncludes : [ ... rule . modelIncludes ] ,
when : rule . when ? { ... rule . when } : undefined ,
rates : { ... rule . rates } ,
} ) ) ,
} ;
}
2026-06-02 13:14:10 +08:00
function calculateEnterpriseVideoCredits ( input ) {
const duration = normalizeEnterpriseVideoDuration ( input . durationSeconds || input . duration ) ;
2026-06-08 16:03:49 +08:00
return Number ( ( getEnterpriseVideoCreditRate ( input ) * duration * CREDITS _PER _CNY ) . toFixed ( 2 ) ) ;
2026-06-02 13:14:10 +08:00
}
function calculateEnterpriseVideoCost ( input ) {
const resolution = normalizeEnterpriseVideoResolution ( input . resolution || input . quality ) ;
const durationSeconds = normalizeEnterpriseVideoDuration ( input . durationSeconds || input . duration ) ;
const rateCreditsPerSecond = getEnterpriseVideoCreditRate ( {
... input ,
resolution ,
durationSeconds ,
} ) ;
2026-06-08 16:03:49 +08:00
const rateCentsPerSecond = Math . round ( rateCreditsPerSecond * CREDITS _PER _CNY * CREDIT _UNITS _PER _CREDIT ) ;
2026-06-02 13:14:10 +08:00
return {
resolution ,
durationSeconds ,
rateCentsPerSecond ,
amountCents : rateCentsPerSecond * durationSeconds ,
} ;
}
function prepareEnterpriseVideoBilling ( { user , providerConfig , params } ) {
if ( ! isEnterpriseVideoBillingUser ( user ) ) return null ;
assertEnterpriseVideoModelAllowed ( providerConfig , params . requestedModel || params . model ) ;
const hasReferenceVideo = Boolean ( params . hasReferenceVideo ) ;
const muted = Boolean ( params . muted ) ;
const pricing = calculateEnterpriseVideoCost ( {
model : params . model ,
requestedModel : params . requestedModel ,
resolution : params . resolution || params . quality ,
duration : params . duration ,
muted ,
hasReferenceVideo ,
} ) ;
return {
enterpriseId : user . enterpriseId ,
userId : user . id ,
model : params . model ,
requestedModel : params . requestedModel ,
muted ,
hasReferenceVideo ,
... pricing ,
} ;
}
async function reserveEnterpriseVideoCredits ( client , billing ) {
if ( ! billing || billing . amountCents <= 0 ) return null ;
const { rows : balanceRows } = await client . query (
"UPDATE enterprises SET balance_cents = balance_cents - $1, updated_at = NOW() WHERE id = $2 AND enabled = 1 AND balance_cents >= $1 RETURNING balance_cents" ,
[ billing . amountCents , billing . enterpriseId ] ,
) ;
if ( balanceRows . length === 0 ) {
const error = new Error (
` 企业积分不足,预计需要 ${ Number ( ( billing . amountCents / 100 ) . toFixed ( 2 ) ) } 积分 ` ,
) ;
error . status = 402 ;
error . code = "INSUFFICIENT_ENTERPRISE_BALANCE" ;
throw error ;
}
const {
rows : [ ledger ] ,
} = await client . query (
`
INSERT INTO credit_ledger (
enterprise_id,
user_id,
task_id,
model,
task_type,
resolution,
duration_seconds,
rate_cents_per_second,
amount_cents,
status
)
VALUES ( $ 1, $ 2, $ 3, $ 4, 'video', $ 5, $ 6, $ 7, $ 8, 'reserved')
RETURNING id
` ,
[
billing . enterpriseId ,
billing . userId ,
billing . taskId || null ,
billing . requestedModel || billing . model ,
billing . resolution ,
billing . durationSeconds ,
billing . rateCentsPerSecond ,
billing . amountCents ,
] ,
) ;
return {
... billing ,
creditLedgerId : ledger . id ,
enterpriseBalanceCents : Number ( balanceRows [ 0 ] . balance _cents || 0 ) ,
} ;
}
async function markEnterpriseVideoCreditsAccepted ( clientOrPool , creditLedgerId ) {
if ( ! creditLedgerId ) return false ;
const { rowCount } = await clientOrPool . query (
"UPDATE credit_ledger SET status = 'charged', updated_at = NOW() WHERE id = $1 AND status = 'reserved'" ,
[ creditLedgerId ] ,
) ;
return rowCount > 0 ;
}
2026-06-04 18:58:45 +08:00
async function refundEnterpriseVideoCredits ( clientOrPool , billing , reason ) {
if ( ! billing || ! billing . creditLedgerId ) return false ;
const { rowCount } = await clientOrPool . query (
"UPDATE credit_ledger SET status = 'refunded', refund_reason = $1, updated_at = NOW() WHERE id = $2 AND status = 'reserved'" ,
[ reason || null , billing . creditLedgerId ] ,
) ;
if ( rowCount > 0 && billing . amountCents > 0 && billing . enterpriseId ) {
await clientOrPool . query (
"UPDATE enterprises SET balance_cents = balance_cents + $1, updated_at = NOW() WHERE id = $2" ,
[ billing . amountCents , billing . enterpriseId ] ,
) ;
}
return rowCount > 0 ;
}
2026-06-02 13:14:10 +08:00
module . exports = {
ENTERPRISE _VIDEO _ALLOWED _MODELS ,
assertEnterpriseVideoModelAllowed ,
calculateEnterpriseVideoCost ,
calculateEnterpriseVideoCredits ,
2026-06-10 14:27:42 +08:00
getEnterpriseVideoPricingConfig ,
2026-06-02 13:14:10 +08:00
getEnterpriseVideoCreditRate ,
isEnterpriseVideoBillingUser ,
isEnterpriseVideoModelAllowed ,
markEnterpriseVideoCreditsAccepted ,
normalizeEnterpriseVideoDuration ,
normalizeEnterpriseVideoResolution ,
prepareEnterpriseVideoBilling ,
2026-06-04 18:58:45 +08:00
refundEnterpriseVideoCredits ,
2026-06-02 13:14:10 +08:00
reserveEnterpriseVideoCredits ,
} ;