Navigating the Integration of Web-Based Casino Games: A Comprehensive Guide

Chukwuyenum Opone
8 min readAug 14, 2023

Introduction:

Embarking on the journey of integrating web-based casino games onto your platform can be an exhilarating experience. Over the past five years, my exploration into building web and gaming applications using Laravel and Node has been nothing short of a roller coaster ride. Today, I want to delve into the fascinating world of integrating casino games, also known as web-based online casinos, and share insights gained from my hands-on experience.

Web-Based Online Casinos: Unveiling the Landscape

Web-based online casinos, often referred to as no-download casinos, offer users the opportunity to engage in casino games without the need to download software. Instead, a stable internet connection is all that’s required to enjoy a seamless gaming experience. Graphics, sounds, and animations are loaded directly through the web interface. Gone are the days of browser plugins like Flash Player, Shockwave Player, or Java, as most gameplay now takes place through an HTML interface.

src : https://en.wikipedia.org/wiki/Online_casino

Evolution of Web-Based Casinos: Creativity Meets Innovation

The landscape of web-based casinos is constantly evolving, with providers adopting creative strategies to enhance user engagement. Over the years, I’ve had the privilege of building virtual games for platforms like dice.ng and seamlessly integrating third-party casino and virtual games from renowned providers such as Evo Play, C2 Gaming and Shacks Evolution into sports betting web applications across Africa.

A Strategic Blueprint: Steps to Integrate Casino Games

For platform owners seeking to integrate casino games, a strategic blueprint is essential for success. Here’s a comprehensive roadmap:

1. Find the Right Provider: Identify a reputable casino game provider that aligns with your platform’s vision and audience.
2. Establish Communication Channels: Create effective channels of communication with the chosen provider, such as WhatsApp or Skype.
3. Obtain API Documentation: Request comprehensive API documentation, which could be in the form of Postman collections, Swagger docs, or PDFs.
4. Engage Expertise: Enlist the expertise of a skilled engineer to navigate the integration process seamlessly.

The Integration Odyssey: Exploring the Workflow

As an experienced software engineer, I’ve fine-tuned a workflow that ensures a successful casino game integration process. Here’s a glimpse into the workflow:

1. Retrieve Game Information: Acquire essential game details such as IDs, names, and images.
2. Authentication and Security: Implement proper authentication mechanisms, such as OAuth or custom methods, to secure endpoints.
3. Establish Game Sessions: Create and manage game sessions for a smooth user experience.
4. Real-Time Balance Update: Employ web-hooks to update player balances in real-time.
5. Wallet Endpoint Web-hooks: Facilitate seamless crediting and debiting of user accounts based on their betting outcomes.

Beyond the Basics: Unlocking Provider-Specific Features

While the core integration features remain consistent among providers, some go the extra mile to offer additional features like analytics and financial insights. Some even provide a user-friendly UI dashboard to monitor activities and enhance control.

Strategic Approaches: Direct Integration vs. Middleman Micro-services

When considering integration, you have two strategic options:

1. Direct Integration: Integrate the casino games directly onto your platform for a cohesive user experience.
2. Middleman Micro-services: Opt for a middleman approach, employing micro-services to bridge the gap between your platform and the casino game provider.

Here’s a comparison of two sample service classes, one written in Laravel PHP and the other in Node.js TypeScript, using Shack-Evolution as a sample scenario. This comparison showcases the same functionality implemented in two different programming languages.

  1. Service class in Laravel php
<?php

namespace App\Service;

use Exception;
use Carbon\Carbon;
use App\Models\User;
use App\Models\Game;
use App\Models\Bet;

