iplaygames-sdk 1.0.2
IPlayGames SDK - High-level wrapper for casino game aggregation
License: MIT
Requires Python: >=3.8

# IPlayGames Python SDK

High-level Python SDK for the IPlayGames Game Aggregator API.

## Installation

```bash
pip install iplaygames-sdk
```

This will automatically install the `iplaygames-api` dependency.

## Quick Start

```python
from iplaygames import Client

client = Client(
    api_key='your-api-key',
    base_url='https://api.gamehub.com',  # Configurable!
)

# Get games
games = client.games().list(currency='USD')

# Start a game session
session = client.sessions().start(
    game_id=123,
    player_id='player_456',
    currency='USD',
    country_code='US',
    ip_address='192.168.1.1',
)

# Redirect player to game
game_url = session['game_url']
```

## Configuration

```python
client = Client(
    api_key='your-api-key',           # Required
    base_url='https://api.gamehub.com',  # Optional, defaults to https://api.gamehub.com
    timeout=30,                        # Optional, request timeout in seconds
    webhook_secret='your-secret',      # Optional, for webhook verification
)
```

## Available Flows

### Games

```python
# List games with filters
games = client.games().list(
    currency='USD',
    country='US',
    category='slots',
    search='bonanza',
)

# Get single game
game = client.games().get(123)

# Convenience methods
pragmatic_games = client.games().by_producer('Pragmatic Play')
live_games = client.games().by_category('live')
search_results = client.games().search('sweet bonanza')
player_games = client.games().for_player('USD', 'US')
```

### Sessions

```python
# Start a game session
session = client.sessions().start(
    game_id=123,
    player_id='player_456',
    currency='USD',
    country_code='US',
    ip_address=request.remote_addr,
    locale='en',
    device='mobile',
    return_url='https://casino.com/lobby',
)

# Get session status
status = client.sessions().status(session['session_id'])

# End session
client.sessions().end(session['session_id'])

# Start demo session
demo = client.sessions().start_demo(123)
```

### Jackpot

```python
# Get configuration
config = client.jackpot().get_configuration()

# Get all pools
pools = client.jackpot().get_pools()

# Get specific pool
daily_pool = client.jackpot().get_pool('daily')
weekly_pool = client.jackpot().get_pool('weekly')

# Get winners
winners = client.jackpot().get_winners('daily')

# Manage games
client.jackpot().add_games('daily', [1, 2, 3])
client.jackpot().remove_games('daily', [1])
```

### Promotions

```python
# List promotions
promotions = client.promotions().list(status='active')

# Get promotion details
promo = client.promotions().get(1)

# Get leaderboard
leaderboard = client.promotions().get_leaderboard(1)

# Opt-in player
client.promotions().opt_in(1, 'player_456', 'USD')
```

### Jackpot Widgets

```python
# 1. Register your domain
domain = client.jackpot_widget().register_domain('casino.example.com')
domain_token = domain['domain_token']

# 2. Create anonymous token (view-only)
token = client.jackpot_widget().create_anonymous_token(domain_token)

# 3. Create player token (can start game sessions)
player_token = client.jackpot_widget().create_player_token(
    domain_token,
    'player_456',
    'USD'
)

# 4. Get embed code for your frontend
embed_code = client.jackpot_widget().get_embed_code(token['token'], {
    'theme': 'dark',
    'container': 'jackpot-widget',
})
```

### Promotion Widgets

```python
# Same flow as jackpot widgets
domain = client.promotion_widget().register_domain('casino.example.com')
token = client.promotion_widget().create_player_token(
    domain['domain_token'],
    'player_456',
    'USD'
)
embed_code = client.promotion_widget().get_embed_code(token['token'])
```

### Multi-Session (TikTok-style Game Swiping)

```python
# Start multi-session
multi_session = client.multi_session().start(
    player_id='player_456',
    currency='USD',
    country_code='US',
    ip_address=request.remote_addr,
    device='mobile',
)

# Get iframe HTML to embed the swipe UI
iframe = client.multi_session().get_iframe(multi_session['swipe_url'], {
    'width': '100%',
    'height': '100vh',
})

# Get status
status = client.multi_session().status(multi_session['multi_session_id'])

# End when player leaves
client.multi_session().end(multi_session['multi_session_id'])
```

## Handling Webhooks

GameHub sends webhooks for transactions. Your casino must implement a webhook endpoint.

### Webhook Types

| Type | Description |
|------|-------------|
| `authenticate` | Verify player exists and get initial data |
| `balance_check` | Get current player balance |
| `bet` | Player placed a bet |
| `win` | Player won money |
| `rollback` | Undo a transaction |
| `reward` | Award from promotions/tournaments |

### Implementing Your Webhook Handler (Flask)

