diff --git a/web-next/app/[locale]/customize-character/page.tsx b/web-next/app/[locale]/customize-character/page.tsx new file mode 100644 index 0000000..a8cada6 --- /dev/null +++ b/web-next/app/[locale]/customize-character/page.tsx @@ -0,0 +1,76 @@ +import type { Metadata } from 'next' +import { setRequestLocale } from 'next-intl/server' +import { redirect } from '@/i18n/navigation' +import { getSession } from '@/lib/session' +import { getGameCharacters } from '@/lib/characters' +import { getCustomizePrice } from '@/lib/prices' +import { PageShell } from '@/components/PageShell' +import { ServiceBox } from '@/components/ServiceBox' +import { PaidServiceForm } from '@/components/PaidServiceForm' + +export const dynamic = 'force-dynamic' + +export const metadata: Metadata = { title: 'Personalizar personaje' } + +export default async function CustomizeCharacterPage({ params }: { params: Promise<{ locale: string }> }) { + const { locale } = await params + setRequestLocale(locale) + + const session = await getSession() + if (!session.bnetId) redirect({ href: '/login', locale }) + if (!session.username) redirect({ href: '/select-account', locale }) + + const [chars, price] = await Promise.all([getGameCharacters(session.accountId!), getCustomizePrice()]) + + return ( + + +
+

+ La herramienta Personalizar personaje te permite cambiar los siguientes aspectos un personaje: +

+

- Color de la piel

+

- Rostro

+

- Pelo

+

- Sexo

+

- Características correspondientes a cada raza como cuernos, pendientes, marcas, vello facial

+

Si también deseas cambiar el nombre del personaje, debes usar la herramienta Renombrar personaje.

+
+

+ Al usar el botón PERSONALIZAR del personaje que hayas escogido, se enviará una petición de personalización de + dicho personaje. +

+

Cuando ingreses a la lista de personajes de tu cuenta, verás un ícono a la izquierda de tu personaje escogido.

+

+ Al darle click a dicho ícono ingresarás al menú de edición de personaje que te permitirá escoger diferentes + características. +

+
+
+ NOTA +
+

+ Por favor, asegúrate de haber escogido al personaje correcto ya que esta acción no es reversible una vez + realizada. +

+
+
+ +
+
+
+

Requiere {price} (SumUp)

+
+ + ({ name: c.name, classCss: c.classCss }))} + checkoutEndpoint="/api/character/customize/checkout" + payLabel="PERSONALIZAR" + buttonClass="customize-button" + provider="sumup" + confirmText={`¿Estás seguro de personalizar al personaje seleccionado por ${price} € (SumUp)?`} + /> +
+
+ ) +} diff --git a/web-next/app/[locale]/customize/page.tsx b/web-next/app/[locale]/customize/page.tsx deleted file mode 100644 index 13503b7..0000000 --- a/web-next/app/[locale]/customize/page.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { ServicePageContent } from '@/components/ServicePageContent' - -export const dynamic = 'force-dynamic' - -export default async function Page({ params }: { params: Promise<{ locale: string }> }) { - const { locale } = await params - return -} diff --git a/web-next/app/[locale]/d-points-success/page.tsx b/web-next/app/[locale]/d-points-success/page.tsx index ecddcdd..e27f3a2 100644 --- a/web-next/app/[locale]/d-points-success/page.tsx +++ b/web-next/app/[locale]/d-points-success/page.tsx @@ -45,7 +45,7 @@ export default async function DPointsSuccessPage({

- ← Regresar a mi cuenta + ← Regresar a mi cuenta

diff --git a/web-next/app/[locale]/login/LoginForm.tsx b/web-next/app/[locale]/login/LoginForm.tsx index e95b760..5a755d6 100644 --- a/web-next/app/[locale]/login/LoginForm.tsx +++ b/web-next/app/[locale]/login/LoginForm.tsx @@ -36,7 +36,7 @@ export function LoginForm() { const data: { success?: boolean; needsSelection?: boolean; error?: string } = await res.json() if (data.success) { setMessage({ ok: true, text: t('success') }) - router.push(data.needsSelection ? '/select-account' : '/account') + router.push(data.needsSelection ? '/select-account' : '/my-account') } else { const key = (ERROR_KEYS as readonly string[]).includes(data.error ?? '') ? data.error! : 'genericError' setMessage({ ok: false, text: t(key) }) diff --git a/web-next/app/[locale]/account/page.tsx b/web-next/app/[locale]/my-account/page.tsx similarity index 100% rename from web-next/app/[locale]/account/page.tsx rename to web-next/app/[locale]/my-account/page.tsx diff --git a/web-next/app/[locale]/page.tsx b/web-next/app/[locale]/page.tsx index 969fcdd..2d87945 100644 --- a/web-next/app/[locale]/page.tsx +++ b/web-next/app/[locale]/page.tsx @@ -1,4 +1,5 @@ import { setRequestLocale } from 'next-intl/server' +import { Link } from '@/i18n/navigation' import { ServerClock } from '@/components/ServerClock' import { getNoticias, getServerStatus, type Noticia, type ServerStatus } from '@/lib/home' import { getRealmName } from '@/lib/realm' @@ -92,11 +93,11 @@ export default async function HomePage({ params }: { params: Promise<{ locale: s
- ¡RECLUTA A UN AMIGO! + ¡RECLUTA A UN AMIGO!
- ¡RECLUTA A UN AMIGO! + ¡RECLUTA A UN AMIGO!
diff --git a/web-next/app/[locale]/recruit/page.tsx b/web-next/app/[locale]/recruit-a-friend/page.tsx similarity index 97% rename from web-next/app/[locale]/recruit/page.tsx rename to web-next/app/[locale]/recruit-a-friend/page.tsx index 09e9102..0546533 100644 --- a/web-next/app/[locale]/recruit/page.tsx +++ b/web-next/app/[locale]/recruit-a-friend/page.tsx @@ -29,8 +29,8 @@ export default async function RecruitPage({ params }: { params: Promise<{ locale

Información

- Regresar a mi cuenta - Regresar + Regresar a mi cuenta + Regresar

diff --git a/web-next/app/[locale]/rename-character/page.tsx b/web-next/app/[locale]/rename-character/page.tsx new file mode 100644 index 0000000..2eb3b74 --- /dev/null +++ b/web-next/app/[locale]/rename-character/page.tsx @@ -0,0 +1,66 @@ +import type { Metadata } from 'next' +import { setRequestLocale } from 'next-intl/server' +import { redirect } from '@/i18n/navigation' +import { getSession } from '@/lib/session' +import { getGameCharacters } from '@/lib/characters' +import { getRenamePrice } from '@/lib/prices' +import { PageShell } from '@/components/PageShell' +import { ServiceBox } from '@/components/ServiceBox' +import { PaidServiceForm } from '@/components/PaidServiceForm' + +export const dynamic = 'force-dynamic' + +export const metadata: Metadata = { title: 'Renombrar personaje' } + +export default async function RenameCharacterPage({ params }: { params: Promise<{ locale: string }> }) { + const { locale } = await params + setRequestLocale(locale) + + const session = await getSession() + if (!session.bnetId) redirect({ href: '/login', locale }) + if (!session.username) redirect({ href: '/select-account', locale }) + + const [chars, price] = await Promise.all([getGameCharacters(session.accountId!), getRenamePrice()]) + + return ( + + +
+

+ La herramienta Renombrar personaje te permite cambiar de nombre a un personaje. +

+
+

+ Al usar el botón RENOMBRAR del personaje que hayas escogido, se enviará una petición de cambio de nombre de + dicho personaje. +

+

Cuando intentes entrar al reino con ese personaje, te pedirá que escojas un nuevo nombre.

+
+
+ NOTA +
+

+ Por favor, asegúrate de haber escogido al personaje correcto ya que esta acción no es reversible una vez + realizada. +

+
+
+ +
+
+
+

Requiere {price} (SumUp)

+
+ + ({ name: c.name, classCss: c.classCss }))} + checkoutEndpoint="/api/character/rename/checkout" + payLabel="RENOMBRAR" + buttonClass="rename-button" + provider="sumup" + confirmText={`¿Estás seguro de renombrar al personaje seleccionado por ${price} € (SumUp)?`} + /> +
+
+ ) +} diff --git a/web-next/app/[locale]/rename/page.tsx b/web-next/app/[locale]/rename/page.tsx deleted file mode 100644 index 6319f92..0000000 --- a/web-next/app/[locale]/rename/page.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { ServicePageContent } from '@/components/ServicePageContent' - -export const dynamic = 'force-dynamic' - -export default async function Page({ params }: { params: Promise<{ locale: string }> }) { - const { locale } = await params - return -} diff --git a/web-next/app/[locale]/revive-character/page.tsx b/web-next/app/[locale]/revive-character/page.tsx new file mode 100644 index 0000000..fab48bf --- /dev/null +++ b/web-next/app/[locale]/revive-character/page.tsx @@ -0,0 +1,8 @@ +import { ReviveServiceContent } from '@/components/ReviveServiceContent' + +export const dynamic = 'force-dynamic' + +export default async function ReviveCharacterPage({ params }: { params: Promise<{ locale: string }> }) { + const { locale } = await params + return +} diff --git a/web-next/app/[locale]/security-2falogin/page.tsx b/web-next/app/[locale]/security-2falogin/page.tsx index 8daac6c..b6de089 100644 --- a/web-next/app/[locale]/security-2falogin/page.tsx +++ b/web-next/app/[locale]/security-2falogin/page.tsx @@ -27,8 +27,8 @@ export default async function Security2faLoginPage({ params }: { params: Promise

Información

- Regresar a mi cuenta - Regresar + Regresar a mi cuenta + Regresar

diff --git a/web-next/app/[locale]/select-account/SelectAccountForm.tsx b/web-next/app/[locale]/select-account/SelectAccountForm.tsx index e87ea9f..1a713a7 100644 --- a/web-next/app/[locale]/select-account/SelectAccountForm.tsx +++ b/web-next/app/[locale]/select-account/SelectAccountForm.tsx @@ -27,7 +27,7 @@ export function SelectAccountForm({ accounts }: { accounts: GameAccount[] }) { body: JSON.stringify({ accountId: id }), }) const data: { success?: boolean } = await res.json() - if (data.success) router.push('/account') + if (data.success) router.push('/my-account') else { setError(t('error')) setBusy(null) diff --git a/web-next/app/[locale]/service-success/page.tsx b/web-next/app/[locale]/service-success/page.tsx index 6c72ca9..2608a24 100644 --- a/web-next/app/[locale]/service-success/page.tsx +++ b/web-next/app/[locale]/service-success/page.tsx @@ -1,6 +1,7 @@ import { getTranslations, setRequestLocale } from 'next-intl/server' import { fulfillCheckoutSession } from '@/lib/fulfill' import { isSessionPaid } from '@/lib/stripe' +import { fulfillSumUpCheckout } from '@/lib/sumup' import { PageShell } from '@/components/PageShell' export const dynamic = 'force-dynamic' @@ -10,19 +11,24 @@ export default async function ServiceSuccessPage({ searchParams, }: { params: Promise<{ locale: string }> - searchParams: Promise<{ service?: string; session_id?: string }> + searchParams: Promise<{ service?: string; session_id?: string; provider?: string; ref?: string }> }) { const { locale } = await params setRequestLocale(locale) - const { service: urlService, session_id } = await searchParams + const { service: urlService, session_id, provider, ref } = await searchParams const t = await getTranslations('Paid') - // Entrega compartida con el webhook (idempotente). Si el webhook ya entregó, - // el reclamo atómico devuelve ok=false pero el pago sigue confirmado. + // Entrega compartida con el webhook/reconciliación (idempotente vía reclamo atómico). let status: 'delivered' | 'already' | 'error' = 'error' let okName: string | null = null let service = urlService ?? null - if (session_id) { + if (provider === 'sumup' && ref) { + const r = await fulfillSumUpCheckout(ref) + service = r.service ?? urlService ?? null + okName = r.character ?? null + if (r.ok) status = 'delivered' + else if (r.paid) status = 'already' + } else if (session_id) { const r = await fulfillCheckoutSession(session_id, urlService) service = r.service ?? urlService ?? null if (r.ok) { diff --git a/web-next/app/[locale]/unstuck/page.tsx b/web-next/app/[locale]/unstuck-character/page.tsx similarity index 100% rename from web-next/app/[locale]/unstuck/page.tsx rename to web-next/app/[locale]/unstuck-character/page.tsx diff --git a/web-next/app/api/character/[service]/checkout/route.ts b/web-next/app/api/character/[service]/checkout/route.ts index e95af15..036f978 100644 --- a/web-next/app/api/character/[service]/checkout/route.ts +++ b/web-next/app/api/character/[service]/checkout/route.ts @@ -1,6 +1,8 @@ +import { randomUUID } from 'crypto' import { getSession } from '@/lib/session' import { getGameCharacters } from '@/lib/characters' import { createCheckoutSession } from '@/lib/stripe' +import { createSumUpCheckout, sumupConfigured } from '@/lib/sumup' import { getPaidService } from '@/lib/paid-services' export async function POST(request: Request, { params }: { params: Promise<{ service: string }> }) { @@ -39,6 +41,29 @@ export async function POST(request: Request, { params }: { params: Promise<{ ser const site = process.env.SITE_URL || '' const ip = (request.headers.get('x-forwarded-for') || '').split(',')[0].trim() || '0.0.0.0' + const productName = cfg.productName(character, metadata) + + // Pago por SumUp (euros): al pagar, la reconciliación/return ejecuta la acción + // del servicio sobre el personaje (no acredita PD). + if (String(body.provider) === 'sumup') { + if (!sumupConfigured()) return Response.json({ success: false, error: 'notConfigured' }) + const reference = randomUUID() + const r = await createSumUpCheckout({ + accountId: session.accountId, + username: session.username || '', + email: session.bnetEmail || '', + acoreIp: ip, + amount: price, + points: 0, + reference, + description: productName, + returnUrl: `${site}/service-success?service=${service}&provider=sumup&ref=${reference}`, + service, + characterName: character, + }) + if (!r.success || !r.url) return Response.json({ success: false, error: r.error || 'sumupError' }) + return Response.json({ success: true, url: r.url }) + } const r = await createCheckoutSession({ accountId: session.accountId, @@ -47,7 +72,7 @@ export async function POST(request: Request, { params }: { params: Promise<{ ser acoreIp: ip, amount: price, characterName: character, - productName: cfg.productName(character, metadata), + productName, successUrl: `${site}/service-success?service=${service}`, cancelUrl: `${site}/${service}`, metadata, diff --git a/web-next/components/AccountTools.tsx b/web-next/components/AccountTools.tsx index 1282a69..189cd59 100644 --- a/web-next/components/AccountTools.tsx +++ b/web-next/components/AccountTools.tsx @@ -19,7 +19,7 @@ export function AccountTools({ isAdmin }: { isAdmin: boolean }) { { href: '/trade-points', icon: 'trade-pd-icon', label: t('svcTradePD'), desc: t('svcTradePDDesc') }, { href: '/promo-code', icon: 'promo-icon', label: t('svcPromo'), desc: t('svcPromoDesc') }, { href: '/security-token', icon: 'token-icon', label: t('svcToken'), desc: t('svcTokenDesc') }, - { href: '/recruit', icon: 'recruit-a-friend-icon', label: t('svcRecruit'), desc: t('svcRecruitDesc') }, + { href: '/recruit-a-friend', icon: 'recruit-a-friend-icon', label: t('svcRecruit'), desc: t('svcRecruitDesc') }, { href: '/rename-guild', icon: 'rename-guild-icon', label: t('svcRenameGuild'), desc: t('svcRenameGuildDesc') }, { href: '/vote-points', icon: 'vote-icon', label: t('svcVote'), desc: t('svcVoteDesc') }, { href: '/d-points', icon: 'dnt-icon', label: t('svcDPoints'), desc: t('svcDPointsDesc') }, @@ -29,10 +29,10 @@ export function AccountTools({ isAdmin }: { isAdmin: boolean }) { ...(isAdmin ? [{ href: '/admin', icon: 'store-icon', label: `🛡️ ${t('adminPanel')}`, desc: t('adminPanelDesc') }] : []), ] const characterTools: Tool[] = [ - { href: '/unstuck', icon: 'unstuck-icon', label: t('svcUnstuck'), desc: t('svcUnstuckDesc') }, - { href: '/revive', icon: 'revive-icon', label: t('svcRevive'), desc: t('svcReviveDesc') }, - { href: '/rename', icon: 'rename-icon', label: t('svcRename'), desc: t('svcRenameDesc') }, - { href: '/customize', icon: 'customize-icon', label: t('svcCustomize'), desc: t('svcCustomizeDesc') }, + { href: '/unstuck-character', icon: 'unstuck-icon', label: t('svcUnstuck'), desc: t('svcUnstuckDesc') }, + { href: '/revive-character', icon: 'revive-icon', label: t('svcRevive'), desc: t('svcReviveDesc') }, + { href: '/rename-character', icon: 'rename-icon', label: t('svcRename'), desc: t('svcRenameDesc') }, + { href: '/customize-character', icon: 'customize-icon', label: t('svcCustomize'), desc: t('svcCustomizeDesc') }, { href: '/change-race', icon: 'change-race-icon', label: t('svcChangeRace'), desc: t('svcChangeRaceDesc') }, { href: '/change-faction', icon: 'change-faction-icon', label: t('svcChangeFaction'), desc: t('svcChangeFactionDesc') }, { href: '/level-up', icon: 'level-up-icon', label: t('svcLevelUp'), desc: t('svcLevelUpDesc') }, diff --git a/web-next/components/DPointsTabs.tsx b/web-next/components/DPointsTabs.tsx index 8e25728..c19fecf 100644 --- a/web-next/components/DPointsTabs.tsx +++ b/web-next/components/DPointsTabs.tsx @@ -40,8 +40,8 @@ export function DPointsTabs({ realm, currency }: { realm: string; currency: stri > Información - Regresar a mi cuenta - Regresar + Regresar a mi cuenta + Regresar