class ShackService
{

/**
* Public Key
* @var string
*/
protected $publicKey;

/**
* Secret Key
* @var string
*/
protected $secretKey;

/**
* Token
* @var string
*/
protected $token;

/**
* Instance of Client
* @var HttpClient
*/
protected $client;

/**
* Response from requests made to Your Notify API
* @var mixed
*/
protected $response;

/**
* Your Notify API base Url
* @var string
*/
protected $baseUrl;


/**
* Dial Code Settings
* @var string
*/
protected $dialCode;


/**
* Sms Id
* @var string
*/
protected $clientId;


/**
* Email Id
* @var string
*/
protected $emailId;


public function __construct()
{
$this->baseUrl = config('shack.base_url');
$this->publicKey = config('shack.public_key');
$this->secretKey = config('shack.secret_key');
$this->clientId = config('shack.client_id');
$this->emailId = config('shack.email');
$this->setRequestOptions();
}

/**
* Set options for making the Client request
*/
private function setRequestOptions()
{
$this->client = Http::withHeaders([
"Accept" => "application/json",
"Content-Type" => "application/json",
"CLIENT-ID" => $this->publicKey,
"PARTNER-EMAIL" => $this->emailId,
]);
}

private function authorizeHeader(){
$this->client = Http::withHeaders([
"Accept" => "application/json",
"Content-Type" => "application/json",
'Authorization' => 'Bearer ' . $this->token,
]);
}

/**
* @param string $relativeUrl
* @param string $method
* @param array $body
* @return YourNotifyRepository
* @throws IsNullException
*/
private function setHttpResponse($relativeUrl, $method, $body = [])
{
// post or get method must be specified
if (is_null($method)) {
throw new Exception("Empty method not allowed");
}
if (isset($body)) {
$this->response = $this->client->{strtolower($method)}($this->baseUrl . $relativeUrl, $body)->json();
}else{
$this->response = $this->client->{strtolower($method)}($this->baseUrl . $relativeUrl)->json();
}
return $this;
}

/**
* Get the whole response from a get operation
* @return array
*/
private function getResponse()
{
return $this->response;
}

// generate token to be used by other endpoints
public function generateToken() {
$url = '/v1/partner/fe/token';
$this->setHttpResponse($url, 'GET', []);
$this->token = $this->getResponse()['meta']['token'];
}
// get games from provider
public function getGames() {
$url = '/v2/games';
$this->authorizeHeader();
$this->setHttpResponse($url, 'GET', []);
return $this->getResponse();
}


// get games synced in db
public function getGamesDb() {
return Game::whereProviderId("shack-evolution")->get();
}
// construct a game url to open in a new tab to start up a game
public function constructGameUrl($data, Game $game) {
$url = '/v2/partner/cl/tokenize';
$this->authorizeHeader();
$data = array_merge($data,[
"email"=> $this->emailId,
"publicKey"=> $this->publicKey
]);
$this->setHttpResponse($url, 'POST', $data);
$response = $this->getResponse();
if (isset($response['status']) && $response['status'] == true) {
$session_url = $this->removeTrailingSlash($game->url).'?clientId='.$response['data'];
return array_merge($response,[
"session_url" => $session_url,
]);
}
return $response;
}

// returns player details
public function getPlayerDetails($data){

$user = User::whereId($data['playerId'])->first();
if (!$user){
throw new Exception('User not found');
}
return $this->balanceResponse($user->balance, $user->username, $user->id, "NGN");
}

/**
*
* Callback Transaction
*
*/
public function callBack($data)
{
if(isset($data['type'])){
switch ($data['type']) {
case 'debit':
// process bet
return $this->processBet($data);
break;
case 'credit':
// process win
return $this->processWin($data);
break;
}
}else {
// return player details
return $this->getPlayerDetails($data);
}
}


private function processBet($data)
{
//find user details
$user = User::whereId($data['playerId'])->first();
//if user not found, return false
if(!$user){
throw new Exception('User not found');
}
// check user has sufficient balance
if ($user->balance < $data['amount']){
throw new Exception('Insufficient balance');
}
// check ticket transaction id not exist
$ticket = Bet::whereRoundId($data['roundId'])->first();
if ($ticket){
return $this->response(true, 3, 'Duplicate Transaction');
}

// Deduct amount from user balance here


return $this->response(true, 1);
}

private function processWin($data)
{
//find user details
$user = User::whereId($data['playerId'])->first();
//if user not found, return false
if(!$user){
throw new Exception('User Not Found');
}
// check ticket round id not exist
$ticket = Bet::whereRoundId($data['roundId'])->first();

// check it ticket user id is not the same as webhook player id
if ($ticket->user_id != $user->id){
throw new Exception('Player id is not the same as webhook player');
}
$payout = $data['amount'];
if($payout > 0){
// add payout to user account
} else {
//update bet to lost
}
return $this->response(true, 1);
}

private function balanceResponse($balance, $username, $userId, $currency = "NGN"){
return response()->json([
"balance"=> $balance,
"username"=> $username,
"userId"=> $userId,
"currency"=> $currency,
]);
}

private function response($status, $code, $message = ""){
return response()->json([
"processed" => $status,
"code" => $code,
"message" => $message
]);
}

public function generateMD5Hash() {
return md5($this->publicKey.$this->secretKey);
}

function removeTrailingSlash($url) {
if (substr($url, -1) === '/') {
return substr($url, 0, -1);
}
return $url;
}

// sync games from api to game table in your db
public function syncGame() {
$gameList = $this->getGames();
foreach ($gameList['data'] as $key => $game) {
Game::updateOrCreate(
['game_id' => $game['gameId']],
[
'title' => $game['gameId'],
'url' => $game['url'],
'image_path' => $game['assets']['logo'],
'banner_path' => $game['assets']['banner'],
'type' => 'casino',
'provider_id' => "shack-evolution"
]
);
}
return $this->getGamesDb();
}
}

