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:
@@ -1,9 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { useRouter } from '@/i18n/navigation'
|
||||
import { useTranslations, useLocale } from 'next-intl'
|
||||
import type { GmGuild } from '@/lib/guild'
|
||||
import { PaymentMethodSelect, pdCostOf, type PayMethod } from '@/components/PaymentMethodSelect'
|
||||
|
||||
function renameErrorKey(error?: string): string {
|
||||
switch (error) {
|
||||
@@ -12,25 +12,29 @@ function renameErrorKey(error?: string): string {
|
||||
case 'invalidToken':
|
||||
case 'notGuildMaster':
|
||||
case 'nameTaken':
|
||||
case 'insufficientPoints':
|
||||
case 'soapError':
|
||||
case 'notAuthenticated':
|
||||
return error
|
||||
case 'insufficientPd': // pago con PD sin saldo -> mensaje específico de hermandad
|
||||
return 'insufficientPoints'
|
||||
case 'fulfillFailed': // el renombrado por SOAP no se aplicó (se reembolsó)
|
||||
return 'soapError'
|
||||
default:
|
||||
return 'generic'
|
||||
}
|
||||
}
|
||||
|
||||
export function RenameGuildForm({ guilds }: { guilds: GmGuild[] }) {
|
||||
export function RenameGuildForm({ guilds, price, pdBalance }: { guilds: GmGuild[]; price: number; pdBalance: number }) {
|
||||
const t = useTranslations('Points')
|
||||
const tpay = useTranslations('Pay')
|
||||
const locale = useLocale()
|
||||
const renameError = (error?: string) => t(`renameGuild.errors.${renameErrorKey(error)}`)
|
||||
const router = useRouter()
|
||||
const [guildId, setGuildId] = useState('')
|
||||
const [newName, setNewName] = useState('')
|
||||
const [token, setToken] = useState('')
|
||||
const [showToken, setShowToken] = useState(false)
|
||||
const [method, setMethod] = useState<PayMethod>(pdBalance >= pdCostOf(price) ? 'pd' : 'sumup')
|
||||
const [busy, setBusy] = useState(false)
|
||||
const [done, setDone] = useState(false)
|
||||
const [message, setMessage] = useState<{ ok: boolean; text: string } | null>(null)
|
||||
|
||||
if (guilds.length === 0) {
|
||||
@@ -45,35 +49,32 @@ export function RenameGuildForm({ guilds }: { guilds: GmGuild[] }) {
|
||||
|
||||
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||
e.preventDefault()
|
||||
if (busy || done) return
|
||||
if (busy) return
|
||||
if (!guildId || !newName.trim() || !token.trim()) {
|
||||
setMessage({ ok: false, text: renameError('missingFields') })
|
||||
return
|
||||
}
|
||||
if (!window.confirm(t('renameGuild.confirm'))) return
|
||||
const amount = method === 'pd' ? tpay('pdCost', { cost: pdCostOf(price) }) : tpay('eur', { price })
|
||||
if (!window.confirm(tpay('confirm', { amount }))) return
|
||||
|
||||
setBusy(true)
|
||||
setMessage(null)
|
||||
try {
|
||||
const res = await fetch('/api/guild/rename', {
|
||||
const res = await fetch('/api/guild/rename/checkout', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify({ guildId: Number(guildId), newName: newName.trim(), token: token.trim() }),
|
||||
body: JSON.stringify({ guildId: Number(guildId), newName: newName.trim(), token: token.trim(), provider: method, locale }),
|
||||
})
|
||||
const data: { success?: boolean; error?: string; newName?: string } = await res.json()
|
||||
if (data.success) {
|
||||
setDone(true)
|
||||
setMessage({ ok: true, text: t('renameGuild.success', { newName: data.newName ?? '' }) })
|
||||
setNewName('')
|
||||
setToken('')
|
||||
setTimeout(() => router.refresh(), 3000)
|
||||
} else {
|
||||
setMessage({ ok: false, text: renameError(data.error) })
|
||||
const data: { success?: boolean; url?: string; error?: string } = await res.json()
|
||||
if (data.success && data.url) {
|
||||
window.location.href = data.url // éxito PD, o Checkout (Stripe/SumUp)
|
||||
return
|
||||
}
|
||||
setMessage({ ok: false, text: renameError(data.error) })
|
||||
setBusy(false)
|
||||
} catch {
|
||||
setMessage({ ok: false, text: renameError() })
|
||||
} finally {
|
||||
setBusy(false)
|
||||
}
|
||||
}
|
||||
@@ -127,8 +128,13 @@ export function RenameGuildForm({ guilds }: { guilds: GmGuild[] }) {
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<button type="submit" className="rename-guild-button" disabled={busy || done} style={done ? { color: '#d79602' } : undefined}>
|
||||
{done ? t('renameGuild.renamed') : busy ? t('renameGuild.renaming') : t('renameGuild.renameButton')}
|
||||
<PaymentMethodSelect value={method} onChange={setMethod} priceEur={price} pdBalance={pdBalance} />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<button type="submit" className="rename-guild-button" disabled={busy}>
|
||||
{busy ? t('renameGuild.renaming') : t('renameGuild.renameButton')}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user