Setting up payments
Integrating Funtico payments into your game or application involves creating transactions, handling user authentication, and managing payment flows. This guide covers the essential patterns and best practices for a successful integration using a secure backend approach.
The Funtico payment integration consists of three main components:
- Transaction Creation - Your backend creates transactions via API
- User Authentication - Users authenticate with Funtico to access their TICO balance
- Payment Processing - Users complete payments through Funtico’s secure checkout
Prerequisites
Section titled “Prerequisites”Before integrating payments, ensure you have:
- Funtico Account - Register as a developer at Funtico Developer Portal
- API Credentials - PSP Client ID and Secret for transaction creation
- Auth Credentials - Auth Client ID and Secret for user authentication
- SDK Installation - Install the appropriate SDK for your platform
npm install @pillarex/funtico-sdkTransaction Creation
Section titled “Transaction Creation”Basic Transaction Flow
Section titled “Basic Transaction Flow”Transactions are created on your backend server to ensure security and prevent client-side manipulation:
import { FunticoSDK } from '@pillarex/funtico-sdk';
const sdk = new FunticoSDK({ pspClientId: process.env.FUNTICO_PSP_CLIENT_ID, pspClientSecret: process.env.FUNTICO_PSP_CLIENT_SECRET, env: process.env.NODE_ENV === 'production' ? 'production' : 'staging'});
// Create a transactionconst transaction = await sdk.createTransaction({ items: [ { name: "Magic Sword", description: "A powerful weapon for your character", image_url: "https://your-game.com/images/magic-sword.png", quantity: 1, unit_price: 10.0, // 10.0 TICO currency: "tico", metadata: { item_id: "sword_001", rarity: "epic", category: "weapon" } } ], success_url: "https://your-game.com/success", cancel_url: "https://your-game.com/cancel", ui_mode: "redirect", expiration_sec: 3600, // 1 hour metadata: { user_id: "user_123", session_id: "session_456" }});Transaction Configuration
Section titled “Transaction Configuration”- Required fields:
name,description,image_url,metadata,quantity,unit_price,currency - Currency: Use
"tico"for TICO cryptocurrency payments or"usd"for USD - Pricing: Specify prices as numbers (e.g., 10.0 for 10.0 TICO)
- Description/Image: Can be
nullbut must be provided in the request
UI Modes
Section titled “UI Modes”redirect- User is redirected to checkout and back to your gamenew_tab- Checkout opens in new tab/window and closes when complete
Expiration
Section titled “Expiration”Set appropriate expiration times based on your use case:
- Short sessions (5-15 minutes): 900-1800 seconds
- Standard purchases (30-60 minutes): 1800-3600 seconds
- Complex flows (1-2 hours): 3600-7200 seconds
User Authentication
Section titled “User Authentication”Authentication Flow (Backend)
Section titled “Authentication Flow (Backend)”Users must authenticate with Funtico to access their TICO balance. Handle authentication on your backend for security:
// Backend: Initialize auth SDKconst authSDK = new FunticoSDK({ authClientId: process.env.FUNTICO_AUTH_CLIENT_ID, authClientSecret: process.env.FUNTICO_AUTH_CLIENT_SECRET, env: process.env.NODE_ENV === 'production' ? 'production' : 'staging'});
// Backend: POST /auth/loginapp.post('/auth/login', async (req, res) => { try { const { codeVerifier, redirectUrl, state } = await authSDK.signInWithFuntico({ callbackUrl: "https://your-game.com/auth/callback", scopes: ['openid', 'profile', 'email', 'offline_access', 'balance:read', 'transactions:read'] });
// Store codeVerifier in secure cookie res.cookie(`state_${state}`, codeVerifier, { httpOnly: true, secure: true, sameSite: 'lax', maxAge: 10 * 60 * 1000 // 10 minutes });
res.json({ redirectUrl }); } catch (error) { res.status(500).json({ error: 'Failed to initiate login' }); }});Token Exchange (Backend)
Section titled “Token Exchange (Backend)”After user authentication, exchange the authorization code for tokens on your backend:
// Backend: Handle OAuth callbackapp.get('/auth/callback', async (req, res) => { try { const state = req.query.state as string; const code = req.query.code as string;
if (!state || !code) { return res.status(400).json({ error: 'Missing state or code parameter' }); }
const codeVerifier = req.cookies[`state_${state}`]; if (!codeVerifier) { return res.status(400).json({ error: 'Invalid or expired state' }); }
res.clearCookie(`state_${state}`);
// Exchange code for tokens const { accessToken, refreshToken } = await authSDK.getTokens({ codeVerifier, url: req.url });
// Store tokens in secure cookies res.cookie('access_token', accessToken, { httpOnly: true, secure: true, sameSite: 'lax', maxAge: 60 * 60 * 1000 // 1 hour });
res.cookie('refresh_token', refreshToken, { httpOnly: true, secure: true, sameSite: 'lax', maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days });
res.redirect('https://your-game.com/dashboard?login=success'); } catch (error) { res.status(500).json({ error: 'Failed to handle callback' }); }});Payment Flow Integration
Section titled “Payment Flow Integration”Frontend Integration
Section titled “Frontend Integration”Your frontend handles the user journey from purchase intent to payment completion:
// 1. User clicks purchase buttonasync function handlePurchase(itemId: string) { try { // 2. Create transaction on backend with authentication cookies const response = await fetch('/api/transactions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', // Include authentication cookies body: JSON.stringify({ itemId }) });
if (!response.ok) { if (response.status === 401) { // User not authenticated, redirect to login window.location.href = '/login'; return; } throw new Error('Failed to create transaction'); }
const { transaction } = await response.json();
// 3. Redirect to Funtico checkout window.location.href = transaction.checkout_url; } catch (error) { console.error('Failed to create transaction:', error); // Show user-friendly error message showError('Unable to start payment. Please try again.'); }}
// Helper function for loginasync function initiateLogin() { try { const response = await fetch('/auth/login', { method: 'POST' }); const { redirectUrl } = await response.json(); window.location.href = redirectUrl; } catch (error) { console.error('Failed to start login:', error); }}Backend Transaction Creation
Section titled “Backend Transaction Creation”Your backend API creates transactions securely:
// POST /api/transactionsapp.post('/api/transactions', async (req, res) => { try { const { itemId } = req.body;
// Validate user authentication via cookies const accessToken = req.cookies.access_token; if (!accessToken) { return res.status(401).json({ error: 'Authentication required' }); }
// Get user ID from token or session const userId = req.user?.id; // Assuming middleware sets req.user
// Get item details from your database const item = await getItemById(itemId);
// Create transaction const transaction = await sdk.createTransaction({ items: [{ name: item.name, description: item.description, image_url: item.imageUrl, quantity: 1, unit_price: item.price, // Price as decimal number currency: "tico", metadata: { item_id: item.id, category: item.category } }], success_url: `${process.env.GAME_URL}/purchase/success`, cancel_url: `${process.env.GAME_URL}/purchase/cancel`, ui_mode: "redirect", expiration_sec: 1800, // 30 minutes metadata: { user_id: userId, item_id: itemId } });
res.json({ transaction }); } catch (error) { console.error('Transaction creation failed:', error); res.status(500).json({ error: 'Failed to create transaction' }); }});Status Tracking
Section titled “Status Tracking”Polling Pattern
Section titled “Polling Pattern”For simple integrations, poll the transaction status:
// Check transaction status periodicallyasync function pollTransactionStatus(transactionId: string) { const maxAttempts = 60; // 5 minutes with 5-second intervals let attempts = 0;
const poll = async () => { try { const transaction = await sdk.getTransactionById(transactionId);
if (transaction) { switch (transaction.current_status) { case 'completed': handlePaymentSuccess(transaction); return; case 'failed': case 'cancelled': case 'expired': handlePaymentFailure(transaction); return; case 'created': case 'confirmed': // Continue polling break; } }
attempts++; if (attempts < maxAttempts) { setTimeout(poll, 5000); // Poll every 5 seconds } else { handlePaymentTimeout(); } } catch (error) { console.error('Failed to check transaction status:', error); } };
poll();}Webhook Pattern (Recommended)
Section titled “Webhook Pattern (Recommended)”For production applications, implement webhook handling:
// Webhook endpointapp.post('/webhooks/funtico', async (req, res) => { try { const { transaction_id, status, reason } = req.body;
// Verify webhook signature (implement signature verification) if (!verifyWebhookSignature(req)) { return res.status(401).send('Unauthorized'); }
// Update transaction status in your database await updateTransactionStatus(transaction_id, status, reason);
// Handle status-specific logic switch (status) { case 'completed': await grantItemToUser(transaction_id); break; case 'failed': await handlePaymentFailure(transaction_id, reason); break; }
res.status(200).send('OK'); } catch (error) { console.error('Webhook processing failed:', error); res.status(500).send('Internal Server Error'); }});Error Handling
Section titled “Error Handling”Common Error Scenarios
Section titled “Common Error Scenarios”// Handle different error typestry { const transaction = await sdk.createTransaction(transactionData);} catch (error) { if (isSDKError(error)) { switch (error.name) { case 'invalid_body_format': // Handle malformed request data console.error('Invalid transaction data:', error); break; case 'transaction_not_found': // Handle missing transaction console.error('Transaction not found:', error); break; default: // Handle other SDK errors console.error('SDK error:', error); } } else { // Handle network or other errors console.error('Unexpected error:', error); }}User-Friendly Error Messages
Section titled “User-Friendly Error Messages”function getErrorMessage(error: unknown): string { if (isSDKError(error)) { switch (error.name) { case 'invalid_body_format': return 'Invalid payment request. Please try again.'; case 'transaction_not_found': return 'Payment session expired. Please try again.'; default: return 'Payment service temporarily unavailable. Please try again later.'; } } return 'An unexpected error occurred. Please try again.';}Best Practices
Section titled “Best Practices”Security
Section titled “Security”- Never expose API credentials in frontend code
- Use HTTP-only cookies for token storage, never localStorage
- Validate all input data before creating transactions
- Implement webhook signature verification for production
- Use HTTPS for all API communications
- Backend-only authentication - handle OAuth flows on backend
- Token expiration - Access tokens expire in 1 hour, refresh tokens in 7 days
User Experience
Section titled “User Experience”- Provide clear feedback during payment processing
- Handle all transaction states (success, failure, cancellation, expiration)
- Implement retry logic for failed transactions
- Show appropriate loading states during API calls
- Display transaction details before user confirmation
Performance
Section titled “Performance”- Cache transaction data when appropriate
- Implement efficient polling with reasonable intervals
- Use webhooks for real-time updates in production
- Optimize API calls to minimize latency
Testing
Section titled “Testing”- Use sandbox environment for development and testing
- Test all transaction states and error scenarios
- Verify webhook handling with test transactions
- Test expiration handling with short timeouts
Environment Configuration
Section titled “Environment Configuration”Development
Section titled “Development”const sdk = new FunticoSDK({ pspClientId: process.env.FUNTICO_PSP_CLIENT_ID, pspClientSecret: process.env.FUNTICO_PSP_CLIENT_SECRET, env: 'staging'});Production
Section titled “Production”const sdk = new FunticoSDK({ pspClientId: process.env.FUNTICO_PSP_CLIENT_ID, pspClientSecret: process.env.FUNTICO_PSP_CLIENT_SECRET, env: 'production'});Next Steps
Section titled “Next Steps”- Authentication Integration - Set up user authentication
- Webhook Configuration - Configure webhook endpoints
- SDK API Reference - Complete SDK documentation