2. Service class in Node Typescript

import axios, { AxiosResponse } from 'axios';
import Game from '../models/Game';

interface Game {
gameId: string;
url: string;
assets: {
logo: string;
banner: string;
};
}

interface WebhookData {
type: string;
playerId: string;
amount: number;
roundId: string;
}

class ShackService {
private publicKey: string;
private secretKey: string;
private token: string;
private client: any;
private response: any;
private baseUrl: string;
private emailId: string;

constructor() {
this.baseUrl = process.env.SHACK_BASE_URL;
this.publicKey = process.env.SHACK_PUBLIC_KEY;
this.secretKey = process.env.SHACK_SECRET_KEY;
this.emailId = process.env.SHACK_EMAIL;
this.setRequestOptions();
}

private setRequestOptions() {
this.client = axios.create({
baseURL: this.baseUrl,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'CLIENT-ID': this.publicKey,
'PARTNER-EMAIL': this.emailId,
},
});
}

private authorizeHeader() {
this.client.defaults.headers['Authorization'] = 'Bearer ' + this.token;
}

private setHttpResponse(relativeUrl: string, method: string, body: any = null) {
if (!method) {
throw new Error('Empty method not allowed');
}
const requestConfig = {
url: relativeUrl,
method: method.toLowerCase(),
data: body,
};
return this.client.request(requestConfig).then((response: AxiosResponse) => {
this.response = response.data;
});
}

public generateToken(): void {
const url = '/v1/partner/fe/token';
this.setHttpResponse(url, 'GET');
this.token = this.response.meta.token;
}

public getGames(): Promise<Game[]> {
const url = '/v2/games';
this.authorizeHeader();
return this.setHttpResponse(url, 'GET').then(() => {
return this.response.data;
});
}

public async getGamesDb(): Promise<Game[]> {
const gameList = await Game.find({ providerId: 'shack-evolution' });
const games: Game[] = [];

for (const game of gameList) {
games.push({
gameId: game.gameId,
url: game.url,
assets: {
logo: game.imagePath,
banner: game.bannerPath,
},
});
}

return games;
}

public async constructGameUrl(data: any, game: Game): Promise<any> {
const url = '/v2/partner/cl/tokenize';
this.authorizeHeader();
const requestData = {
...data,
email: this.emailId,
publicKey: this.publicKey,
};
await this.setHttpResponse(url, 'POST', requestData);
const response = this.response;
if (response.status && response.status === true) {
const sessionUrl = this.removeTrailingSlash(game.url) + '?clientId=' + response.data;
return {
...response,
session_url: sessionUrl,
};
}
return response;
}

// Rest of the methods and logic

private removeTrailingSlash(url: string): string {
if (url.endsWith('/')) {
return url.slice(0, -1);
}
return url;
}
}

In this comparison, both service classes, one written in Laravel PHP and the other in Node.js TypeScript, achieve the same goal of fetching casino games generating Tokens, constructing game Url, and processing callback data from a Shack-Evolution provider. The PHP version uses Eloquent ORM to interact with the database, while the TypeScript version uses an asynchronous function with the await keyword to interact with the MongoDB database.

Both implementations showcase the power of their respective programming languages and frameworks while achieving the desired functionality. This comparison demonstrates how developers can choose between these technologies based on their project requirements and personal preferences.

Conclusion:

Integrating web-based casino games onto your platform is an intricate yet rewarding endeavour. It demands meticulous planning, strong communication with providers, technical prowess, and a deep understanding of user dynamics. By following the outlined steps and delving into the integration process, you’ll be well-prepared to offer an enhanced gaming experience that captivates and entertains users across the African continent and beyond.

If you found this helpful please let me know what you think in the comments, You are welcome to suggest changes.

Thank you for the time spent reading this article.

You can follow me on all my social handles @officialyenum and please subscribe and 👏 would mean a lot thanks

--

--

Chukwuyenum Opone

A software and game programmer with over 5 years in tech and gaming, driven by innovation and a passion for AI, and a way with words.