Allmine API

Balance socket entegrasyonu

~10 dkMobil / WebKararlı

Bakiye güncellemeleri WebSocket client

Bakiye Socket Gateway - React Native Entegrasyon Dokümantasyonu

Migration rehberi

Yapılandırma sırası için Migration şablonu. Yeni düzenlemelerde Amaç → Önkoşullar → Endpoint → Request/Response → Hata kodları → Client adımları → İlgili sayfalar bölümlerini tercih edin.

Genel Bakış

Bakiye Socket Gateway, kullanıcıların bakiyelerindeki değişiklikleri gerçek zamanlı olarak dinleyebilmelerini sağlar. Tüm bakiye işlemleri (yükleme, yayın gelirleri, hediye giderleri/gelirleri, superlike giderleri/gelirleri, kredi transferleri, yayın izleme ücretleri) socket üzerinden bildirilir.

Önemli Not: Hediye ve superlike gönderildiğinde, gönderen kullanıcının bakiyesi düşerken, yayıncıların (creator ve guest) bakiyelerine gelir eklenir. Gelir dağıtımı: Guest yoksa creator %80, guest varsa creator %40 ve guest %40 alır.

Kurulum

1. Socket.IO Client Kurulumu

npm install socket.io-client
# veya
yarn add socket.io-client

2. TypeScript Tipleri (Opsiyonel)

npm install --save-dev @types/socket.io-client

Temel Kullanım

1. Socket Bağlantısı Oluşturma

import io, { Socket } from 'socket.io-client';

// Socket bağlantısı oluştur
const socket = io('YOUR_API_BASE_URL/balance', {
  transports: ['websocket', 'polling'],
  reconnection: true,
  reconnectionDelay: 1000,
  reconnectionAttempts: 5,
});

2. Bağlantı Event'leri

// Bağlantı başarılı olduğunda
socket.on('connect', () => {
  console.log('Balance socket bağlantısı kuruldu');
});

// Bağlantı kesildiğinde
socket.on('disconnect', (reason) => {
  console.log('Balance socket bağlantısı kesildi:', reason);
});

// Bağlantı hatası
socket.on('connect_error', (error) => {
  console.error('Balance socket bağlantı hatası:', error);
});

3. Bakiye Dinlemeye Başlama (Subscribe)

// Token ile subscribe ol
socket.emit('subscribe', {
  token: 'YOUR_JWT_TOKEN', // Bearer token veya sadece token
});

// Subscribe başarılı
socket.on('subscribeSuccess', (data) => {
  console.log('Bakiye dinlemeye başlandı:', data);
  // data: { userId: string, room: string }
});

// Subscribe hatası
socket.on('subscribeError', (error) => {
  console.error('Subscribe hatası:', error);
  // error: { code: string, message: string }
});

4. Bakiye Değişikliklerini Dinleme

socket.on('balanceChange', (event: BalanceChangeEvent) => {
  console.log('Bakiye değişikliği:', event);
  
  // Event yapısı:
  // {
  //   userId: string,
  //   previousBalance: number,
  //   newBalance: number,
  //   changeAmount: number,
  //   transactionType: BalanceTransactionType,
  //   description: string,
  //   metadata?: BalanceChangeMetadata,
  //   timestamp: Date
  // }
  
  // UI'ı güncelle
  updateBalanceInUI(event.newBalance);
});

5. Bakiye Dinlemeyi Bırakma (Unsubscribe)

socket.emit('unsubscribe');

socket.on('unsubscribeSuccess', () => {
  console.log('Bakiye dinleme durduruldu');
});

TypeScript Tipleri

BalanceTransactionType Enum

