diff --git a/web-next/app/[locale]/d-points-success/page.tsx b/web-next/app/[locale]/d-points-success/page.tsx new file mode 100644 index 0000000..ecddcdd --- /dev/null +++ b/web-next/app/[locale]/d-points-success/page.tsx @@ -0,0 +1,54 @@ +import { setRequestLocale } from 'next-intl/server' +import { Link } from '@/i18n/navigation' +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' + +export default async function DPointsSuccessPage({ + params, + searchParams, +}: { + params: Promise<{ locale: string }> + searchParams: Promise<{ provider?: string; session_id?: string; ref?: string }> +}) { + const { locale } = await params + setRequestLocale(locale) + const { provider, session_id, ref } = await searchParams + + // Entrega compartida con el webhook/return (idempotente vía reclamo atómico). + let status: 'ok' | 'already' | 'error' = 'error' + + if (provider === 'sumup' && ref) { + const r = await fulfillSumUpCheckout(ref) + if (r.ok) status = 'ok' + else if (r.paid) status = 'already' + } else if (session_id) { + const r = await fulfillCheckoutSession(session_id) + if (r.ok) status = 'ok' + else if (await isSessionPaid(session_id)) status = 'already' + } + + return ( + +
+
+ {status === 'ok' ? ( + ¡Pago completado! Tus PD ya fueron acreditados a tu cuenta. + ) : status === 'already' ? ( + Este pago ya había sido procesado. Tus PD están acreditados. + ) : ( + No pudimos confirmar tu pago. Si se realizó el cargo, contacta con soporte. + )} +
+
+

+ ← Regresar a mi cuenta +

