Files
NightSpire/web-next/components/BattlepayList.tsx
T
Inna f721aac7e5 Tienda Battlepay: pago de órdenes pendientes vía Stripe
Reutiliza la infraestructura Stripe (sin depender de SumUp/credenciales externas):
- lib/battlepay.ts: getPendingOrders/getPendingOrder/markOrderPaid sobre
  acore_auth.battlepay_orders (PENDING->PAID, el mod-battlepay entrega en juego).
- fulfill.ts: rama battlepay (metadata.battlepay_reference -> markOrderPaid),
  idempotente vía el claim atómico compartido con el webhook.
- API POST /api/battlepay/pay (valida orden PENDING de la cuenta, crea checkout).
- UI: /battlepay (lista + pagar) y /battlepay-success (entrega compartida),
  enlace "Tienda" en la cabecera para usuarios logueados.
- i18n es/en: namespace Battlepay + Nav.store.

Verificado: build OK, /battlepay 307->login, API 401 sin sesión, success 200.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 01:12:12 +00:00

68 lines
2.2 KiB
TypeScript

'use client'
import { useState } from 'react'
import { useTranslations } from 'next-intl'
import type { BattlepayOrder } from '@/lib/battlepay'
export function BattlepayList({ orders }: { orders: BattlepayOrder[] }) {
const t = useTranslations('Battlepay')
const [busy, setBusy] = useState<string | null>(null)
const [error, setError] = useState<string | null>(null)
async function pay(reference: string) {
if (busy) return
setBusy(reference)
setError(null)
try {
const res = await fetch('/api/battlepay/pay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'same-origin',
body: JSON.stringify({ reference }),
})
const d: { success?: boolean; url?: string; error?: string } = await res.json()
if (d.success && d.url) {
window.location.href = d.url
} else {
setError(t(d.error === 'orderNotFound' ? 'orderNotFound' : 'genericError'))
setBusy(null)
}
} catch {
setError(t('genericError'))
setBusy(null)
}
}
if (orders.length === 0) return <p className="text-amber-200/70">{t('noOrders')}</p>
return (
<div>
{error && <p className="mb-4 text-red-400">{error}</p>}
<ul className="space-y-3">
{orders.map((o) => (
<li key={o.id} className="flex flex-wrap items-center justify-between gap-3 nw-card">
<div>
<p className="font-semibold text-amber-300">{o.product_name}</p>
<p className="text-xs text-amber-200/50">
{t('reference')}: {o.reference}
{o.created_at ? ` · ${new Date(o.created_at * 1000).toLocaleDateString()}` : ''}
</p>
</div>
<div className="flex items-center gap-3">
<span className="font-semibold text-nw-gold-light">{o.price_eur.toFixed(2)} </span>
<button
type="button"
onClick={() => pay(o.reference)}
disabled={busy !== null}
className="nw-btn text-sm disabled:opacity-60"
>
{busy === o.reference ? t('redirecting') : t('pay')}
</button>
</div>
</li>
))}
</ul>
</div>
)
}