web-next: change-race/faction/level-up/gold por SumUp + rastreador de misiones

- /change-race-character (3€), /change-faction-character (5€, valida condiciones:
  sin hermandad/correo/subastas/capitán-arena y oro <= cap por nivel),
  /level-up-character (10€), /gold-character (oro por correo SOAP). Cada uno borra
  su ruta antigua y actualiza el link del panel.
- SumUp: hook precheck en PaidServiceConfig (condiciones cambio de facción);
  columna home_stripelog.metadata para campos extra (gold_amount).
- /quest-character: rastreador de estado de misiones/cadenas (clase, Hijos de
  Hodir, misión por ID) en personaje offline; cooldowns por categoría, Turnstile,
  IDs de misión verificados en wotlkdb 3.3.5a. Tabla home_questsearchhistory.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-14 12:39:28 +00:00
parent f7a376bd7c
commit eaae16e33a
24 changed files with 1154 additions and 112 deletions
+26 -6
View File
@@ -31,6 +31,8 @@ interface CreateParams {
// servicio sobre `characterName` en vez de acreditar PD. Sin `service` = compra de PD.
service?: string
characterName?: string
// Campos extra del servicio (p.ej. { gold_amount }) que la entrega necesita.
metadata?: Record<string, string>
}
/**
@@ -60,9 +62,21 @@ export async function createSumUpCheckout(p: CreateParams): Promise<{ success: b
if (!url) return { success: false, error: 'noHostedUrl' }
await db(DB.default).query(
'INSERT INTO home_stripelog (account_id, username, email, acore_ip, stripe_ip, product_name, amount, session_id, mode, character_name, service, timestamp, fulfilled) ' +
'VALUES (?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, NOW(), 0)',
[p.accountId, p.username, p.email, p.acoreIp, p.description, p.amount, p.reference, 'sumup', p.characterName ?? p.username, p.service ?? null],
'INSERT INTO home_stripelog (account_id, username, email, acore_ip, stripe_ip, product_name, amount, session_id, mode, character_name, service, metadata, timestamp, fulfilled) ' +
'VALUES (?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, NOW(), 0)',
[
p.accountId,
p.username,
p.email,
p.acoreIp,
p.description,
p.amount,
p.reference,
'sumup',
p.characterName ?? p.username,
p.service ?? null,
p.metadata ? JSON.stringify(p.metadata) : null,
],
)
return { success: true, url }
} catch (e) {
@@ -96,7 +110,7 @@ export async function fulfillSumUpCheckout(
if (!paid) return { ok: false, paid: false }
const [rows] = await db(DB.default).query<RowDataPacket[]>(
'SELECT id, account_id, amount, service, character_name FROM home_stripelog WHERE session_id = ? AND mode = ?',
'SELECT id, account_id, amount, service, character_name, metadata FROM home_stripelog WHERE session_id = ? AND mode = ?',
[reference, 'sumup'],
)
const log = rows[0]
@@ -109,11 +123,17 @@ export async function fulfillSumUpCheckout(
)
if (res.affectedRows === 0) return { ok: false, paid: true }
// Pago de servicio (renombrar…): ejecuta la acción sobre el personaje.
// Pago de servicio (renombrar, oro…): ejecuta la acción sobre el personaje.
const service = log.service ? String(log.service) : ''
const cfg = service ? getPaidService(service) : null
if (cfg) {
const ok = await cfg.fulfill(String(log.character_name), { service })
let meta: Record<string, string> = { service }
try {
if (log.metadata) meta = { ...JSON.parse(String(log.metadata)), service }
} catch {
/* metadata corrupto: seguimos con lo básico */
}
const ok = await cfg.fulfill(String(log.character_name), meta)
return { ok, paid: true, service, character: String(log.character_name) }
}