+
+
+
+ ) +} diff --git a/web-next/app/[locale]/d-points/page.tsx b/web-next/app/[locale]/d-points/page.tsx index 173dba6..7d5323c 100644 --- a/web-next/app/[locale]/d-points/page.tsx +++ b/web-next/app/[locale]/d-points/page.tsx @@ -1,5 +1,4 @@ import type { Metadata } from 'next' -import { headers } from 'next/headers' import { setRequestLocale } from 'next-intl/server' import { redirect } from '@/i18n/navigation' import { getSession } from '@/lib/session' @@ -20,25 +19,11 @@ export default async function DPointsPage({ params }: { params: Promise<{ locale if (!session.username) redirect({ href: '/select-account', locale }) const realm = await getRealmName() - const h = await headers() - const ip = (h.get('x-forwarded-for') || '').split(',')[0].trim() || '0.0.0.0' - const host = h.get('x-forwarded-host') || h.get('host') || '' - const proto = h.get('x-forwarded-proto') || 'https' - const origin = host ? `${proto}://${host}` : '' - - // Pasarelas automáticas: se habilitan al configurar sus credenciales por entorno. - const paypalBusiness = process.env.PAYPAL_BUSINESS_EMAIL || '' + const currency = (process.env.DP_CURRENCY || 'eur').toLowerCase() return ( - + ) } diff --git a/web-next/app/api/dpoints/checkout/route.ts b/web-next/app/api/dpoints/checkout/route.ts new file mode 100644 index 0000000..408ce2d --- /dev/null +++ b/web-next/app/api/dpoints/checkout/route.ts @@ -0,0 +1,78 @@ +import { randomUUID } from 'crypto' +import { getSession } from '@/lib/session' +import { createCheckoutSession } from '@/lib/stripe' +import { createSumUpCheckout, sumupConfigured } from '@/lib/sumup' +import { PD_PER_UNIT, DP_MIN_AMOUNT, DP_MAX_AMOUNT } from '@/lib/dpoints' + +/** + * Inicia la compra de PD por Stripe o SumUp. El importe lo elige el usuario + * (1 unidad = 100 PD). Devuelve la URL de checkout a la que redirigir. + */ +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 }) + } + + const provider = String(body.provider ?? '') + if (provider !== 'stripe' && provider !== 'sumup') { + return Response.json({ success: false, error: 'invalidProvider' }, { status: 400 }) + } + + const amount = Number(body.amount) + if (!Number.isInteger(amount) || amount < DP_MIN_AMOUNT || amount > DP_MAX_AMOUNT) { + return Response.json({ success: false, error: 'invalidAmount' }) + } + + const points = amount * PD_PER_UNIT + const site = process.env.SITE_URL || '' + const ip = (request.headers.get('x-forwarded-for') || '').split(',')[0].trim() || '0.0.0.0' + const currency = process.env.DP_CURRENCY || 'eur' + + if (provider === 'stripe') { + const r = await createCheckoutSession({ + accountId: session.accountId, + username: session.username, + email: session.bnetEmail || '', + acoreIp: ip, + amount, + currency, + characterName: session.username, + productName: `${points} PD`, + successUrl: `${site}/d-points-success?provider=stripe`, + cancelUrl: `${site}/d-points`, + metadata: { + service: 'dpoints', + accountId: String(session.accountId), + points: String(points), + amount: String(amount), + }, + }) + if (!r.success || !r.url) return Response.json({ success: false, error: 'stripeError' }) + return Response.json({ success: true, url: r.url }) + } + + // 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, + points, + reference, + description: `${points} PD`, + returnUrl: `${site}/d-points-success?provider=sumup&ref=${reference}`, + }) + if (!r.success || !r.url) return Response.json({ success: false, error: r.error || 'sumupError' }) + return Response.json({ success: true, url: r.url }) +} diff --git a/web-next/app/globals.css b/web-next/app/globals.css index 78cc6b3..a20e6e3 100644 --- a/web-next/app/globals.css +++ b/web-next/app/globals.css @@ -419,3 +419,11 @@ textarea:focus { .class-warlock { color: #9482c9; } .class-druid { color: #ff7d0a; } .class-unknown { color: #f0b100; } + +/* Logos en línea de las pasarelas de pago (lista de métodos en /d-points) */ +.pay-inline-logo { + height: 18px; + width: auto; + vertical-align: middle; + margin: 0 2px; +} diff --git a/web-next/components/DPointsTabs.tsx b/web-next/components/DPointsTabs.tsx index 4cdd093..8e25728 100644 --- a/web-next/components/DPointsTabs.tsx +++ b/web-next/components/DPointsTabs.tsx @@ -1,23 +1,12 @@ 'use client' -import { useEffect, useState } from 'react' +import { useState } from 'react' import { Link } from '@/i18n/navigation' -const IMG = '/nw-themes/nw-ryu/nw-images' -const LOGOS = `${IMG}/nw-logos` -const RANKS = `${IMG}/nw-ranks` -const FLAGS = `${IMG}/nw-country-flags` +const LOGOS = '/nw-themes/nw-ryu/nw-images/nw-logos' +const RANKS = '/nw-themes/nw-ryu/nw-images/nw-ranks' -type TabId = 'Info' | 'PayPal' | 'dLocal' | 'Crypto' | 'Skrill' | 'Argentina' | 'Chile' | 'Colombia' | 'Perú' - -// Las banderas son .png sólo para México y Panamá; el resto son .webp. -function flagSrc(code: string) { - const ext = code === 'mx' || code === 'pa' ? 'png' : 'webp' - return `${FLAGS}/${code}.${ext}` -} -function Flag({ code, name }: { code: string; name: string }) { - return {name} -} +type TabId = 'Info' | 'Stripe' | 'SumUp' const RANK_ROWS = [ ['1_Newbie', 'Nivel 1', 'Desde 0 PD'], @@ -36,23 +25,10 @@ const RANK_ROWS = [ ['14_GrandMaster', 'Nivel 14', 'Más de 10000 PD'], ] -export function DPointsTabs({ - realm, - username, - ip, - locale, - origin, - paypalBusiness, -}: { - realm: string - username: string - ip: string - locale: string - origin: string - paypalBusiness: string -}) { +export function DPointsTabs({ realm, currency }: { realm: string; currency: string }) { const [active, setActive] = useState('Info') const [showRanks, setShowRanks] = useState(false) + const sym = currency === 'usd' ? '$' : '€' return ( <> @@ -68,30 +44,11 @@ export function DPointsTabs({ Regresar
- - - - - - - -
@@ -132,682 +89,199 @@ export function DPointsTabs({
¿Cuáles son los métodos de compra de PD?

Los métodos de compra disponibles actualmente son:

-

- PayPal (Para todos los países)

-

- dLocal Go para Argentina , Bolivia , Chile , Colombia , Costa Rica , Ecuador , Guatemala , México , Panamá , Perú , Paraguay , Uruguay .

-

- Criptomonedas (Para todos los países)

-

- Skrill (Para todos los países)

-

- Métodos alternativos sólo para Argentina ( ARG)

-

- Métodos alternativos sólo para Chile ( CHI)

-

- Métodos alternativos sólo para Colombia ( COL)

-

- Métodos alternativos sólo para Perú ( PER)

-
-

Puedes hacer una sugerencia sobre nuevos métodos en nuestro foro de sugerencias aquí.

+

- Stripe Stripe (tarjeta de crédito/débito, para todos los países).

+

- SumUp SumUp (tarjeta de crédito/débito).




¿Cuántos PD se reciben? -

Cada método indicará cuántos PD se reciben.

+

Por cada {sym}1 recibes 100 PD. La cantidad final se indica antes de confirmar el pago.




¿Tienes dudas o problemas con una compra?

No realices una compra hasta estar completamente seguro de que has leído esta información y sabes cómo es el proceso que vayas a escoger.

-

Si a pesar de leer este instructivo o el de alguna de nuestras herramientas, aún tienes dudas, el equipo de {realm} podrá asesorarte en cualquier momento. Puedes colocar un ticket en el servidor, o contactarnos mediante nuestra plataforma de Discord.

+

Si a pesar de leer este instructivo aún tienes dudas, el equipo de {realm} podrá asesorarte en cualquier momento. Puedes colocar un ticket en el servidor, o contactarnos mediante nuestra plataforma de Discord.

)} - {active === 'PayPal' && ( - - )} - {active === 'dLocal' && } - {active === 'Crypto' && } - {active === 'Skrill' && } - {active === 'Argentina' && } - {active === 'Chile' && } - {active === 'Colombia' && } - {active === 'Perú' && } + {active === 'Stripe' && } + {active === 'SumUp' && } ) } -/* ---------- Nota común para pasarelas automáticas sin backend configurado ---------- */ -function gatewayUnavailable(realm: string) { - alert(`Este método de pago aún no está disponible en ${realm}. Por favor, inténtalo de nuevo más tarde o utiliza otro método.`) +/* --------------------------- Lógica de pago común --------------------------- */ +function checkoutError(error?: string): string { + switch (error) { + case 'notConfigured': + return 'Este método de pago aún no está disponible. Por favor, utiliza otro método o inténtalo más tarde.' + case 'invalidAmount': + return 'La cantidad debe ser un número entero entre 1 y 200.' + case 'notAuthenticated': + return 'Tu sesión ha expirado. Inicia sesión de nuevo.' + default: + return 'No se pudo iniciar el pago. Inténtalo de nuevo.' + } } -/* ---------------------------------- PayPal ---------------------------------- */ -function PayPalTab({ - realm, - username, - ip, - origin, - locale, - paypalBusiness, -}: { - realm: string - username: string - ip: string - origin: string - locale: string - paypalBusiness: string -}) { +function usePayment(provider: 'stripe' | 'sumup') { + const [amount, setAmount] = useState('') const [accepted, setAccepted] = useState(false) - const configured = Boolean(paypalBusiness) + const [busy, setBusy] = useState(false) + const [err, setErr] = useState(null) - function onSubmit(e: React.FormEvent) { - if (!configured) { - e.preventDefault() - gatewayUnavailable(realm) + async function pay(e: React.FormEvent, sym: string) { + e.preventDefault() + if (!accepted) { + alert('Debes aceptar los Términos y Condiciones y la Política de Reembolso') return } - const form = e.currentTarget - const amount = (form.elements.namedItem('amount') as HTMLInputElement)?.value - if (!window.confirm(`Por favor revisa que el valor en USD que has ingresado es correcto.\nEl valor ingresado es el costo final en USD que pagarás al ser redirigido.\n\nEl monto a pagar es ${amount} USD.`)) { - e.preventDefault() + const n = Number(amount) + if (!Number.isInteger(n) || n < 1 || n > 200) { + setErr(checkoutError('invalidAmount')) + return + } + if (!window.confirm(`Vas a adquirir ${n * 100} PD por ${sym}${n}.\n¿Deseas continuar al pago?`)) return + setBusy(true) + setErr(null) + try { + const res = await fetch('/api/dpoints/checkout', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + credentials: 'same-origin', + body: JSON.stringify({ provider, amount: n }), + }) + const d: { success?: boolean; url?: string; error?: string } = await res.json() + if (d.success && d.url) { + window.location.href = d.url + return + } + setErr(checkoutError(d.error)) + setBusy(false) + } catch { + setErr(checkoutError()) + setBusy(false) } } - return ( -
-
- ¿Qué es PayPal?PayPal -

Un servicio que te permite pagar, enviar dinero y aceptar pagos sin tener que introducir tus datos financieros continuamente. Paga con PayPal de forma más rápida y segura. 250 millones de personas de más de 200 países y mercados usan PayPal. Y en 25 divisas.
Más información sobre PayPal aquí.

-
-
-
- ¿Cómo adquirir PD a través de PayPal? -

Al ser un sistema auto-gestionado, las adquisiciones mediante este sistema son automáticas y en cuestión de minutos tras realizar tu compra, recibirás los PD en tu cuenta en la página web.

-
-
-
-

- Introduce en el campo de texto que aparece más abajo (el recuadro negro), la cantidad que deseas adquirir en USD.

-

- Al presionar el botón ADQUIRIR PD, serás redirigido a la plataforma de PayPal donde tendrás que ingresar con tu cuenta de PayPal y confirmar la compra en su plataforma.

-

- Una vez que PayPal valide tu pago, tendrás los PD en tu cuenta.

-
-

Por favor ten en cuenta que a la hora de realizar tu compra, la cantidad a adquirir sólo acepta números enteros y el mínimo posible es $1 USD.

-

Ejemplos de valor incorrecto: $5,90 | $22,30 | $53,10

-

Ejemplos de valor correcto: $6 | $22 | $53

-


-

$1 USD = 100 PD

-

Valor máximo por transacción: $200

-
-
-
-

¡Importante!

-

No añadas ceros decimales, sólo valores enteros.

-

Ejemplo: Si deseas 200 PD, escribe sólo el número 2, sin ceros.

-
-
-
-

Cantidad en USD a adquirir

-
- - USD - - - - - - - - - -

- setAccepted(e.target.checked)} required /> - -

- -
-
-
- ) + return { amount, setAmount, accepted, setAccepted, busy, err, pay } } -/* ---------------------------------- dLocal ---------------------------------- */ -function DLocalTab({ username, realm }: { username: string; realm: string }) { - const [accepted, setAccepted] = useState(false) +/* -------------------------------- Formulario -------------------------------- */ +function AmountForm({ + provider, + sym, + buttonLabel, +}: { + provider: 'stripe' | 'sumup' + sym: string + buttonLabel: string +}) { + const st = usePayment(provider) + const points = Number(st.amount) > 0 ? Number(st.amount) * 100 : 0 return ( -
-
- ¿Qué es dLocal Go?dLocal Go -

dLocal Go es una plataforma de pagos que permite una conexión en directo con todas las tarjetas de crédito y débito, pagos en efectivo y transferencias bancarias en diferentes países de América Latina.
Más información sobre dLocal Go aquí.

-
-
-
- ¿Cómo adquirir PD a través de dlocal Go? -

Al ser un sistema auto-gestionado, las adquisiciones mediante este sistema son automáticas y tras realizar tu compra, recibirás los PD en tu cuenta en la página web.

-
-

Tiempo aproximado de procesamiento de pagos según método:

-

- Tarjeta de crédito/debito: De minutos a 1 día hábil

-

- Efectivo: 1 a 2 días hábiles

-

- Transferencia bancaria: 1 día hábil

+ <> +

{sym}1 = 100 PD

+

Valor mínimo: {sym}1 · Valor máximo por transacción: {sym}200

+
+
+
+

¡Importante!

+

Introduce sólo valores enteros (sin decimales).

+

Ejemplo: para 200 PD escribe 2.

+
+
+

Cantidad en {sym === '$' ? 'USD' : 'EUR'} a adquirir

+
st.pay(e, sym)}> + st.setAmount(e.target.value)} + required + />{' '} + {sym === '$' ? 'USD' : 'EUR'} + {points > 0 &&

Recibirás {points} PD

}

-

Importante:

-

Los tiempos indicados en la tabla son orientativos. En algunas ocasiones, la demora puede ser mayor.

-

Ante cualquier duda con la demora, por favor contáctanos en nuestra plataforma de Discord o a través de los medios de contacto disponibles en Ayuda.

+ st.setAccepted(e.target.checked)} + required + /> + +

+ {st.err &&

{st.err}

} + +
+ + ) +} + +/* ---------------------------------- Stripe ---------------------------------- */ +function StripeTab({ realm, sym }: { realm: string; sym: string }) { + return ( +
+
+ ¿Qué es Stripe?Stripe +

Stripe es una plataforma de pagos en línea segura que procesa tarjetas de crédito y débito de todo el mundo. Tus datos de pago se gestionan directamente en Stripe; {realm} nunca los almacena.
Más información sobre Stripe aquí.




-

- Introduce en el campo de texto que aparece más abajo (el recuadro negro), la cantidad que deseas adquirir en USD.

-

- Al presionar el botón ADQUIRIR PD, serás redirigido a la plataforma de dLocal Go donde tendrás que realizar el pago y completar la compra en su plataforma.

-

- Una vez que dLocal Go valide tu pago, tendrás los PD en tu cuenta.

+ ¿Cómo adquirir PD a través de Stripe? +

Al ser un sistema auto-gestionado, las adquisiciones son automáticas y en cuestión de minutos tras completar el pago recibirás los PD en tu cuenta.


-

Por favor ten en cuenta que a la hora de realizar tu compra, la cantidad a adquirir sólo acepta números enteros y el mínimo posible es $2 USD.

-

Ejemplos de valor incorrecto: $5,90 | $22,30 | $53,10

-

Ejemplos de valor correcto: $6 | $22 | $53

+
+
+

- Introduce en el campo de más abajo la cantidad que deseas adquirir.

+

- Al presionar ADQUIRIR PD, serás redirigido a la pasarela segura de Stripe para completar el pago con tu tarjeta.

+

- Una vez que Stripe valide tu pago, tendrás los PD en tu cuenta de forma automática.

+
+

Recuerda que la cantidad sólo acepta números enteros y el mínimo posible es {sym}1.

+

Ejemplos de valor incorrecto: {sym}5,90 | {sym}22,30 | {sym}53,10

+

Ejemplos de valor correcto: {sym}6 | {sym}22 | {sym}53




-

$1 USD = 100 PD

-

Valor mínimo por transacción: $2

-
-

Cantidad en USD a adquirir

-
{ e.preventDefault(); if (!accepted) { alert('Debes aceptar los Términos y Condiciones y la Política de Reembolso'); return } gatewayUnavailable(realm) }}> - USD - -

- setAccepted(e.target.checked)} required /> - -

- -
+
) } -/* ---------------------------------- Crypto ---------------------------------- */ -function CryptoTab({ username, realm }: { username: string; realm: string }) { - const [accepted, setAccepted] = useState(false) +/* ---------------------------------- SumUp ----------------------------------- */ +function SumUpTab({ realm, sym }: { realm: string; sym: string }) { return (
- ¿Qué son las criptomonedas?NOWPayments -

Las criptomonedas son monedas digitales descentralizadas que permiten realizar pagos de forma rápida, segura y sin intermediarios. Aceptamos más de 150 criptomonedas a través de NOWPayments.

-

Más información sobre NOWPayments aquí.

+ ¿Qué es SumUp?SumUp +

SumUp es una plataforma de pagos europea que permite pagar de forma rápida y segura con tarjeta de crédito o débito. Tus datos de pago se gestionan directamente en SumUp; {realm} nunca los almacena.
Más información sobre SumUp aquí.




- ¿Cómo adquirir PD con criptomonedas? -

Al ser un sistema auto-gestionado, las adquisiciones mediante este sistema son automáticas y tras realizar tu compra, recibirás los PD en tu cuenta en la página web.

-
-

Tiempo aproximado de procesamiento según criptomoneda:

-

- BNB, USDT (BSC), USDC (BSC): 1-5 minutos

-

- ETH (Base), USDC (Base): 1-5 minutos

-

- Bitcoin (BTC): 10-60 minutos

-
-

-

Importante:

-

Los tiempos indicados son orientativos y dependen de la congestión de la red blockchain.

-

Ante cualquier duda, por favor contáctanos en nuestra plataforma de Discord o a través de los medios de contacto disponibles en Ayuda.

+ ¿Cómo adquirir PD a través de SumUp? +

Al ser un sistema auto-gestionado, las adquisiciones son automáticas y tras completar el pago recibirás los PD en tu cuenta.




-

- Introduce en el campo de texto la cantidad que deseas adquirir en USD.

-

- Al presionar el botón ADQUIRIR PD, serás redirigido a la plataforma de NOWPayments donde podrás elegir la criptomoneda con la que deseas pagar.

-

- Una vez que NOWPayments confirme tu pago en la blockchain, tendrás los PD en tu cuenta.

+

- Introduce en el campo de más abajo la cantidad que deseas adquirir.

+

- Al presionar ADQUIRIR PD, serás redirigido a la pasarela segura de SumUp para completar el pago con tu tarjeta.

+

- Una vez que SumUp valide tu pago, tendrás los PD en tu cuenta de forma automática.


-

Por favor ten en cuenta que la cantidad a adquirir sólo acepta números enteros y el mínimo posible es $2 USD.

-

Ejemplos de valor incorrecto: $5,90 | $22,30 | $53,10

-

Ejemplos de valor correcto: $6 | $22 | $53

+

Recuerda que la cantidad sólo acepta números enteros y el mínimo posible es {sym}1.

+

Ejemplos de valor incorrecto: {sym}5,90 | {sym}22,30 | {sym}53,10

+

Ejemplos de valor correcto: {sym}6 | {sym}22 | {sym}53




-

$1 USD = 100 PD

-

Valor mínimo por transacción: $2 | Valor máximo: $200

-
-
-
-

¡Importante!

-

Envía el monto EXACTO que indica NOWPayments.

-

Si envías menos, el pago quedará pendiente y deberás contactar a soporte.

-
-
-
-

Cantidad en USD a adquirir

-
{ e.preventDefault(); if (!accepted) { alert('Debes aceptar los Términos y Condiciones y la Política de Reembolso'); return } gatewayUnavailable(realm) }}> - USD - -

- setAccepted(e.target.checked)} required /> - -

- -
-
-
- ) -} - -/* ---------------------------------- Skrill ---------------------------------- */ -function SkrillTab() { - return ( -
-
- ¿Qué es Skrill?Skrill -

Skrill es una plataforma de pagos en línea rápida y segura. Envía dinero, paga y recibe pagos sin ingresar tus datos financieros constantemente. Presente en 200 países y acepta 40 monedas.

-

Más información sobre Skrill aquí.

-
-
-
- ¿Cuál es el valor de los PD? -

El valor será calculado según país en base al cambio indicado en el momento del envío.

-
-

$1 USD = 100 PD

-
-
-
- ¿Cómo adquirir PD por Skrill? -

Paso 1) Inicia sesión en Skrill. Accede a tu cuenta desde la aplicación o la página web oficial de Skrill.

-
-

Paso 2) Envía el dinero