export enum BalanceTransactionType {
  DEPOSIT = 'DEPOSIT',                    // Bakiye yükleme
  WITHDRAWAL = 'WITHDRAWAL',              // Para çekme
  GIFT_SENT = 'GIFT_SENT',                // Hediye gönderme (gönderen için)
  GIFT_RECEIVED = 'GIFT_RECEIVED',        // Hediye alma
  GIFT_REVENUE = 'GIFT_REVENUE',          // Hediye geliri (creator/guest için)
  STREAM_REVENUE = 'STREAM_REVENUE',      // Yayın geliri (creator/guest için)
  STREAM_CHARGE = 'STREAM_CHARGE',        // Yayın izleme ücreti
  SUPERLIKE = 'SUPERLIKE',                // Superlike gönderme (gönderen için)
  SUPERLIKE_REVENUE = 'SUPERLIKE_REVENUE', // Superlike geliri (creator/guest için)
  CREDIT_TRANSFER_SENT = 'CREDIT_TRANSFER_SENT',        // Kredi transferi (gönderen)
  CREDIT_TRANSFER_RECEIVED = 'CREDIT_TRANSFER_RECEIVED', // Kredi transferi (alan)
}

BalanceChangeEvent Interface

export interface BalanceChangeMetadata {
  streamId?: string;
  giftId?: string;
  transactionId?: string;
  creditPackageId?: string;
  recipientId?: string;
  senderId?: string;
  role?: 'creator' | 'guest';
  amount?: number;
  [key: string]: any;
}

export interface BalanceChangeEvent {
  userId: string;
  previousBalance: number;
  newBalance: number;
  changeAmount: number; // Pozitif veya negatif
  transactionType: BalanceTransactionType;
  description: string;
  metadata?: BalanceChangeMetadata;
  timestamp: Date;
}

React Native Hook Örneği

useBalanceSocket Hook

import { useEffect, useRef, useState } from 'react';
import io, { Socket } from 'socket.io-client';
import { BalanceChangeEvent, BalanceTransactionType } from './types';

interface UseBalanceSocketOptions {
  token: string;
  apiBaseUrl: string;
  onBalanceChange?: (event: BalanceChangeEvent) => void;
  onError?: (error: { code: string; message: string }) => void;
}

export const useBalanceSocket = ({
  token,
  apiBaseUrl,
  onBalanceChange,
  onError,
}: UseBalanceSocketOptions) => {
  const [isConnected, setIsConnected] = useState(false);
  const [isSubscribed, setIsSubscribed] = useState(false);
  const [currentBalance, setCurrentBalance] = useState<number | null>(null);
  const socketRef = useRef<Socket | null>(null);

  useEffect(() => {
    // Socket bağlantısı oluştur
    const socket = io(`${apiBaseUrl}/balance`, {
      transports: ['websocket', 'polling'],
      reconnection: true,
      reconnectionDelay: 1000,
      reconnectionAttempts: 5,
    });

    socketRef.current = socket;

    // Bağlantı event'leri
    socket.on('connect', () => {
      console.log('Balance socket bağlandı');
      setIsConnected(true);

      // Bağlantı kurulduğunda otomatik subscribe
      if (token) {
        socket.emit('subscribe', { token });
      }
    });

    socket.on('disconnect', () => {
      console.log('Balance socket bağlantısı kesildi');
      setIsConnected(false);
      setIsSubscribed(false);
    });

    socket.on('connect_error', (error) => {
      console.error('Balance socket bağlantı hatası:', error);
      setIsConnected(false);
    });

    // Subscribe event'leri
    socket.on('subscribeSuccess', (data) => {
      console.log('Bakiye dinlemeye başlandı:', data);
      setIsSubscribed(true);
    });

    socket.on('subscribeError', (error) => {
      console.error('Subscribe hatası:', error);
      setIsSubscribed(false);
      onError?.(error);
    });

    // Bakiye değişikliği
    socket.on('balanceChange', (event: BalanceChangeEvent) => {
      console.log('Bakiye değişikliği:', event);
      setCurrentBalance(event.newBalance);
      onBalanceChange?.(event);
    });

    // Unsubscribe event
    socket.on('unsubscribeSuccess', () => {
      console.log('Bakiye dinleme durduruldu');
      setIsSubscribed(false);
    });

    // Cleanup
    return () => {
      if (socket.connected) {
        socket.emit('unsubscribe');
      }
      socket.disconnect();
    };
  }, [apiBaseUrl, token, onBalanceChange, onError]);

  // Manuel subscribe/unsubscribe fonksiyonları
  const subscribe = (newToken?: string) => {
    if (socketRef.current?.connected) {
      socketRef.current.emit('subscribe', {
        token: newToken || token,
      });
    }
  };

  const unsubscribe = () => {
    if (socketRef.current?.connected) {
      socketRef.current.emit('unsubscribe');
    }
  };

  return {
    isConnected,
    isSubscribed,
    currentBalance,
    subscribe,
    unsubscribe,
  };
};

