Skip to content

Funtico SDK

npm version

TypeScript

License: MIT

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.

  • 🔐 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
Terminal window
npm install @pillarex/funtico-sdk
import { FunticoSDK } from '@pillarex/funtico-sdk';
// Initialize SDK with your 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', // For tournament operations
apiKeySecret: 'your-api-key-secret', // For tournament operations
env: 'staging' // or 'production'
});
// Create a transaction
const 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: {}
});

The SDK supports three authentication configurations:

For applications that only need user authentication:

const sdk = new FunticoSDK({
authClientId: 'your-auth-client-id',
authClientSecret: 'your-auth-client-secret',
env: 'staging'
});

For applications that only need payment processing:

const sdk = new FunticoSDK({
pspClientId: 'your-psp-client-id',
pspClientSecret: 'your-psp-client-secret',
env: 'staging'
});

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'
});

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'
});
  • staging: For development and testing
  • production: For live applications
  1. 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 client
    const { codeVerifier, redirectUrl, state } = signInResponse;
  2. Handle Callback:

    // After user completes authentication, handle the callback
    const tokens = await sdk.getTokens({
    codeVerifier: codeVerifier, // from step 1
    url: request.url // full callback URL with all query parameters
    });
    // Store tokens securely
    const { accessToken, refreshToken } = tokens;
  3. Get User Information:

    // Get user profile from OpenID Connect
    const userProfile = await sdk.getMe({
    accessToken: accessToken
    });
    // Get detailed user info from Funtico API
    const userInfo = await sdk.getUserInfo({
    accessToken: accessToken
    });
    // Get user balance
    const balance = await sdk.getUserBalance({
    accessToken: accessToken
    });
  4. Sign Out:

    const signOutResponse = await sdk.signOut({
    postSignOutRedirectUri: 'https://yourapp.com/signed-out',
    });
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);
const transaction = await sdk.getTransactionById('tx_1234567890');
if (transaction) {
console.log('Transaction status:', transaction.status.current);
console.log('Transaction items:', transaction.items);
}

For full-page payment flows:

const transaction = await sdk.createTransaction({
// ... other properties
ui_mode: 'redirect'
});

For first-time payments:

const transaction = await sdk.createTransaction({
// ... other properties
ui_mode: 'new_tab'
});

Tournament operations require API Key authentication for game server operations and Access Token authentication for player actions.

// Create a basic tournament
const 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 prizes
const 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
}
]
});
// Get tournaments list
const tournaments = await sdk.getTournaments({
status?: "draft" | "published" | "active" | "completed",
limit?: 50,
offset?: 0
});
// Get specific tournament
const tournament = await sdk.getTournamentById('tournament_123');
// Publish a draft tournament
await 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 operations require both API Key (game server) and Access Token (user) authentication:

// 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);
// 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);
// Get tournament leaderboard
const 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 status
const playerStatus = await sdk.getPlayerTournamentStatus('tournament_123', {
accessToken: 'user_access_token'
});
console.log('Player joined:', playerStatus.is_joined);
console.log('Player entries:', playerStatus.entries);
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);
}
}
}

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);
}
}

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 creation
const 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 creation
const 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 }]
}
]
};

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 frontend
app.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 callback
app.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 token
app.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 frontend
app.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 callback
app.get('/auth/signed-out', (req, res) => {
// User has been signed out from Funtico
res.redirect('https://myapp.com?logout=success');
});

Your frontend would interact with these endpoints like this:

// Frontend: Initiate login
async 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 profile
async 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 token
async function refreshToken() {
const response = await fetch('/auth/refresh', {
method: 'POST',
credentials: 'include'
});
return response.ok;
}
// Frontend: Logout
async function logout() {
const response = await fetch('/auth/logout', { method: 'POST' });
const { signOutUrl } = await response.json();
// Redirect user to sign out
window.location.href = signOutUrl;
}
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;
}
}
  • Issue: internal_server_error during authentication
  • Solution: Verify your authClientId and authClientSecret are correct
  • Issue: internal_server_error during transaction creation
  • Solution: Verify your pspClientId and pspClientSecret are correct
  • Issue: Requests timing out
  • Solution: Check your network connection and API endpoint availability
  • Ensure you’re using the correct environment configuration
  • Staging credentials won’t work in production and vice versa

This project is licensed under the MIT License - see the LICENSE file for details.