-

1. Selecciona la opción "Enviar" o "Send Money".

-

2. Elige "Skrill a Skrill".

-

3. Introduce el correo del destinatario: skrill@ultimowow.com

-

4. Indica el importe que deseas enviar.

-

5. Revisa los datos y confirma la operación.

-
-

Paso 3) Guarda el comprobante

-

Una vez completado el pago, realiza una captura de pantalla donde se vea claramente:

-

- El correo del destinatario.

-

- El importe enviado.

-

- El estado de la transacción (Completada, Exitosa o similar).

-
-

Paso 4) Envía la información

-

Envía un correo a skrill@ultimowow.com indicando:

-

- El nombre de cualquier personaje de la cuenta donde deseas recibir los PD.

-

- La captura de pantalla de la transacción. No envíes el comprobante en formato PDF.

-
-

Paso 5) Recepción de PD

-

Una vez verificado el pago, los PD serán acreditados en tu cuenta lo antes posible. Cuando la entrega haya sido completada, serás informado.

-
-
-
- Contacto -

Correo: skrill@ultimowow.com

-
-

Este método no es automático sino manual siendo realizado por la persona encargada.

-

Recomendamos no realizar ninguna acción sin comprobar primero si la persona encargada está disponible para entregar los PD.

-
-
- ) -} - -/* -------------------------------- Argentina --------------------------------- */ -function ArgentinaTab() { - const [usd, setUsd] = useState(1) - const [rate, setRate] = useState(null) - - useEffect(() => { - let alive = true - fetch('https://dolarapi.com/v1/dolares/oficial') - .then((r) => (r.ok ? r.json() : Promise.reject(new Error('HTTP ' + r.status)))) - .then((j) => { if (alive) setRate(typeof j?.venta === 'number' ? j.venta : null) }) - .catch(() => {}) - return () => { alive = false } - }, []) - - const fmtARS = (v: number) => v.toLocaleString('es-AR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) - const validUsd = Number.isInteger(usd) && usd >= 1 - const arsText = rate && validUsd ? fmtARS(rate * usd) : '—' - const pdText = validUsd ? String(usd * 100) : '—' - - return ( -
-
- ¿Qué son los métodos alternativos para Argentina? -

Son métodos disponibles para Argentina para aquellos que no tienen acceso a PayPal.

-

Este método trabaja con Pesos Argentinos.

-
-
-
- Métodos disponibles -

- Transferencia bancaria en pesos argentinos (ARS)

-
-
-
- ¿Cuál es el valor de los PD? -

La cantidad de Pesos según país será en base al cambio indicado en el momento de la transferencia.

-

El siguiente recuadro permite hacer una conversión de USD a Pesos argentinos mostrando el valor final y no habrá impuestos ni recargos.

-

Se puede cambiar los valores para ver el precio de la cantidad deseada de PD.

-
-

- Calculadora de conversión -

- setUsd(parseInt(e.target.value, 10))} - style={{ width: '60px', textAlign: 'center' }} - />{' '} - USD ={' '} - {arsText} ARS ={' '} - {pdText} PD -

-
-
-
- ¿Cómo adquirir PD por Transferencia? -

Paso 1) Realiza la transferencia con los siguientes datos:

