rename-guild: permitir pagar con PD, Stripe o SumUp

Renombrar hermandad ya no es solo PD: ahora ofrece el mismo selector de
forma de pago que el resto de servicios (saldo PD / tarjeta Stripe / tarjeta
SumUp). El coste es equivalente: 1000 PD = 10 €.

- lib/guild: se separa la validación (checkGuildRenameEligibility) del
  renombrado por SOAP (renameGuildBySoap); nuevo GUILD_RENAME_EUR (10 €).
- paid-services: entrada 'rename-guild' para que la reconciliación/webhook
  entregue el servicio tras el pago con tarjeta.
- nueva ruta /api/guild/rename/checkout con las 3 pasarelas (valida
  elegibilidad antes de cobrar); se elimina la antigua /api/guild/rename.
- RenameGuildForm usa PaymentMethodSelect y redirige a service-success.
- i18n: Paid.rename-guild (title/success) para la página de éxito.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-14 21:56:44 +00:00
parent 1b0920d11f
commit 20ee5a83c8
8 changed files with 189 additions and 92 deletions
@@ -0,0 +1,93 @@
import { randomUUID } from 'crypto'
import { getSession } from '@/lib/session'
import { createCheckoutSession } from '@/lib/stripe'
import { createSumUpCheckout, sumupConfigured } from '@/lib/sumup'
import { getPaidService } from '@/lib/paid-services'
import { payServiceWithDPoints } from '@/lib/pay-with-dpoints'
import { checkGuildRenameEligibility, GUILD_RENAME_EUR } from '@/lib/guild'
function safeLocale(v: unknown): string {
return v === 'en' ? 'en' : 'es'
}
/**
* Inicia el renombrado de hermandad con la forma de pago elegida (PD / Stripe /
* SumUp). Valida la elegibilidad (Maestro, token, nombre libre) antes de cobrar.
* Con PD, ejecuta el renombrado al momento; con Stripe/SumUp, lo hace la
* reconciliación/webhook vía PAID_SERVICES['rename-guild'].
*/
export async function POST(request: Request) {
const cfg = getPaidService('rename-guild')!
const session = await getSession()
if (!session.accountId || !session.username) {
return Response.json({ success: false, error: 'notAuthenticated' }, { status: 401 })
}
let body: Record<string, unknown> = {}
try {
body = await request.json()
} catch {
return Response.json({ success: false, error: 'invalidRequest' }, { status: 400 })
}
const guildId = Number(body.guildId)
const newName = String(body.newName ?? '').trim()
const token = String(body.token ?? '').trim()
const elig = await checkGuildRenameEligibility({ accountId: session.accountId, guildId, newName, token })
if (!elig.ok) return Response.json({ success: false, error: elig.error })
const metadata: Record<string, string> = { service: 'rename-guild', guildId: String(guildId), newName }
const price = GUILD_RENAME_EUR
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(newName, metadata)
// Pago con saldo PD: descuenta el saldo y renombra al momento.
if (String(body.provider) === 'pd') {
const r = await payServiceWithDPoints(session.accountId, price, () => cfg.fulfill(newName, metadata))
if (!r.success) return Response.json({ success: false, error: r.error })
const locale = safeLocale(body.locale)
return Response.json({
success: true,
url: `${site}/${locale}/service-success?service=rename-guild&provider=pd&name=${encodeURIComponent(newName)}`,
})
}
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=rename-guild&provider=sumup&ref=${reference}`,
service: 'rename-guild',
characterName: newName,
metadata,
})
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,
username: session.username || '',
email: session.bnetEmail || '',
acoreIp: ip,
amount: price,
characterName: newName,
productName,
successUrl: `${site}/service-success?service=rename-guild`,
cancelUrl: `${site}/rename-guild`,
metadata,
})
if (!r.success || !r.url) return Response.json({ success: false, error: 'stripeError' })
return Response.json({ success: true, url: r.url })
}
-25
View File
@@ -1,25 +0,0 @@
import { getSession } from '@/lib/session'
import { renameGuild } from '@/lib/guild'
/** Renombra una hermandad de la que la cuenta en sesión es Maestro (cuesta 1000 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<string, unknown> = {}
try {
body = await request.json()
} catch {
return Response.json({ success: false, error: 'invalidRequest' }, { status: 400 })
}
const result = await renameGuild({
accountId: session.accountId,
guildId: Number(body.guildId),
newName: String(body.newName ?? ''),
token: String(body.token ?? '').trim(),
})
return Response.json(result)
}