Kullanım Örneği

Component'te Kullanım

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { useBalanceSocket } from './hooks/useBalanceSocket';
import { BalanceChangeEvent, BalanceTransactionType } from './types';

const BalanceScreen: React.FC = () => {
  const token = 'YOUR_JWT_TOKEN'; // Auth context'ten alınabilir
  const apiBaseUrl = 'https://api.example.com';

  const handleBalanceChange = (event: BalanceChangeEvent) => {
    // Bakiye değişikliğine göre UI güncelle
    console.log('Yeni bakiye:', event.newBalance);
    console.log('Değişim:', event.changeAmount);
    console.log('İşlem tipi:', event.transactionType);
    console.log('Açıklama:', event.description);

    // İşlem tipine göre farklı işlemler yapılabilir
    switch (event.transactionType) {
      case BalanceTransactionType.DEPOSIT:
        showSuccessMessage('Bakiye yüklendi!');
        break;
      case BalanceTransactionType.STREAM_CHARGE:
        showInfoMessage('Yayın izleme ücreti kesildi');
        break;
      case BalanceTransactionType.STREAM_REVENUE:
        showSuccessMessage('Yayın geliri eklendi!');
        break;
      case BalanceTransactionType.GIFT_SENT:
        showInfoMessage('Hediye gönderildi');
        break;
      case BalanceTransactionType.GIFT_REVENUE:
        showSuccessMessage('Hediye geliri eklendi!');
        break;
      case BalanceTransactionType.SUPERLIKE:
        showInfoMessage('Superlike gönderildi');
        break;
      case BalanceTransactionType.SUPERLIKE_REVENUE:
        showSuccessMessage('Superlike geliri eklendi!');
        break;
      // ... diğer durumlar
    }
  };

  const { isConnected, isSubscribed, currentBalance } = useBalanceSocket({
    token,
    apiBaseUrl,
    onBalanceChange: handleBalanceChange,
    onError: (error) => {
      console.error('Socket hatası:', error);
      // Hata mesajı göster
    },
  });

  return (
    <View style={styles.container}>
      <Text style={styles.title}>Bakiye</Text>
      
      <View style={styles.statusContainer}>
        <Text>
          Durum: {isConnected ? 'Bağlı' : 'Bağlı Değil'} |{' '}
          {isSubscribed ? 'Dinleniyor' : 'Dinlenmiyor'}
        </Text>
      </View>

      <View style={styles.balanceContainer}>
        <Text style={styles.balanceLabel}>Mevcut Bakiye</Text>
        <Text style={styles.balanceAmount}>
          {currentBalance !== null ? `${currentBalance} kredi` : 'Yükleniyor...'}
        </Text>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  statusContainer: {
    marginBottom: 20,
  },
  balanceContainer: {
    backgroundColor: '#f5f5f5',
    padding: 20,
    borderRadius: 10,
  },
  balanceLabel: {
    fontSize: 14,
    color: '#666',
    marginBottom: 5,
  },
  balanceAmount: {
    fontSize: 32,
    fontWeight: 'bold',
    color: '#000',
  },
});

export default BalanceScreen;

Context API ile Global Kullanım

BalanceContext

import React, { createContext, useContext, ReactNode } from 'react';
import { useBalanceSocket } from '../hooks/useBalanceSocket';
import { BalanceChangeEvent } from '../types';

interface BalanceContextType {
  isConnected: boolean;
  isSubscribed: boolean;
  currentBalance: number | null;
  subscribe: (token?: string) => void;
  unsubscribe: () => void;
}

const BalanceContext = createContext<BalanceContextType | undefined>(undefined);