-

   El valor en ARS puede obtenerse usando la calculadora disponible.

-

   Ejemplo: Si deseas 200 PD, coloca 2 en la casilla USD y te dirá el monto en ARS

-

   Envía el monto exacto que indica la calculadora.

-
-

Paso 2) Envia el correo detallando:

-

 - Nombre de cualquier PERSONAJE de la cuenta donde deseas recibir los PD.

-

 - Cantidad de PD deseada.

-

Paso 3) Adjunta al correo una captura de pantalla (imagen, no pdf) del comprobante.

-

Paso 4) Envia el correo a la dirección indicada en Contacto.

-

Paso 5) Espera la respuesta al correo enviado.

-

Paso 6) Dentro del horario de atención, serás informado que has recibido los PD.

-
-
-
- Contacto -

Correo: arg@ultimowow.com

-

Lunes a Viernes por las tardes (Horario Argentina).

-

-

Este método no es automático sino manual siendo realizado por la persona encargada.

-

Recomendamos no realizar ninguna acción sin comprobar primero si la persona encargada está disponible para entregar los PD.

-
-
- ) -} - -/* ----------------------------------- Chile ---------------------------------- */ -function ChileTab() { - const rows: [string, string][] = [ - ['1.200 Pesos Chilenos', '100 PD'], - ['3.600 Pesos Chilenos', '300 PD'], - ['6.000 Pesos Chilenos', '500 PD'], - ['9.600 Pesos Chilenos', '800 PD'], - ['12.000 Pesos Chilenos', '1000 PD'], - ['30.000 Pesos Chilenos', '2500 PD'], - ['60.000 Pesos Chilenos', '5000 PD'], - ] - return ( -
-
- ¿Qué son los métodos alternativos para Chile? -

Son métodos disponibles para Chile para aquellos que no tienen acceso a PayPal.

-

Este método trabaja con Pesos Chilenos.

-
-
-
- Métodos disponibles -

- Transferencia bancaria en pesos chilenos (CLP)

-

- Mach en pesos chilenos (CLP)

-
-
-
- ¿Cuál es el valor de los PD? - - - - {rows.map(([p, pd]) => ())} - -
PrecioPD
{p}{pd}
-
-
-
- ¿Cómo adquirir PD por Transferencia? -

Paso 1) Contacta con la persona encargada mediante Discord. El medio de contacto aparece al final de esta sección.

-

Paso 2) Debes informarle:

