// 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 */}
{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;