export const BalanceProvider: React.FC<{
  children: ReactNode;
  token: string;
  apiBaseUrl: string;
}> = ({ children, token, apiBaseUrl }) => {
  const handleBalanceChange = (event: BalanceChangeEvent) => {
    // Global balance change handler
    // Örneğin: Redux store'a kaydet, notification göster, vs.
    console.log('Global balance change:', event);
  };

  const balanceSocket = useBalanceSocket({
    token,
    apiBaseUrl,
    onBalanceChange: handleBalanceChange,
  });

  return (
    <BalanceContext.Provider value={balanceSocket}>
      {children}
    </BalanceContext.Provider>
  );
};

export const useBalance = () => {
  const context = useContext(BalanceContext);
  if (!context) {
    throw new Error('useBalance must be used within BalanceProvider');
  }
  return context;
};

App.tsx'te Kullanım

import React from 'react';
import { BalanceProvider } from './contexts/BalanceContext';
import { useAuth } from './hooks/useAuth';

const App: React.FC = () => {
  const { token } = useAuth();
  const apiBaseUrl = 'https://api.example.com';

  return (
    <BalanceProvider token={token} apiBaseUrl={apiBaseUrl}>
      {/* Diğer component'ler */}
    </BalanceProvider>
  );
};

Hata Yönetimi

Hata Kodları

export enum BalanceSocketErrorCode {
  AUTH_REQUIRED = 'AUTH_REQUIRED',
  TOKEN_INVALID = 'TOKEN_INVALID',
  USER_INVALID = 'USER_INVALID',
}

// Hata yönetimi örneği
socket.on('subscribeError', (error) => {
  switch (error.code) {
    case BalanceSocketErrorCode.AUTH_REQUIRED:
      // Token eksik, login sayfasına yönlendir
      navigateToLogin();
      break;
    case BalanceSocketErrorCode.TOKEN_INVALID:
      // Token geçersiz, token yenile
      refreshToken();
      break;
    case BalanceSocketErrorCode.USER_INVALID:
      // Kullanıcı geçersiz
      console.error('Geçersiz kullanıcı');
      break;
    default:
      console.error('Bilinmeyen hata:', error);
  }
});

Best Practices

1. Token Yönetimi

// Token değiştiğinde yeniden subscribe et
useEffect(() => {
  if (socketRef.current?.connected && token) {
    socketRef.current.emit('subscribe', { token });
  }
}, [token]);

2. Reconnection Handling

socket.on('reconnect', (attemptNumber) => {
  console.log('Yeniden bağlandı, deneme:', attemptNumber);
  // Token ile yeniden subscribe
  if (token) {
    socket.emit('subscribe', { token });
  }
});

3. Background/Foreground Handling

import { AppState } from 'react-native';

useEffect(() => {
  const subscription = AppState.addEventListener('change', (nextAppState) => {
    if (nextAppState === 'active') {
      // Uygulama foreground'a geldiğinde bağlantıyı kontrol et
      if (!socketRef.current?.connected) {
        socketRef.current?.connect();
      }
    } else if (nextAppState === 'background') {
      // Uygulama background'a gittiğinde unsubscribe et (opsiyonel)
      // socketRef.current?.emit('unsubscribe');
    }
  });

  return () => {
    subscription.remove();
  };
}, []);

4. Performance Optimizasyonu

// Balance değişikliklerini debounce et
import { debounce } from 'lodash';

const debouncedBalanceUpdate = debounce((event: BalanceChangeEvent) => {
  updateUI(event);
}, 300);

socket.on('balanceChange', debouncedBalanceUpdate);

5. Memory Leak Önleme

useEffect(() => {
  const socket = io(/* ... */);
  
  // Cleanup function
  return () => {
    socket.off('balanceChange'); // Event listener'ları temizle
    socket.off('subscribeSuccess');
    socket.off('subscribeError');
    socket.disconnect();
  };
}, []);

Event Örnekleri

1. Bakiye Yükleme (DEPOSIT)

{
  "userId": "507f1f77bcf86cd799439011",
  "previousBalance": 1000,
  "newBalance": 1200,
  "changeAmount": 200,
  "transactionType": "DEPOSIT",
  "description": "Bakiye yükleme - RevenueCat (APP_STORE)",
  "metadata": {
    "transactionId": "transaction_123",
    "creditPackageId": "package_456",
    "productId": "product_789"
  },
  "timestamp": "2024-01-16T15:30:00.000Z"
}