-

- Nombre de cualquier personaje de la cuenta donde deseas recibir los PD.

-

- Cantidad de PD deseada.

-

Paso 3) La persona encargada te dará los datos necesarios para la transferencia.

-

Paso 4) Confirma con la persona encargada el costo final antes de realizar la transferencia.

-

Si usas el método Mach, se te entregará un código QR para realizar la transferencia.

-

Paso 5) Una vez realizada la transferencia, deberás enviarla como imagen (no como archivo pdf) a la persona encargada.

-

Paso 6) Serás informado que has recibido los PD.

-
-
-
- Contacto -

- Discord al usuario Bhaal (ID dakiron)

-
- -

-

* Asegúrate que la persona que contactes tiene rango Maestro de Juego en nuestro discord.

-
-

Este método no es automático sino manual siendo realizado por la persona encargada.

-

Recomendamos no realizar ninguna acción sin comprobar primero si la persona encargada está disponible para entregar los PD.

-
-
- ) -} - -/* --------------------------------- Colombia --------------------------------- */ -function ColombiaTab() { - const rows: [string, string][] = [ - ['17.200 Pesos Colombianos', '300 PD'], - ['23.000 Pesos Colombianos', '400 PD'], - ['28.700 Pesos Colombianos', '500 PD'], - ['34.400 Pesos Colombianos', '600 PD'], - ['57.350 Pesos Colombianos', '1000 PD'], - ['143.350 Pesos Colombianos', '2500 PD'], - ] - return ( -
-
- ¿Qué son los métodos alternativos para Colombia? -

Son métodos disponibles para Colombia para aquellos que no tienen acceso a PayPal que se lleva a cabo a través de un representante oficial, a través de diversos métodos de pago para un país en específico.

-

Este método trabaja con Pesos Colombianos.

-
-
-
- ¿Cuál es el valor de los PD? - - - - {rows.map(([p, pd]) => ())} - -
PrecioPD
{p}{pd}
-
-
-
- ¿Cómo adquiir PD por Representante? -

Para realizar una adquisición y recibir tus PD, tienes varios medios disponibles:

-

- Recarga NEQUI en Efectivo

-

- Recarga NEQUI mediante PSE

-

- Transferencia mediante NEQUI

-

- Consignación o transferencia bancaria

-
-
- Por Recarga NEQUI en EfectivoNequi -
-

1 - Ubica el punto más cercano a donde estés aquí.

-

2 - Al entrar te solicitará conocer tu ubicación y debes hacer clic al botón Permitir.

-

3 - Cuando la web te pregunte ¿Qué quieres hacer? Selecciona la opción Recargar.

-

4 - Mira las ubicaciones que te aparecen señalados en el mapa en color amarillo (Para ver la dirección exacta toca el punto que deseas ver en el mapa).

-

5 - Ve al establecimiento elegido e indica que quieres Recargar a NEQUI X Pesos donde X es la cantidad de pesos correspondiente a los PD deseados (Verifica la tabla y coloca el valor correctamente).

-

6 - Necesitarás dar la siguiente información:

-
-
    -
  • Número de convenio: 63703
  • -
  • Número de celular: 3133639892
  • -
-
-

7 - Solicita recibo de la transacción.

-

8 - Escanea o toma una foto de dicho recibo y envíalo al representante junto con la cantidad de PD y el nombre del personaje donde deseas recibir los PD.

-

9 - Espera a que el encargado te envíe respuesta por el medio que le enviaste el mensaje donde te indicará que ya fueron cargados los PD.

-
-
-
-
- Por Recarga NEQUI mediante PSENequi -
-

El recargar NEQUI por PSE es un método que permite enviar dinero de una cuenta bancaria (de casi cualquier banco) a NEQUI.

-

Nota: Este método puede implicar que tengas activo algunos servicios en el banco donde tienes tu cuenta para poder realizarlo.

-
-

1 - Ingresa a la web de Nequi.

-

2 - Haz clic en la opción Recarga.

-

3 - Aparecerá un formulario que solicitará la siguiente información:

-
-
    -
  • Número de celular: 3133639892
  • -
  • Confirma el número de celular: 3133639892
  • -
  • Tipo de persona: Natural
  • -
  • Cantidad: Cantidad correspondiente a los PD deseados (Verifica la tabla y coloca correctamente el valor)
  • -
-
-

4 - Haz clic en No soy robot y de ahí a Continuar.

-

5 - Al momento de dar a Continuar serás redireccionado a la web oficial de PSE, donde deberás ingresar TU cuenta de PSE (si nunca antes haz hecho pagos por PSE deberás registrarte).

-

6 - Cuando ingreses la cuenta de PSE serás redireccionado a la web de tu banco que accede a este servicio donde deberás completar el proceso que tenga el banco de TU cuenta bancaria (puede que algunos bancos cobren adicional por el acceso o uso de este servicio).

-

7 - Una vez completado y finalizado el pago por PSE, serás re direccionado a NEQUI donde te mostrará un enlace con el mensaje Ver comprobante al cual debes hacerle clic.

-

8 - Descarga el comprobante de pago para generar un archivo en formato pdf y envíalo al representante junto con la cantidad de PD y el nombre del personaje donde deseas recibir los PD.

-

9 - Espera a que el encargado te envíe respuesta por el medio que le enviaste el mensaje donde te indicará que ya fueron cargados los PD.

-
-
-
-
- Transferencia mediante NEQUINequi -
-

1 - Ingresa a tu cuenta NEQUI Desde la App móvil de NEQUI Colombia.

-

2 - Al ingresar, selecciona el botón con el símbolo $ de color rosa.

-

3 - Selecciona la opción Envía plata.

-

4 - Indica desde dónde enviarás el dinero.

-

5 - En los campos que están en Envía a NEQUI coloca la siguiente información:

-
-
    -
  • Número de celular: 3133639892
  • -
  • Cantidad: Cantidad correspondiente a los PD deseados (Verifica la tabla y coloca correctamente el valor)
  • -
  • Mensaje: PD UltimoWoW
  • -
-
-

6 - Presiona Continuar.

-

7 - Una vez que se termine de procesar el pago te mostrará la lista de tus movimientos, en el último sale el nombre de la persona relacionada a la cuenta transferida que es ROBERT PENA.

-

8 - Selecciona dicho movimiento para que te muestre los detalles del mismo.

-

9 - Toma una captura de pantalla de los detalles y envíalo al representante junto con la cantidad de PD y el nombre del personaje donde deseas recibir los PD.

-

10 - Espera a que el encargado te envíe respuesta por el medio que le enviaste el mensaje donde te indicará que ya fueron cargados los PD.

-
-
-
-
- Consignación o Transferencia a cuenta bancariaBancolombia -
-

Mediante este método deberas realizar una consignación en un banco o corresponsal bancario por el valor de los PD que quieres adquirir (Verifica la tabla y coloca correctamente el valor).

-
-

1 - Cuando realices la consignación te solicitarán los siguientes datos:

-
-
    -
  • Número de cuenta: 203-133849-78
  • -
  • A nombre de: Robert Camilo Peña
  • -
  • Tipo de cuenta: De ahorros
  • -
  • Banco: Bancolombia
  • -
-
-

2 - Solicita copia del recibo de transacción.

-

3 - Escanea o toma una foto del recibo y envíalo al representante junto con la cantidad de PD y el nombre del personaje donde deseas recibir los PD.

-

4 - Espera a que el encargado te envíe respuesta por el medio que le enviaste el mensaje donde te indicará que ya fueron cargados los PD.

-
-
-
-
-
- ¿Cómo contactar con el Representante? -

Nombre: Robert Peña

-

Usuario de Discord: Raziel#1403

-

Lunes a Jueves: 15:00 a 22:00 (Horario Colombia)

-

Sábados y Domingos: 18:00 a 23:00 (Horario Colombia)

-

Importante: Los días Viernes no hay atención

-
- - -
-
-
- ) -} - -/* ----------------------------------- Perú ----------------------------------- */ -function PeruTab() { - const rows: [string, string][] = [ - ['5.5 Soles Peruanos', '100 PD'], - ['25,5 Soles Peruanos', '500 PD'], - ['40 Soles Peruanos', '800 PD'], - ['48 Soles Peruanos', '1000 PD'], - ['118 Soles Peruanos', '2500 PD'], - ] - return ( -
-
- ¿Qué son los métodos alternativos para Perú? -

Son métodos disponibles para aquellos que no tienen acceso a PayPal que se lleva a cabo a través de un representante oficial, a través de diversos métodos de pago para un país en específico.

-

Este método trabaja con Soles Peruanos.

-
-
-
- ¿Cuál es el valor de los PD? - - - - {rows.map(([p, pd]) => ())} - -
PrecioPD
{p}{pd}
-
-
-
- ¿Cómo adquirir PD por Representante? -

Para realizar una adquisición y recibir tus PD, tienes varios medios disponibles:

-

- Recarga por medio de YAPE

-

- Recarga por medio de PLIN

-

- Pago presencial mediante OFICINA (LIMA)

-

- Transferencia bancaria y interbancaria

-
-
- Recarga por medio de YAPE -
- Yape -
-
-
-
- Pago por medio de PLIN -
-

Pago mediante número.

-
- Plin -
-
-
-
- Pago presencial mediante OFICINA -
-

Contactarse con el número mediante 941841649 previamente cita.

-
- Oficina -
-
-
-
- Transferencia bancaria y interbancaria -
-

Mediante este método deberas realizar una consignación en un banco o corresponsal bancario por el valor de los PD que quieres adquirir (Verifica la tabla y coloca correctamente el valor).

-
-

- BANCO BCP

-

- BANCO BBVA

-

- BANCO SCOTIABANK

-

- BANCO DE LA NACIÓN

-
-

Comunicarse previamente con el representante.

-
-
-
-

Una vez realizado el proceso por cualquiera de los medios indicados, contactar con el Representante.

-

Si es en los horarios de atención, los PD serán cargados en minutos en la cuenta.

-
-
-
- ¿Cómo contactar con el Representante? -

Nombre: Anibal Gavidia Correa

-

Lunes a Jueves: 9:00 a 22:00 (Horario Perú)

-

Sábados y Domingos: 9:00 a 23:00 (Horario Perú)

-
- - -
+
) diff --git a/web-next/lib/dpoints.ts b/web-next/lib/dpoints.ts new file mode 100644 index 0000000..88e6bea --- /dev/null +++ b/web-next/lib/dpoints.ts @@ -0,0 +1,27 @@ +import type { RowDataPacket } from 'mysql2' +import { db, DB } from './db' + +/** Ratio de conversión: cada 1 unidad de moneda = 100 PD. */ +export const PD_PER_UNIT = 100 +/** Límites de compra por transacción (en unidades de moneda). */ +export const DP_MIN_AMOUNT = 1 +export const DP_MAX_AMOUNT = 200 + +/** Acredita PD (donate points) a una cuenta de juego en `home_api_points` (upsert). */ +export async function creditDPoints(accountId: number, points: number): Promise { + if (!accountId || !(points > 0)) return false + try { + const [rows] = await db(DB.default).query( + 'SELECT id FROM home_api_points WHERE accountID = ?', + [accountId], + ) + if (rows[0]) { + await db(DB.default).query('UPDATE home_api_points SET dp = dp + ? WHERE accountID = ?', [points, accountId]) + } else { + await db(DB.default).query('INSERT INTO home_api_points (accountID, vp, dp) VALUES (?, 0, ?)', [accountId, points]) + } + return true + } catch { + return false + } +} diff --git a/web-next/lib/paid-services.ts b/web-next/lib/paid-services.ts index 3d5fc00..e86e7cd 100644 --- a/web-next/lib/paid-services.ts +++ b/web-next/lib/paid-services.ts @@ -1,5 +1,6 @@ import { executeSoapCommand } from './soap' import { db, DB } from './db' +import { creditDPoints } from './dpoints' import { getRenamePrice, getCustomizePrice, @@ -78,6 +79,14 @@ export const PAID_SERVICES: Record = { fulfill: (c, m) => soapFulfill(`.char changeaccount ${m.destination_account} ${c}`), extraFields: ['destination_account'], }, + // Compra de PD (Adquirir PD): acredita puntos a la cuenta que hizo el pago. + // El importe lo elige el usuario, por eso `price` lee metadata.amount. + dpoints: { + price: (m) => Promise.resolve(Number(m.amount)), + productName: (_c, m) => `${Number(m.points)} PD`, + fulfill: (_c, m) => creditDPoints(Number(m.accountId), Number(m.points)), + extraFields: [], + }, } export function getPaidService(slug: string): PaidServiceConfig | null { diff --git a/web-next/lib/sumup.ts b/web-next/lib/sumup.ts new file mode 100644 index 0000000..e3843ab --- /dev/null +++ b/web-next/lib/sumup.ts @@ -0,0 +1,108 @@ +import type { RowDataPacket, ResultSetHeader } from 'mysql2' +import { db, DB } from './db' +import { creditDPoints, PD_PER_UNIT } from './dpoints' + +const API = 'https://api.sumup.com/v0.1' + +/** SumUp está disponible sólo si hay API key y merchant code configurados. */ +export function sumupConfigured(): boolean { + return Boolean(process.env.SUMUP_API_KEY && process.env.SUMUP_MERCHANT_CODE) +} + +function authHeaders(): Record { + return { + Authorization: `Bearer ${process.env.SUMUP_API_KEY}`, + 'Content-Type': 'application/json', + } +} + +interface CreateParams { + accountId: number + username: string + email: string + acoreIp: string + amount: number // importe en la moneda del comercio + points: number + reference: string + description: string + returnUrl: string +} + +/** + * Crea un checkout hospedado de SumUp y registra el pago en `home_stripelog` + * (reutilizamos la tabla de logs; `mode = 'sumup'`, `session_id = reference`). + * Devuelve la URL del checkout hospedado para redirigir al usuario. + */ +export async function createSumUpCheckout(p: CreateParams): Promise<{ success: boolean; url?: string; error?: string }> { + if (!sumupConfigured()) return { success: false, error: 'notConfigured' } + try { + const res = await fetch(`${API}/checkouts`, { + method: 'POST', + headers: authHeaders(), + body: JSON.stringify({ + checkout_reference: p.reference, + amount: p.amount, + currency: process.env.SUMUP_CURRENCY || 'EUR', + merchant_code: process.env.SUMUP_MERCHANT_CODE, + description: p.description, + return_url: p.returnUrl, + hosted_checkout: { enabled: true }, + }), + }) + if (!res.ok) return { success: false, error: `sumup_${res.status}` } + const data: { id?: string; hosted_checkout_url?: string } = await res.json() + const url = data.hosted_checkout_url + 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, timestamp, fulfilled) ' + + 'VALUES (?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, NOW(), 0)', + [p.accountId, p.username, p.email, p.acoreIp, p.description, p.amount, p.reference, 'sumup', p.username], + ) + return { success: true, url } + } catch (e) { + return { success: false, error: String(e) } + } +} + +/** ¿El checkout con esta referencia está PAGADO en SumUp? */ +async function isReferencePaid(reference: string): Promise { + try { + const res = await fetch(`${API}/checkouts?checkout_reference=${encodeURIComponent(reference)}`, { + headers: authHeaders(), + }) + if (!res.ok) return false + const list: { status?: string }[] = await res.json() + return Array.isArray(list) && list.some((c) => c.status === 'PAID') + } catch { + return false + } +} + +/** + * Entrega un checkout SumUp pagado: verifica el estado, reclama de forma atómica + * (fulfilled 0->1) y acredita los PD. Idempotente. Devuelve true si acreditó ahora. + */ +export async function fulfillSumUpCheckout(reference: string): Promise<{ ok: boolean; paid: boolean }> { + if (!reference) return { ok: false, paid: false } + const paid = await isReferencePaid(reference) + if (!paid) return { ok: false, paid: false } + + const [rows] = await db(DB.default).query( + 'SELECT id, account_id, amount FROM home_stripelog WHERE session_id = ? AND mode = ?', + [reference, 'sumup'], + ) + const log = rows[0] + if (!log) return { ok: false, paid: true } + + // Reclamo atómico: sólo gana quien pasa fulfilled 0->1. + const [res] = await db(DB.default).query( + 'UPDATE home_stripelog SET fulfilled = 1 WHERE id = ? AND fulfilled = 0', + [log.id], + ) + if (res.affectedRows === 0) return { ok: false, paid: true } + + const points = Math.round(Number(log.amount) * PD_PER_UNIT) + const ok = await creditDPoints(Number(log.account_id), points) + return { ok, paid: true } +} diff --git a/web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/nowpayments-logo.png b/web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/nowpayments-logo.png deleted file mode 100644 index 23cf2ca..0000000 Binary files a/web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/nowpayments-logo.png and /dev/null differ diff --git a/web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/stripe-logo.svg b/web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/stripe-logo.svg new file mode 100644 index 0000000..0907489 --- /dev/null +++ b/web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/stripe-logo.svg @@ -0,0 +1,4 @@ + + + stripe + diff --git a/web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/stripe-p-logo.svg b/web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/stripe-p-logo.svg new file mode 100644 index 0000000..f005edd --- /dev/null +++ b/web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/stripe-p-logo.svg @@ -0,0 +1,4 @@ + + + S + diff --git a/web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/sumup-logo.svg b/web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/sumup-logo.svg new file mode 100644 index 0000000..7a280ea --- /dev/null +++ b/web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/sumup-logo.svg @@ -0,0 +1,5 @@ + + + + SumUp + diff --git a/web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/sumup-p-logo.svg b/web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/sumup-p-logo.svg new file mode 100644 index 0000000..5cbb775 --- /dev/null +++ b/web-next/public/nw-themes/nw-ryu/nw-images/nw-logos/sumup-p-logo.svg @@ -0,0 +1,4 @@ + + + +