Añade gold y transfer: patrón de pago unificado con fulfill + metadata
- lib/stripe.ts: createCheckoutSession acepta metadata; claimPaidCheckout la recupera de la sesión Stripe y la devuelve. - lib/paid-services.ts: config unificado con fulfill(character, meta) + extraFields. gold -> UPDATE characters money (BD directa, no SOAP), transfer -> SOAP .char changeaccount. Los 5 simples usan soapFulfill. - checkout [service]: lee extraFields -> metadata, precio depende de metadata (gold), valida precio>0. service-success llama cfg.fulfill(name, metadata). - prices.ts: getTransferPrice, getGoldOptions/goldPriceFor (home_goldprice). - GoldForm (personaje + cantidad) y TransferForm (personaje + destino); páginas /gold y /transfer. Catálogos Paid.gold/transfer. Verificado: /gold /transfer redirigen a login, checkout 401 sin sesión, home OK. NOTA: transfer no valida aún el token de seguridad ni reglas AC (nivel>=55, DK). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import { getTranslations, setRequestLocale } from 'next-intl/server'
|
||||
import { redirect } from '@/i18n/navigation'
|
||||
import { getSession } from '@/lib/session'
|
||||
import { getGameCharacters } from '@/lib/characters'
|
||||
import { getGoldOptions } from '@/lib/prices'
|
||||
import { GoldForm } from '@/components/GoldForm'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
export default async function GoldPage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params
|
||||
setRequestLocale(locale)
|
||||
const t = await getTranslations('Paid')
|
||||
const session = await getSession()
|
||||
if (!session.bnetId) redirect({ href: '/login', locale })
|
||||
if (!session.username) redirect({ href: '/select-account', locale })
|
||||
|
||||
const [chars, options] = await Promise.all([getGameCharacters(session.accountId!), getGoldOptions()])
|
||||
|
||||
return (
|
||||
<main className="mx-auto max-w-3xl px-4 py-8">
|
||||
<h1 className="mb-6 text-center text-2xl font-bold text-amber-500">{t('gold.title')}</h1>
|
||||
<GoldForm characters={chars.map((c) => c.name)} options={options} />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { getTranslations, setRequestLocale } from 'next-intl/server'
|
||||
import { claimPaidCheckout } from '@/lib/stripe'
|
||||
import { executeSoapCommand } from '@/lib/soap'
|
||||
import { getPaidService } from '@/lib/paid-services'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
@@ -23,8 +22,8 @@ export default async function ServiceSuccessPage({
|
||||
if (cfg && session_id) {
|
||||
const claim = await claimPaidCheckout(session_id)
|
||||
if (claim) {
|
||||
const resp = await executeSoapCommand(cfg.command(claim.characterName))
|
||||
if (resp !== null) okName = claim.characterName
|
||||
const ok = await cfg.fulfill(claim.characterName, claim.metadata)
|
||||
if (ok) okName = claim.characterName
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { getTranslations, setRequestLocale } from 'next-intl/server'
|
||||
import { redirect } from '@/i18n/navigation'
|
||||
import { getSession } from '@/lib/session'
|
||||
import { getGameCharacters } from '@/lib/characters'
|
||||
import { getTransferPrice } from '@/lib/prices'
|
||||
import { TransferForm } from '@/components/TransferForm'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
export default async function TransferPage({ params }: { params: Promise<{ locale: string }> }) {
|
||||
const { locale } = await params
|
||||
setRequestLocale(locale)
|
||||
const t = await getTranslations('Paid')
|
||||
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!), getTransferPrice()])
|
||||
|
||||
return (
|
||||
<main className="mx-auto max-w-3xl px-4 py-8">
|
||||
<h1 className="mb-6 text-center text-2xl font-bold text-amber-500">{t('transfer.title')}</h1>
|
||||
<TransferForm characters={chars.map((c) => c.name)} price={price} />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -11,13 +11,13 @@ export async function POST(request: Request, { params }: { params: Promise<{ ser
|
||||
const session = await getSession()
|
||||
if (!session.accountId) return Response.json({ success: false, error: 'notAuthenticated' }, { status: 401 })
|
||||
|
||||
let character = ''
|
||||
let body: Record<string, string> = {}
|
||||
try {
|
||||
const body = await request.json()
|
||||
character = String(body.character ?? '')
|
||||
body = await request.json()
|
||||
} catch {
|
||||
return Response.json({ success: false, error: 'invalidRequest' }, { status: 400 })
|
||||
}
|
||||
const character = String(body.character ?? '')
|
||||
if (!character) return Response.json({ success: false, error: 'invalidCharacter' })
|
||||
|
||||
const chars = await getGameCharacters(session.accountId)
|
||||
@@ -25,7 +25,17 @@ export async function POST(request: Request, { params }: { params: Promise<{ ser
|
||||
return Response.json({ success: false, error: 'invalidCharacter' })
|
||||
}
|
||||
|
||||
const price = await cfg.getPrice()
|
||||
// Campos extra del servicio (gold_amount, destination_account...) -> metadata
|
||||
const metadata: Record<string, string> = {}
|
||||
for (const f of cfg.extraFields) {
|
||||
const v = String(body[f] ?? '').trim()
|
||||
if (!v) return Response.json({ success: false, error: 'missingFields' })
|
||||
metadata[f] = v
|
||||
}
|
||||
|
||||
const price = await cfg.price(metadata)
|
||||
if (!price || price <= 0) return Response.json({ success: false, error: 'invalidRequest' })
|
||||
|
||||
const site = process.env.SITE_URL || ''
|
||||
const ip = (request.headers.get('x-forwarded-for') || '').split(',')[0].trim() || '0.0.0.0'
|
||||
|
||||
@@ -36,9 +46,10 @@ export async function POST(request: Request, { params }: { params: Promise<{ ser
|
||||
acoreIp: ip,
|
||||
amount: price,
|
||||
characterName: character,
|
||||
productName: cfg.productName(character),
|
||||
productName: cfg.productName(character, metadata),
|
||||
successUrl: `${site}/service-success?service=${service}`,
|
||||
cancelUrl: `${site}/${service}`,
|
||||
metadata,
|
||||
})
|
||||
if (!r.success || !r.url) return Response.json({ success: false, error: 'stripeError' })
|
||||
return Response.json({ success: true, url: r.url })
|
||||
|
||||
Reference in New Issue
Block a user