2. Yayın İzleme Ücreti (STREAM_CHARGE)

{
  "userId": "507f1f77bcf86cd799439011",
  "previousBalance": 1000,
  "newBalance": 990,
  "changeAmount": -10,
  "transactionType": "STREAM_CHARGE",
  "description": "Yayın izleme ücreti - Dakika bazlı ücret kesme",
  "metadata": {
    "streamId": "stream_123",
    "amount": 10
  },
  "timestamp": "2024-01-16T15:30:00.000Z"
}

3. Yayın Geliri (STREAM_REVENUE)

{
  "userId": "507f1f77bcf86cd799439011",
  "previousBalance": 1000,
  "newBalance": 1400,
  "changeAmount": 400,
  "transactionType": "STREAM_REVENUE",
  "description": "Yayın geliri - Creator (guest ile)",
  "metadata": {
    "streamId": "stream_123",
    "role": "creator",
    "amount": 400
  },
  "timestamp": "2024-01-16T15:30:00.000Z"
}

4. Hediye Gönderme (GIFT_SENT)

{
  "userId": "507f1f77bcf86cd799439011",
  "previousBalance": 1000,
  "newBalance": 900,
  "changeAmount": -100,
  "transactionType": "GIFT_SENT",
  "description": "Hediye gönderildi - 100 kredi",
  "metadata": {
    "giftId": "gift_123",
    "streamId": "stream_456",
    "giftTypeId": "gift_type_789"
  },
  "timestamp": "2024-01-16T15:30:00.000Z"
}

5. Superlike Gönderme (SUPERLIKE)

{
  "userId": "507f1f77bcf86cd799439011",
  "previousBalance": 1000,
  "newBalance": 990,
  "changeAmount": -10,
  "transactionType": "SUPERLIKE",
  "description": "Superlike gönderildi",
  "metadata": {
    "streamId": "stream_123"
  },
  "timestamp": "2024-01-16T15:30:00.000Z"
}

6. Hediye Geliri (GIFT_REVENUE) - Creator

{
  "userId": "507f1f77bcf86cd799439011",
  "previousBalance": 1000,
  "newBalance": 1080,
  "changeAmount": 80,
  "transactionType": "GIFT_REVENUE",
  "description": "Hediye geliri - Creator (guest yok)",
  "metadata": {
    "streamId": "stream_123",
    "role": "creator",
    "amount": 80
  },
  "timestamp": "2024-01-16T15:30:00.000Z"
}

7. Hediye Geliri (GIFT_REVENUE) - Guest

{
  "userId": "507f1f77bcf86cd799439011",
  "previousBalance": 500,
  "newBalance": 540,
  "changeAmount": 40,
  "transactionType": "GIFT_REVENUE",
  "description": "Hediye geliri - Guest",
  "metadata": {
    "streamId": "stream_123",
    "role": "guest",
    "amount": 40
  },
  "timestamp": "2024-01-16T15:30:00.000Z"
}

8. Superlike Geliri (SUPERLIKE_REVENUE) - Creator

{
  "userId": "507f1f77bcf86cd799439011",
  "previousBalance": 1000,
  "newBalance": 1008,
  "changeAmount": 8,
  "transactionType": "SUPERLIKE_REVENUE",
  "description": "Superlike geliri - Creator (guest yok)",
  "metadata": {
    "streamId": "stream_123",
    "role": "creator",
    "amount": 8
  },
  "timestamp": "2024-01-16T15:30:00.000Z"
}

9. Superlike Geliri (SUPERLIKE_REVENUE) - Guest

{
  "userId": "507f1f77bcf86cd799439011",
  "previousBalance": 500,
  "newBalance": 504,
  "changeAmount": 4,
  "transactionType": "SUPERLIKE_REVENUE",
  "description": "Superlike geliri - Guest",
  "metadata": {
    "streamId": "stream_123",
    "role": "guest",
    "amount": 4
  },
  "timestamp": "2024-01-16T15:30:00.000Z"
}

