Funtico SDK
The official TypeScript/JavaScript SDK for integrating with Funticoâs payment processing and authentication services. This SDK provides a comprehensive interface for managing transactions, user authentication, and payment flows in your applications.
Features
Section titled âFeaturesâ- đ OAuth2 + PKCE Authentication - Secure user authentication with OpenID Connect
- đł Payment Processing - Create and manage transactions with multiple currencies
- đ Tournament System - Create, manage, and run competitive tournaments with TICO prizes
- đ¤ User Management - Retrieve user information and balances
- đŽ Game Integration - Support for game-specific transactions and tournaments
- đĄď¸ Type Safety - Full TypeScript support with strict typing
- đ Multi-Environment - Support for staging and production environments
- đą Flexible UI Modes - redirect, and new tab payment flows
Installation
Section titled âInstallationânpm install @pillarex/funtico-sdkyarn add @pillarex/funtico-sdkpnpm add @pillarex/funtico-sdkQuick Start
Section titled âQuick Startâimport { FunticoSDK } from '@pillarex/funtico-sdk';
// Initialize SDK with your credentialsconst sdk = new FunticoSDK({ pspClientId: 'your-psp-client-id', pspClientSecret: 'your-psp-client-secret', authClientId: 'your-auth-client-id', authClientSecret: 'your-auth-client-secret', apiKeyId: 'your-api-key-id', // For tournament operations apiKeySecret: 'your-api-key-secret', // For tournament operations env: 'staging' // or 'production'});
// Create a transactionconst transaction = await sdk.createTransaction({ items: [{ name: 'Premium Game Pass', unit_price: 1999, // $19.99 in cents quantity: 1, currency: 'usd', metadata: {} }], success_url: 'https://yourapp.com/success', cancel_url: 'https://yourapp.com/cancel', ui_mode: 'redirect', expiration_sec: 3600, metadata: {}});Configuration
Section titled âConfigurationâAuthentication Modes
Section titled âAuthentication ModesâThe SDK supports three authentication configurations:
1. Auth-Only Mode
Section titled â1. Auth-Only ModeâFor applications that only need user authentication:
const sdk = new FunticoSDK({ authClientId: 'your-auth-client-id', authClientSecret: 'your-auth-client-secret', env: 'staging'});2. PSP-Only Mode
Section titled â2. PSP-Only ModeâFor applications that only need payment processing:
const sdk = new FunticoSDK({ pspClientId: 'your-psp-client-id', pspClientSecret: 'your-psp-client-secret', env: 'staging'});3. Full Mode
Section titled â3. Full ModeâFor applications that need both authentication and payments:
const sdk = new FunticoSDK({ pspClientId: 'your-psp-client-id', pspClientSecret: 'your-psp-client-secret', authClientId: 'your-auth-client-id', authClientSecret: 'your-auth-client-secret', env: 'staging'});4. Tournament Mode
Section titled â4. Tournament ModeâFor applications that need tournament functionality (requires API Key credentials):
const sdk = new FunticoSDK({ pspClientId: 'your-psp-client-id', pspClientSecret: 'your-psp-client-secret', authClientId: 'your-auth-client-id', authClientSecret: 'your-auth-client-secret', apiKeyId: 'your-api-key-id', apiKeySecret: 'your-api-key-secret', env: 'staging'});Environment Configuration
Section titled âEnvironment Configurationâ- staging: For development and testing
- production: For live applications
Authentication Flow
Section titled âAuthentication Flowâ-
Initiate Sign-In:
const signInResponse = await sdk.signInWithFuntico({callbackUrl: 'https://yourapp.com/auth/callback'});// Store the codeVerifier and state for later use return redirectUrl to the clientconst { codeVerifier, redirectUrl, state } = signInResponse; -
Handle Callback:
// After user completes authentication, handle the callbackconst tokens = await sdk.getTokens({codeVerifier: codeVerifier, // from step 1url: request.url // full callback URL with all query parameters});// Store tokens securelyconst { accessToken, refreshToken } = tokens; -
Get User Information:
// Get user profile from OpenID Connectconst userProfile = await sdk.getMe({accessToken: accessToken});// Get detailed user info from Funtico APIconst userInfo = await sdk.getUserInfo({accessToken: accessToken});// Get user balanceconst balance = await sdk.getUserBalance({accessToken: accessToken}); -
Sign Out:
const signOutResponse = await sdk.signOut({postSignOutRedirectUri: 'https://yourapp.com/signed-out',});
Payment Operations
Section titled âPayment OperationsâCreating Transactions
Section titled âCreating Transactionsâconst transaction = await sdk.createTransaction({ items: [ { name: 'Gold Coins', description: 'Premium in-game currency', unit_price: 499, // $4.99 in cents quantity: 100, currency: 'usd', image_url: 'https://example.com/gold-coins.png', metadata: { item_type: 'currency', game_id: 'my-game-123' } }, { name: 'Power-up Pack', unit_price: 50, // 50 TICO tokens quantity: 1, currency: 'tico', metadata: { item_type: 'powerup' } } ], success_url: 'https://yourapp.com/payment/success', cancel_url: 'https://yourapp.com/payment/cancel', ui_mode: 'redirect', // 'redirect' | 'new_tab' expiration_sec: 3600, // 1 hour metadata: { user_id: 'user-123', session_id: 'session-456' }});
console.log('Transaction created:', transaction.id);Retrieving Transactions
Section titled âRetrieving Transactionsâconst transaction = await sdk.getTransactionById('tx_1234567890');
if (transaction) { console.log('Transaction status:', transaction.status.current); console.log('Transaction items:', transaction.items);}UI Modes
Section titled âUI ModesâRedirect Mode
Section titled âRedirect ModeâFor full-page payment flows:
const transaction = await sdk.createTransaction({ // ... other properties ui_mode: 'redirect'});New Card Mode
Section titled âNew Card ModeâFor first-time payments:
const transaction = await sdk.createTransaction({ // ... other properties ui_mode: 'new_tab'});Tournament Operations
Section titled âTournament OperationsâTournament Management
Section titled âTournament ManagementâTournament operations require API Key authentication for game server operations and Access Token authentication for player actions.
Creating Tournaments
Section titled âCreating Tournamentsâ// Create a basic tournamentconst tournament = await sdk.createTournament({ name: "Weekly Puzzle Challenge", description: "Compete in our weekly puzzle tournament for TICO prizes!", starts_at: "2024-12-01T10:00:00Z", ends_at: "2024-12-07T22:00:00Z", max_players: 100, score_mode: "descending", // "ascending" | "descending" score_type: "points", // "points" | "points_accumulated" entry_fee_type: "free", // "free" | "tico" entry_limit: 1, // Number of entries per player is_kyc_required: false, is_published: false, // Create as draft metadata: { difficulty: "medium", game_mode: "puzzle" }});
// Create tournament with prizesconst prizeTournament = await sdk.createTournament({ name: "Championship Battle Royale", description: "Ultimate battle royale tournament with massive TICO prizes!", cover_image_url: "https://your-game.com/images/tournament-cover.jpg", starts_at: "2024-12-15T18:00:00Z", ends_at: "2024-12-15T22:00:00Z", max_players: 64, score_mode: "descending", score_type: "points", entry_fee_type: "tico", entry_fee_amount: 10, // 10 TICO entry fee entry_limit: 3, is_kyc_required: true, is_published: false, metadata: { game_mode: "battle_royale", difficulty: "expert" }, prize_distributions: [ { type: "specific_place", place: 1, rewards: [{ type: "tico", amount: 300 }] // 1st place: 300 TICO }, { type: "place_range", place_range_start: 2, place_range_end: 3, rewards: [{ type: "tico", amount: 100 }] // 2nd-3rd: 100 TICO each }, { type: "percentage_range", percentage_range: 10, // Top 10% rewards: [{ type: "tico", amount: 25 }] // Top 10%: 25 TICO each } ]});Tournament Management Operations
Section titled âTournament Management Operationsâ// Get tournaments listconst tournaments = await sdk.getTournaments({ status?: "draft" | "published" | "active" | "completed", limit?: 50, offset?: 0});
// Get specific tournamentconst tournament = await sdk.getTournamentById('tournament_123');
// Publish a draft tournamentawait sdk.publishTournament('tournament_123');
// Update tournament (limited fields after publishing)await sdk.updateTournament('tournament_123', { name: "Updated Tournament Name", description: "Updated description", cover_image_url: "https://your-game.com/new-cover.jpg"});Player Tournament Actions
Section titled âPlayer Tournament ActionsâPlayer operations require both API Key (game server) and Access Token (user) authentication:
Joining Tournaments
Section titled âJoining Tournamentsâ// Join tournament (requires dual authentication)const joinResult = await sdk.joinTournament('tournament_123', { accessToken: 'user_access_token', // User authentication password?: 'tournament_password', // Optional tournament password user_ip?: '192.168.1.1' // Optional for analytics});
console.log('Tournament joined:', joinResult.success);Score Submission
Section titled âScore Submissionâ// Submit tournament score (requires dual authentication)const scoreResult = await sdk.submitTournamentScore('tournament_123', { score: 9500, // Player's score accessToken: 'user_access_token', // User authentication user_ip?: '192.168.1.1' // Optional for analytics});
console.log('Score submitted:', scoreResult.leaderboard_position);Tournament Data Retrieval
Section titled âTournament Data Retrievalâ// Get tournament leaderboardconst leaderboard = await sdk.getTournamentLeaderboard('tournament_123', { limit?: 100, // Number of entries to retrieve offset?: 0 // Pagination offset});
console.log('Current leader:', leaderboard.entries[0]);
// Get player's tournament statusconst playerStatus = await sdk.getPlayerTournamentStatus('tournament_123', { accessToken: 'user_access_token'});
console.log('Player joined:', playerStatus.is_joined);console.log('Player entries:', playerStatus.entries);Tournament Error Handling
Section titled âTournament Error Handlingâtry { await sdk.joinTournament('tournament_123', { accessToken: userAccessToken });} catch (error) { if (isSDKError(error)) { switch (error.error_code) { case 'tournament_full': console.log('Tournament is full'); break; case 'insufficient_balance': console.log('Insufficient TICO balance for entry fee'); break; case 'already_joined': console.log('Player already joined this tournament'); break; case 'entry_limit_reached': console.log('Player has reached maximum entries'); break; case 'tournament_ended': console.log('Tournament has ended'); break; case 'kyc_required': console.log('KYC verification required'); break; case 'invalid_password': console.log('Invalid tournament password'); break; default: console.error('Tournament error:', error.message); } }}Error Handling
Section titled âError HandlingâThe SDK uses a custom SDKError class for error handling:
import { SDKError, isSDKError } from '@pillarex/funtico-sdk';
try { const transaction = await sdk.createTransaction({ // ... transaction data });} catch (error) { if (isSDKError(error)) { console.error('SDK Error:', error.name, error.status);
switch (error.name) { case 'internal_server_error': // Handle server error break; // Add more error cases as needed } } else { console.error('Unexpected error:', error); }}TypeScript Support
Section titled âTypeScript SupportâThe SDK is written in TypeScript and provides comprehensive type definitions:
import type { CreateTransaction, ReadTransaction, SignInWithFunticoParams, GetTokensResponse, RefreshTokensParams, RefreshTokensResponse, Currency, UIMode, TransactionStatusType, CreateTournament, Tournament, TournamentStatus, TournamentLeaderboard, TournamentScoreMode, TournamentScoreType, PrizeDistribution} from '@pillarex/funtico-sdk';
// Type-safe transaction creationconst transactionData: CreateTransaction = { items: [{ name: 'Premium Feature', unit_price: 999, quantity: 1, currency: 'usd', metadata: {} }], success_url: 'https://example.com/success', cancel_url: 'https://example.com/cancel', ui_mode: 'redirect', expiration_sec: 3600, metadata: {}};
// Type-safe tournament creationconst tournamentData: CreateTournament = { name: 'Championship Tournament', description: 'Elite tournament with TICO prizes', starts_at: '2024-12-01T10:00:00Z', ends_at: '2024-12-07T22:00:00Z', max_players: 100, score_mode: 'descending', score_type: 'points', entry_fee_type: 'tico', entry_fee_amount: 10, entry_limit: 1, is_kyc_required: false, is_published: false, metadata: { difficulty: 'hard', category: 'competitive' }, prize_distributions: [ { type: 'specific_place', place: 1, rewards: [{ type: 'tico', amount: 500 }] } ]};Examples
Section titled âExamplesâComplete Authentication Flow (Backend)
Section titled âComplete Authentication Flow (Backend)âThis example shows backend Express handlers that manage authentication state via cookies:
import express from 'express';import { FunticoSDK } from '@pillarex/funtico-sdk';
const app = express();const sdk = new FunticoSDK({ authClientId: process.env.FUNTICO_AUTH_CLIENT_ID!, authClientSecret: process.env.FUNTICO_AUTH_CLIENT_SECRET!, env: 'staging'});
// 1. Initiate login - returns redirect URL to frontendapp.post('/auth/login', async (req, res) => { try { const { codeVerifier, redirectUrl, state } = await sdk.signInWithFuntico({ callbackUrl: 'https://myapp.com/auth/callback' });
// Store codeVerifier in cookie using state as key res.cookie(`state_${state}`, codeVerifier, { httpOnly: true, secure: true, sameSite: 'lax', maxAge: 10 * 60 * 1000 // 10 minutes });
// Return redirect URL to frontend res.json({ redirectUrl }); } catch (error) { res.status(500).json({ error: 'Failed to initiate login' }); }});
// 2. 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' }); }
// Retrieve codeVerifier from cookie const codeVerifier = req.cookies[`state_${state}`]; if (!codeVerifier) { return res.status(400).json({ error: 'Invalid or expired state' }); }
// Clear the state cookie res.clearCookie(`state_${state}`);
// Exchange code for tokens const { accessToken, refreshToken } = await sdk.getTokens({ codeVerifier, url: req.url // full callback URL with query parameters });
// Get user information const userInfo = await sdk.getUserInfo({ accessToken }); const balance = await sdk.getUserBalance({ accessToken });
// Store tokens in secure cookies or database 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: 30 * 24 * 60 * 60 * 1000 // 30 days });
// Redirect to success page res.redirect('https://myapp.com/dashboard?login=success'); } catch (error) { res.status(500).json({ error: 'Failed to handle callback' }); }});
// 3. Get user profile (protected route)app.get('/auth/profile', async (req, res) => { try { const accessToken = req.cookies.access_token; if (!accessToken) { return res.status(401).json({ error: 'Not authenticated' }); }
const userInfo = await sdk.getUserInfo({ accessToken }); const balance = await sdk.getUserBalance({ accessToken });
res.json({ userInfo, balance }); } catch (error) { res.status(500).json({ error: 'Failed to get user profile' }); }});
// 4. Refresh access tokenapp.post('/auth/refresh', async (req, res) => { try { const refreshToken = req.cookies.refresh_token; if (!refreshToken) { return res.status(401).json({ error: 'No refresh token' }); }
const { accessToken, refreshToken: newRefreshToken } = await sdk.refreshTokens({ refreshToken });
// Update tokens in cookies res.cookie('access_token', accessToken, { httpOnly: true, secure: true, sameSite: 'lax', maxAge: 60 * 60 * 1000 // 1 hour });
res.cookie('refresh_token', newRefreshToken, { httpOnly: true, secure: true, sameSite: 'lax', maxAge: 30 * 24 * 60 * 60 * 1000 // 30 days });
res.json({ success: true }); } catch (error) { res.status(401).json({ error: 'Failed to refresh token' }); }});
// 5. Initiate logout - returns sign out URL to frontendapp.post('/auth/logout', async (req, res) => { try { const { signOutUrl } = await sdk.signOut({ postSignOutRedirectUri: 'https://myapp.com/signed-out' });
// Clear authentication cookies res.clearCookie('access_token'); res.clearCookie('refresh_token');
// Return sign out URL to frontend res.json({ signOutUrl }); } catch (error) { res.status(500).json({ error: 'Failed to initiate logout' }); }});
// 6. Handle logout callbackapp.get('/auth/signed-out', (req, res) => { // User has been signed out from Funtico res.redirect('https://myapp.com?logout=success');});Frontend Integration
Section titled âFrontend IntegrationâYour frontend would interact with these endpoints like this:
// Frontend: Initiate loginasync function login() { const response = await fetch('/auth/login', { method: 'POST' }); const { redirectUrl } = await response.json();
// Redirect user to Funtico window.location.href = redirectUrl;}
// Frontend: Get user profileasync function getProfile() { const response = await fetch('/auth/profile', { credentials: 'include' // Include cookies });
if (response.ok) { const { userInfo, balance } = await response.json(); return { userInfo, balance }; } else if (response.status === 401) { // Try to refresh the token const refreshed = await refreshToken(); if (refreshed) { // Retry the request return getProfile(); } }}
// Frontend: Refresh tokenasync function refreshToken() { const response = await fetch('/auth/refresh', { method: 'POST', credentials: 'include' });
return response.ok;}
// Frontend: Logoutasync function logout() { const response = await fetch('/auth/logout', { method: 'POST' }); const { signOutUrl } = await response.json();
// Redirect user to sign out window.location.href = signOutUrl;}Game Purchase Flow
Section titled âGame Purchase Flowâimport { FunticoSDK } from '@pillarex/funtico-sdk';
class GameStore { private sdk: FunticoSDK;
constructor() { this.sdk = new FunticoSDK({ pspClientId: process.env.FUNTICO_PSP_CLIENT_ID!, pspClientSecret: process.env.FUNTICO_PSP_CLIENT_SECRET!, env: 'production' }); }
async purchaseGameItem(itemId: string, quantity: number) { const itemConfig = { 'gold_coins': { name: 'Gold Coins', price: 99, currency: 'usd' }, 'power_up': { name: 'Power Up', price: 10, currency: 'tico' } };
const item = itemConfig[itemId]; if (!item) throw new Error('Invalid item');
const transaction = await this.sdk.createTransaction({ items: [{ name: item.name, unit_price: item.price, quantity, currency: item.currency as 'usd' | 'tico', metadata: { item_id: itemId } }], success_url: `https://mygame.com/purchase/success`, cancel_url: `https://mygame.com/purchase/cancel`, ui_mode: 'redirect', expiration_sec: 1800, // 30 minutes metadata: { game_session: 'session-123', player_id: 'player-456' } });
return transaction; }
async checkTransactionStatus(transactionId: string) { const transaction = await this.sdk.getTransactionById(transactionId); return transaction?.status.current; }}Troubleshooting
Section titled âTroubleshootingâCommon Issues
Section titled âCommon IssuesâAuthentication Errors
Section titled âAuthentication Errorsâ- Issue:
internal_server_errorduring authentication - Solution: Verify your
authClientIdandauthClientSecretare correct
Payment Errors
Section titled âPayment Errorsâ- Issue:
internal_server_errorduring transaction creation - Solution: Verify your
pspClientIdandpspClientSecretare correct
Network Errors
Section titled âNetwork Errorsâ- Issue: Requests timing out
- Solution: Check your network connection and API endpoint availability
Environment Issues
Section titled âEnvironment IssuesâStaging vs Production
Section titled âStaging vs Productionâ- Ensure youâre using the correct environment configuration
- Staging credentials wonât work in production and vice versa
License
Section titled âLicenseâThis project is licensed under the MIT License - see the LICENSE file for details.