```python
from flask import Flask, request, jsonify
from iplaygames import Client, WebhookHandler
import os

app = Flask(__name__)

client = Client(
    api_key=os.environ['GAMEHUB_API_KEY'],
    webhook_secret=os.environ['GAMEHUB_WEBHOOK_SECRET'],
)

@app.route('/webhooks/gamehub', methods=['POST'])
def handle_webhook():
    payload = request.get_data(as_text=True)
    signature = request.headers.get('X-Signature')

    # Verify signature
    handler = client.webhooks()

    if not handler.verify(payload, signature):
        return jsonify({'error': 'Invalid signature'}), 401

    # Parse webhook
    webhook = handler.parse(payload)

    # Handle by type
    if webhook.type == WebhookHandler.TYPE_AUTHENTICATE:
        return handle_authenticate(webhook)
    elif webhook.type == WebhookHandler.TYPE_BALANCE_CHECK:
        return handle_balance_check(webhook)
    elif webhook.type == WebhookHandler.TYPE_BET:
        return handle_bet(webhook)
    elif webhook.type == WebhookHandler.TYPE_WIN:
        return handle_win(webhook)
    elif webhook.type == WebhookHandler.TYPE_ROLLBACK:
        return handle_rollback(webhook)
    elif webhook.type == WebhookHandler.TYPE_REWARD:
        return handle_reward(webhook)

    return jsonify({'error': 'Unknown webhook type'}), 400


def handle_authenticate(webhook):
    player = Player.query.get(webhook.player_id)

    if not player:
        return jsonify(client.webhooks().player_not_found_response())

    balance = player.get_balance(webhook.currency)

    return jsonify(client.webhooks().success_response(balance, {
        'player_name': player.name,
    }))


def handle_bet(webhook):
    player = Player.query.get(webhook.player_id)
    balance = player.get_balance(webhook.currency)
    bet_amount = webhook.get_amount_in_dollars()

    # Check funds
    if balance < bet_amount:
        return jsonify(client.webhooks().insufficient_funds_response(balance))

    # Check idempotency
    existing = Transaction.query.filter_by(external_id=webhook.transaction_id).first()
    if existing:
        return jsonify(client.webhooks().already_processed_response(balance))

    # Process bet
    player.debit(bet_amount, webhook.currency)
    Transaction.create(
        external_id=webhook.transaction_id,
        player_id=webhook.player_id,
        type='bet',
        amount=bet_amount,
        currency=webhook.currency,
    )
    db.session.commit()

    new_balance = player.get_balance(webhook.currency)
    return jsonify(client.webhooks().success_response(new_balance))


# ... implement other handlers similarly
```

### Django Example

```python
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.conf import settings
from iplaygames import Client, WebhookHandler
import json

client = Client(
    api_key=settings.GAMEHUB_API_KEY,
    webhook_secret=settings.GAMEHUB_WEBHOOK_SECRET,
)

@csrf_exempt
def webhook_handler(request):
    if request.method != 'POST':
        return JsonResponse({'error': 'Method not allowed'}, status=405)

    payload = request.body.decode('utf-8')
    signature = request.headers.get('X-Signature')

    handler = client.webhooks()

    if not handler.verify(payload, signature):
        return JsonResponse({'error': 'Invalid signature'}, status=401)

    webhook = handler.parse(payload)

    # Handle webhook by type...
    return JsonResponse(handler.success_response(balance))
```

## Webhook Payload Fields

### Common Fields (all webhook types)

```python
webhook.type         # 'bet', 'win', 'rollback', 'reward', 'authenticate', 'balance_check'
webhook.player_id    # Player's ID in your system
webhook.currency     # 'USD', 'EUR', etc.
webhook.game_id      # Game ID (nullable)
webhook.game_type    # 'slot', 'live', 'table', etc.
webhook.timestamp    # ISO 8601 timestamp
```

### Transaction Fields (bet, win, rollback, reward)

```python
webhook.transaction_id            # Unique transaction ID
webhook.amount                    # Amount in cents
webhook.get_amount_in_dollars()   # Amount in dollars
webhook.session_id                # Game session ID
webhook.round_id                  # Game round ID
```

### Freespin Fields

```python
webhook.is_freespin               # Is this a freespin round?
webhook.freespin_id               # Freespin campaign ID
webhook.freespin_total            # Total freespins awarded
webhook.freespins_remaining       # Remaining freespins
webhook.freespin_round_number     # Current spin number
webhook.freespin_total_winnings   # Cumulative winnings
```

## Error Handling

```python
from iplaygames import Client, ApiError

try:
    session = client.sessions().start(...)
except ApiError as e:
    print(f"API Error: {e.status} - {e.message}")
    print(f"Details: {e.data}")
except Exception as e:
    print(f"Unexpected error: {e}")
```

## Async Support

```python
import asyncio
from iplaygames import AsyncClient

async def main():
    client = AsyncClient(
        api_key='your-api-key',
        base_url='https://api.gamehub.com',
    )

    games = await client.games().list(currency='USD')

    # Run multiple requests concurrently
    results = await asyncio.gather(
        client.games().list(currency='USD'),
        client.jackpot().get_pools(),
        client.promotions().list(),
    )

asyncio.run(main())
```

## Running Tests

```bash
python -m pytest tests/
```

## License

MIT