// screens-credits.jsx — Credits/Packages + Payment flows + Manage subscription const { useState: _cr_useState, useEffect: _cr_useEffect } = React; function CreditsScreen({ ctx }){ const { t: T, balance, premium, navigate } = ctx; const packages = window.RAVANA_DATA.packages; const availableMethods = (ctx.paymentMethods || ['telegram']) .map(m => m === 'telegram' ? 'stars' : m) .filter(m => m === 'stars' || m === 'paypal'); const [method, setMethod] = _cr_useState(availableMethods[0] || 'stars'); const [pickerOpen, setPickerOpen] = _cr_useState(false); const [pkg, setPkg] = _cr_useState(null); _cr_useEffect(() => { if (!availableMethods.includes(method)) { setMethod(availableMethods[0] || 'stars'); } }, [availableMethods.join(','), method]); return (
}/> {/* Current balance summary */}
Current balance
{balance.photo}
photo cr
{balance.video}
video min
{/* Payment method */} setPickerOpen(true)} onPickerSet={(v)=>{setMethod(v); setPickerOpen(false);}} pickerOpen={pickerOpen} ctx={ctx}/> {/* Hot combo */}

Recommended

p.id==='hot_combo')} method={method} onBuy={()=>{setPkg(packages.find(p=>p.id==='hot_combo'));}}/>

Video packages

{packages.filter(p => p.video > 0 && p.id !== 'hot_combo').map(p => setPkg(p)}/> )}

Photo packages

{packages.filter(p => p.video === 0 && p.id !== 'hot_combo').map(p => setPkg(p)}/> )}
{/* Confirmation/Invoice sheet */} setPkg(null)} ctx={ctx}/>
); } function PaymentMethodSelector({ value, onChange, ctx }){ const { t: T } = ctx; const methods = (ctx.paymentMethods || ['telegram']) .map(m => m === 'telegram' ? 'stars' : m) .filter(m => m === 'stars' || m === 'paypal'); return (
{methods.includes('stars') && } {methods.includes('paypal') && }
); } function PackageCard({ pkg, method, onBuy }){ return ( ); } function PackageConfirmSheet({ pkg, method, onClose, ctx }){ const { t: T, setBalance, setFortune, fortune } = ctx; const [state, setState] = _cr_useState('confirm'); // confirm, waiting, paid, failed, expired const [paymentError, setPaymentError] = _cr_useState(null); _cr_useEffect(() => { if (!pkg) { setState('confirm'); setPaymentError(null); } }, [pkg]); if (!pkg) return null; const markPaid = () => { setState('paid'); setBalance(b => ({photo: b.photo + (pkg.photo || 0), video: b.video + (pkg.video || 0)})); setFortune(f => ({...f, spins: f.spins + 3})); }; const startPayment = async () => { setState('waiting'); setPaymentError(null); const mini = window.RavanaMiniApp || {}; const tg = mini.telegram; const apiMethod = method === 'stars' ? 'telegram' : 'paypal'; if (mini.debug || !mini.createInvoice) { setTimeout(markPaid, 650); return; } try { const invoice = await mini.createInvoice(pkg.productId, apiMethod, mini.context); if (apiMethod === 'telegram' && tg && tg.openInvoice) { tg.openInvoice(invoice.invoice_url, (status) => { if (status === 'paid') { markPaid(); } else { setPaymentError(status === 'cancelled' ? 'Payment was cancelled.' : 'Payment was not completed.'); setState('failed'); } }); return; } if (tg && tg.openLink) { tg.openLink(invoice.invoice_url); } else { window.location.href = invoice.invoice_url; } } catch (error) { setPaymentError(error.message || 'Payment failed.'); setState('failed'); } }; return ( {state === 'confirm' && (
{pkg.name}
{pkg.subline}
0 ? [{ k: 'Video credits', v: pkg.video + ' min', tone: 'cyan' }] : []), ...(pkg.photo > 0 ? [{ k: 'Photo credits', v: pkg.photo + ' cr', tone: 'gold' }] : []), { k: 'Method', v: method === 'stars' ? 'Telegram Stars' : 'PayPal' }, { k: 'Total', v: method === 'stars' ? pkg.stars.toLocaleString() + ' ★' : '$' + pkg.usd, tone: 'gold' }, ]}/>
By proceeding you agree to the Terms. Recurring payments off.
)} {state === 'waiting' && (
{T('invoice_waiting')}
Complete the payment in Telegram. Credits will be added automatically.
)} {state === 'paid' && (
{T('invoice_paid')}
Credits added · +3 extra spins as bonus
)} {state === 'failed' && (
Payment was not completed
{paymentError || 'Please try again.'}
)} ); } // Invoice resume screen (from invoice_reminder context) function InvoiceScreen({ ctx }){ const { t: T, navigate } = ctx; const pkg = window.RAVANA_DATA.packages.find(p => p.id === 'hot_combo'); return (
navigate('credits')}/>
{T('campaign_invoice_title')}
{T('campaign_invoice_sub')}
{pkg.name}
{pkg.subline}
{pkg.video} min {pkg.photo}
); } // Manage subscription function ManageSubscriptionScreen({ ctx }){ const { t: T, navigate } = ctx; const [subs, setSubs] = _cr_useState(window.RAVANA_DATA.subscriptions); const [cancelling, setCancelling] = _cr_useState(null); return (
navigate('credits')}/>
{T('stripe_disabled')}. You can still cancel existing auto-renewals.
{subs.length === 0 ? (
{T('no_active_subscriptions')}
) : subs.map(s => (
{s.name}
{s.active ? {T('auto_renew')} : Cancelled}
{s.active && ( )}
))} setCancelling(null)} dialog>
{T('cancel_subscription')}?
You'll keep access until next payment date.
); } window.CreditsScreen = CreditsScreen; window.InvoiceScreen = InvoiceScreen; window.ManageSubscriptionScreen = ManageSubscriptionScreen;