10. Kredi Transferi (CREDIT_TRANSFER)

{
  "userId": "507f1f77bcf86cd799439011",
  "previousBalance": 1000,
  "newBalance": 800,
  "changeAmount": -200,
  "transactionType": "CREDIT_TRANSFER_SENT",
  "description": "Kredi transferi - recipient_user_id kullanıcısına gönderildi",
  "metadata": {
    "recipientId": "recipient_user_id",
    "transferredAmount": 200
  },
  "timestamp": "2024-01-16T15:30:00.000Z"
}

Test Etme

Mock Socket Test

import { renderHook, waitFor } from '@testing-library/react-native';
import { useBalanceSocket } from './useBalanceSocket';

// Mock socket.io-client
jest.mock('socket.io-client', () => {
  const mockSocket = {
    on: jest.fn(),
    emit: jest.fn(),
    disconnect: jest.fn(),
    connected: true,
  };
  return jest.fn(() => mockSocket);
});

test('should subscribe on connect', async () => {
  const { result } = renderHook(() =>
    useBalanceSocket({
      token: 'test-token',
      apiBaseUrl: 'http://localhost',
    })
  );

  await waitFor(() => {
    expect(result.current.isSubscribed).toBe(true);
  });
});

Sorun Giderme

1. Bağlantı Kurulamıyor

  • API base URL'in doğru olduğundan emin olun
  • Network bağlantısını kontrol edin
  • CORS ayarlarını kontrol edin

2. Subscribe Başarısız

  • Token'ın geçerli olduğundan emin olun
  • Token formatını kontrol edin (Bearer prefix gerekebilir)
  • Token'ın süresi dolmamış olmalı

3. Balance Change Event'leri Gelmiyor

  • Subscribe işleminin başarılı olduğundan emin olun
  • Socket bağlantısının aktif olduğunu kontrol edin
  • Event listener'ların doğru kurulduğunu kontrol edin

API Endpoint

Socket Namespace: /balance

Base URL: YOUR_API_BASE_URL/balance

Desteklenen Event'ler

Client → Server

  • subscribe - Bakiye dinlemeye başla
  • unsubscribe - Bakiye dinlemeyi durdur

Server → Client

  • connect - Bağlantı kuruldu
  • disconnect - Bağlantı kesildi
  • connect_error - Bağlantı hatası
  • subscribeSuccess - Subscribe başarılı
  • subscribeError - Subscribe hatası
  • unsubscribeSuccess - Unsubscribe başarılı
  • balanceChange - Bakiye değişikliği

Güvenlik

  • Tüm bağlantılar JWT token ile authenticate edilir
  • Kullanıcılar sadece kendi bakiyelerini dinleyebilir
  • Token doğrulaması her subscribe işleminde yapılır

Gelir Dağıtım Mantığı

Hediye ve Superlike Gelirleri

Hediye veya superlike gönderildiğinde:

  1. Gönderen kullanıcı: Bakiyesi düşer (GIFT_SENT veya SUPERLIKE transaction type)
  2. Yayıncılar: Bakiyelerine gelir eklenir (GIFT_REVENUE veya SUPERLIKE_REVENUE transaction type)

Dağıtım Oranları:

  • Guest yoksa: Creator %80 alır
  • Guest varsa: Creator %40, Guest %40 alır
  • Kalan %20 platform'da kalır

Örnek Senaryo:

  • 100 kredilik hediye gönderildi
  • Guest yok: Creator 80 kredi alır
  • Guest var: Creator 40 kredi, Guest 40 kredi alır

Yayın Gelirleri

Yayın izleme ücretlerinden gelen gelirler:

  • Guest yoksa: Creator %80 alır
  • Guest varsa: Creator %40, Guest %40 alır
  • Kalan %20 platform'da kalır

Versiyon

API Versiyonu: 1.1.0
Son Güncelleme: 2024-01-16

Değişiklikler:

  • v1.1.0: Hediye ve superlike gelir dağıtımı eklendi (GIFT_REVENUE, SUPERLIKE_REVENUE)

On this page