From 671632a6a0c6421eddf7f9bb6c4f1f0045b9c737 Mon Sep 17 00:00:00 2001 From: adevopg Date: Tue, 14 Jul 2026 13:17:23 +0000 Subject: [PATCH] =?UTF-8?q?web-next:=20/restore-items=20cobra=20por=20SumU?= =?UTF-8?q?p=20(1=E2=82=AC/=C3=ADtem)=20en=20vez=20de=20PD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Nuevo servicio de pago `restore-item` (getRestoreItemPrice default 1€, fulfill SOAP `.item restore `). Al pulsar "Recuperar" se crea un checkout de SumUp y al pagar se ejecuta el restore. - Se quita el descuento de 100 PD y el gate "No tienes PD suficientes". - La búsqueda sigue igual (gratis, cooldown 8h, Turnstile). Co-Authored-By: Claude Opus 4.8 (1M context) --- web-next/app/[locale]/restore-items/page.tsx | 12 ++---- .../character/restore-items/restore/route.ts | 17 -------- web-next/components/RestoreItemsForm.tsx | 31 +++++--------- web-next/lib/paid-services.ts | 9 ++++ web-next/lib/prices.ts | 1 + web-next/lib/restore-items.ts | 41 +------------------ 6 files changed, 27 insertions(+), 84 deletions(-) delete mode 100644 web-next/app/api/character/restore-items/restore/route.ts diff --git a/web-next/app/[locale]/restore-items/page.tsx b/web-next/app/[locale]/restore-items/page.tsx index 2fec1e9..ea357c6 100644 --- a/web-next/app/[locale]/restore-items/page.tsx +++ b/web-next/app/[locale]/restore-items/page.tsx @@ -3,8 +3,7 @@ import { setRequestLocale } from 'next-intl/server' import { redirect } from '@/i18n/navigation' import { getSession } from '@/lib/session' import { getGameCharacters } from '@/lib/characters' -import { getDPointsBalance } from '@/lib/dpoints' -import { RESTORE_ITEM_PD } from '@/lib/restore-items' +import { getRestoreItemPrice } from '@/lib/prices' import { PageShell } from '@/components/PageShell' import { ServiceBox } from '@/components/ServiceBox' import { RestoreItemsForm } from '@/components/RestoreItemsForm' @@ -21,7 +20,7 @@ export default async function RestoreItemsPage({ params }: { params: Promise<{ l if (!session.bnetId) redirect({ href: '/login', locale }) if (!session.username) redirect({ href: '/select-account', locale }) - const [chars, balance] = await Promise.all([getGameCharacters(session.accountId!), getDPointsBalance(session.accountId!)]) + const [chars, price] = await Promise.all([getGameCharacters(session.accountId!), getRestoreItemPrice()]) return ( @@ -51,12 +50,9 @@ export default async function RestoreItemsPage({ params }: { params: Promise<{ l


-

Cada ítem requiere {RESTORE_ITEM_PD} PD

+

Cada ítem requiere {price} (SumUp)


- ({ name: c.name, classCss: c.classCss }))} - canAfford={balance >= RESTORE_ITEM_PD} - /> + ({ name: c.name, classCss: c.classCss }))} price={price} />
diff --git a/web-next/app/api/character/restore-items/restore/route.ts b/web-next/app/api/character/restore-items/restore/route.ts deleted file mode 100644 index 4e6533a..0000000 --- a/web-next/app/api/character/restore-items/restore/route.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { getSession } from '@/lib/session' -import { restoreItem } from '@/lib/restore-items' - -/** Restaura un ítem borrado (cuesta 100 PD). */ -export async function POST(request: Request) { - const session = await getSession() - if (!session.accountId || !session.username) { - return Response.json({ success: false, error: 'notAuthenticated' }, { status: 401 }) - } - let body: Record = {} - try { - body = await request.json() - } catch { - return Response.json({ success: false, error: 'invalidRequest' }, { status: 400 }) - } - return Response.json(await restoreItem(session.accountId, String(body.character ?? '').trim(), Number(body.recoverId))) -} diff --git a/web-next/components/RestoreItemsForm.tsx b/web-next/components/RestoreItemsForm.tsx index b3295c0..b83e7a7 100644 --- a/web-next/components/RestoreItemsForm.tsx +++ b/web-next/components/RestoreItemsForm.tsx @@ -32,7 +32,7 @@ function itemsError(error?: string, minutes?: number): string { } } -export function RestoreItemsForm({ characters, canAfford }: { characters: CharOption[]; canAfford: boolean }) { +export function RestoreItemsForm({ characters, price }: { characters: CharOption[]; price: number }) { const [character, setCharacter] = useState('') const [captcha, setCaptcha] = useState('') const [captchaKey, setCaptchaKey] = useState(0) @@ -40,14 +40,6 @@ export function RestoreItemsForm({ characters, canAfford }: { characters: CharOp const [items, setItems] = useState(null) const [message, setMessage] = useState<{ ok: boolean; text: string } | null>(null) - if (!canAfford) { - return ( -
- No tienes PD suficientes -
- ) - } - async function search(e: React.FormEvent) { e.preventDefault() if (busy || !character) return @@ -78,26 +70,25 @@ export function RestoreItemsForm({ characters, canAfford }: { characters: CharOp async function restore(item: DeletedItem) { if (busy) return - if (!window.confirm(`¿Recuperar "${item.name}" por 100 PD?`)) return + if (!window.confirm(`¿Recuperar "${item.name}" por ${price} € (SumUp)?`)) return setBusy(true) setMessage(null) try { - const res = await fetch('/api/character/restore-items/restore', { + const res = await fetch('/api/character/restore-item/checkout', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'same-origin', - body: JSON.stringify({ character, recoverId: item.recoverId }), + body: JSON.stringify({ character, recover_id: String(item.recoverId), provider: 'sumup' }), }) - const data: { success?: boolean; error?: string } = await res.json() - if (data.success) { - setItems((prev) => (prev ? prev.filter((i) => i.recoverId !== item.recoverId) : prev)) - setMessage({ ok: true, text: `¡"${item.name}" recuperado! Se han descontado 100 PD.` }) - } else { - setMessage({ ok: false, text: itemsError(data.error) }) + const data: { success?: boolean; url?: string; error?: string; message?: string } = await res.json() + if (data.success && data.url) { + window.location.assign(data.url) // checkout de SumUp + return } + setMessage({ ok: false, text: data.message || itemsError(data.error) }) + setBusy(false) } catch { setMessage({ ok: false, text: itemsError() }) - } finally { setBusy(false) } } @@ -139,7 +130,7 @@ export function RestoreItemsForm({ characters, canAfford }: { characters: CharOp diff --git a/web-next/lib/paid-services.ts b/web-next/lib/paid-services.ts index fdd311a..f8382c0 100644 --- a/web-next/lib/paid-services.ts +++ b/web-next/lib/paid-services.ts @@ -9,6 +9,7 @@ import { getChangeFactionPrice, getLevelUpPrice, getTransferPrice, + getRestoreItemPrice, goldPriceFor, } from './prices' @@ -86,6 +87,14 @@ export const PAID_SERVICES: Record = { fulfill: (c, m) => sendGoldByMail(c, Number(m.gold_amount)), extraFields: ['gold_amount'], }, + // Recuperar un ítem borrado (por SumUp): al pagar se ejecuta `.item restore`. + 'restore-item': { + price: () => getRestoreItemPrice(), + productName: (c, m) => `Recuperar ítem #${m.recover_id} de ${c}`, + fulfill: (c, m) => + /^\d+$/.test(String(m.recover_id ?? '')) ? soapFulfill(`.item restore ${m.recover_id} ${c}`) : Promise.resolve(false), + extraFields: ['recover_id'], + }, transfer: { price: () => getTransferPrice(), productName: (c, m) => `Transferir ${c} a la cuenta ${m.destination_account}`, diff --git a/web-next/lib/prices.ts b/web-next/lib/prices.ts index aada602..bb33bc2 100644 --- a/web-next/lib/prices.ts +++ b/web-next/lib/prices.ts @@ -16,6 +16,7 @@ export const getChangeRacePrice = () => priceFrom('home_changeraceprice', 10.0) export const getChangeFactionPrice = () => priceFrom('home_changefactionprice', 10.0) export const getLevelUpPrice = () => priceFrom('home_levelupprice', 10.0) export const getTransferPrice = () => priceFrom('home_transferprice', 10.0) +export const getRestoreItemPrice = () => priceFrom('home_restoreitemprice', 1.0) export interface GoldOption { gold_amount: number diff --git a/web-next/lib/restore-items.ts b/web-next/lib/restore-items.ts index 369514f..ed7389a 100644 --- a/web-next/lib/restore-items.ts +++ b/web-next/lib/restore-items.ts @@ -7,11 +7,10 @@ import { getGameCharacters } from './characters' * Recuperar ítems (/restore-items): usa el sistema de recuperación de AzerothCore * por SOAP. `.item restore list ` lista los ítems borrados recuperables (el * core aplica las condiciones: calidad Raro+, equipable, se liga al recoger, - * borrado < 7 días). `.item restore ` restaura uno. Cada - * restauración cuesta **100 PD**. La consulta tiene cooldown de 8 h por personaje. + * borrado < 7 días). La consulta tiene cooldown de 8 h por personaje. La restauración + * de cada ítem se paga por **SumUp** (servicio `restore-item`, ejecuta `.item restore`). */ -export const RESTORE_ITEM_PD = 100 const SEARCH_COOLDOWN_MS = 8 * 60 * 60 * 1000 const SAFE_NAME = /^[A-Za-z]{1,12}$/ @@ -74,39 +73,3 @@ export async function listDeletedItems(accountId: number, character: string): Pr .catch(() => {}) return { success: true, items: parseRecoverList(soap) } } - -export interface RestoreResult { - success: boolean - error?: string - name?: string -} - -/** Restaura un ítem borrado (cuesta 100 PD; reembolsa si el SOAP falla). */ -export async function restoreItem(accountId: number, character: string, recoverId: number): Promise { - if (!(await ownsCharacter(accountId, character))) return { success: false, error: 'characterNotOwned' } - if (!Number.isInteger(recoverId) || recoverId <= 0) return { success: false, error: 'invalidRequest' } - - // Cobra 100 PD de forma atómica. - let charged = false - try { - const [res] = await db(DB.default).query( - 'UPDATE home_api_points SET dp = dp - ? WHERE accountID = ? AND dp >= ?', - [RESTORE_ITEM_PD, accountId, RESTORE_ITEM_PD], - ) - // @ts-expect-error affectedRows - charged = res.affectedRows === 1 - } catch { - return { success: false, error: 'genericError' } - } - if (!charged) return { success: false, error: 'insufficientPoints' } - - const soap = await executeSoapCommand(`.item restore ${recoverId} ${character}`) - if (soap === null) { - await db(DB.default) - .query('UPDATE home_api_points SET dp = dp + ? WHERE accountID = ?', [RESTORE_ITEM_PD, accountId]) - .catch(() => {}) - return { success: false, error: 'soapError' } - } - - return { success: true } -}