SDK Installation & Setup Guide
Get up and running with StateSet SDKs in minutes. This guide covers everything from installation to advanced configuration across all supported languages and frameworks.🚀 Quick Start
Choose your language and get started in under 5 minutes:npm install stateset-node
📦 Supported SDKs
StateSet provides official SDKs for the following languages:Node.js / TypeScript
Python
Ruby
PHP
🎯 Which SDK Should I Use?
I'm building a modern web application
I'm building a modern web application
- ✅ Best for React, Next.js, Vue, Angular
- ✅ Full TypeScript support
- ✅ Excellent async/await handling
- ✅ Largest community and ecosystem
I'm building a data-heavy or AI/ML application
I'm building a data-heavy or AI/ML application
- ✅ Best for data processing and analytics
- ✅ Native pandas/numpy integration
- ✅ Perfect for Jupyter notebooks
- ✅ Great for FastAPI/Django backends
I'm working with an existing Rails/Sinatra app
I'm working with an existing Rails/Sinatra app
- ✅ Native Rails integration
- ✅ ActiveRecord-style syntax
- ✅ Sidekiq/Resque job support
- ✅ Convention over configuration
I'm building on WordPress or Laravel
I'm building on WordPress or Laravel
- ✅ PSR-compliant implementation
- ✅ WordPress plugin ready
- ✅ Laravel service provider included
- ✅ Composer autoloading
Node.js SDK
Prerequisites
Check Node.js Version
node --version
# Should output v16.0.0 or higher
stateset-node@legacyVerify Package Manager
npm --version # Should be 7.0.0+
# OR
yarn --version # Should be 1.22.0+
# OR
pnpm --version # Should be 6.0.0+
Optional: TypeScript Setup
tsc --version
# Should output Version 4.5.0 or higher
Installation
- npm
- yarn
- pnpm
# Install the main SDK
npm install stateset-node
# Install type definitions (if using TypeScript)
npm install --save-dev @types/node
# Install recommended utilities
npm install dotenv winston axios-retry
# Install the main SDK
yarn add stateset-node
# Install type definitions (if using TypeScript)
yarn add --dev @types/node
# Install recommended utilities
yarn add dotenv winston axios-retry
# Install the main SDK
pnpm add stateset-node
# Install type definitions (if using TypeScript)
pnpm add --save-dev @types/node
# Install recommended utilities
pnpm add dotenv winston axios-retry
Quick Setup
Create Project Structure
mkdir my-StateSet-app && cd my-StateSet-app
npm init -y
npm install stateset-node dotenv
# Create project structure
mkdir -p src/{services,utils,config}
touch .env .env.example .gitignore
Configure Environment Variables
# .env
STATESET_API_KEY=sk_live_your_actual_key_here
STATESET_ENVIRONMENT=production
STATESET_WEBHOOK_SECRET=whsec_3rK9pL7nQ2xS5mT8...
STATESET_LOG_LEVEL=info
STATESET_TIMEOUT=30000
STATESET_MAX_RETRIES=3
# .env.example (commit this to git)
STATESET_API_KEY=your_api_key_here
STATESET_ENVIRONMENT=sandbox
STATESET_WEBHOOK_SECRET=your_webhook_secret_here
STATESET_LOG_LEVEL=info
STATESET_TIMEOUT=30000
STATESET_MAX_RETRIES=3
# .gitignore
node_modules/
.env
.env.local
dist/
*.log
Create Configuration Module
// src/config/StateSet.js
import { StateSetClient } from 'stateset-node';
import dotenv from 'dotenv';
dotenv.config();
// Validate required environment variables
const requiredEnvVars = ['STATESET_API_KEY'];
for (const envVar of requiredEnvVars) {
if (!process.env[envVar]) {
throw new Error(`Missing required environment variable: ${envVar}`);
}
}
// Create and export configured client
export const StateSet = new StateSetClient({
apiKey: process.env.STATESET_API_KEY,
environment: process.env.STATESET_ENVIRONMENT || 'sandbox',
timeout: parseInt(process.env.STATESET_TIMEOUT || '30000'),
maxRetries: parseInt(process.env.STATESET_MAX_RETRIES || '3'),
telemetry: true // Help us improve the SDK
});
// Export configuration for reference
export const config = {
environment: process.env.STATESET_ENVIRONMENT || 'sandbox',
logLevel: process.env.STATESET_LOG_LEVEL || 'info',
webhookSecret: process.env.STATESET_WEBHOOK_SECRET
};
Basic Configuration
- JavaScript (ES6)
- TypeScript
- CommonJS
// app.js
import { StateSet, config } from './src/config/StateSet.js';
// Test the connection with proper error handling
async function testConnection() {
try {
const health = await StateSet.health.check();
logger.info('✅ Connected to StateSet:', {
status: health.status,
environment: config.environment,
version: health.version
});
// Test basic API access
const { data: orders } = await StateSet.orders.list({ limit: 1 });
logger.info('✅ API Access verified:', orders.length, 'orders found');
} catch (error) {
logger.error('❌ Connection failed:', {
message: error.message,
code: error.code,
statusCode: error.statusCode
});
process.exit(1);
}
}
// Run tests
testConnection();
// app.ts
import { StateSetClient, StateSetConfig, StateSetError } from 'stateset-node';
import dotenv from 'dotenv';
dotenv.config();
// Type-safe configuration
interface AppConfig {
StateSet: StateSetConfig;
app: {
environment: 'development' | 'staging' | 'production';
logLevel: 'debug' | 'info' | 'warn' | 'error';
};
}
const config: AppConfig = {
StateSet: {
apiKey: process.env.STATESET_API_KEY!,
environment: (process.env.STATESET_ENVIRONMENT as 'sandbox' | 'production') || 'sandbox',
timeout: parseInt(process.env.STATESET_TIMEOUT || '30000'),
maxRetries: parseInt(process.env.STATESET_MAX_RETRIES || '3'),
telemetry: true
},
app: {
environment: (process.env.NODE_ENV as any) || 'development',
logLevel: (process.env.LOG_LEVEL as any) || 'info'
}
};
const client = new StateSetClient(config.StateSet);
// Type-safe error handling
async function testConnection(): Promise<void> {
try {
const health = await client.health.check();
logger.info('✅ Connected to StateSet:', health);
// Test with proper types
const { data: orders } = await client.orders.list({
limit: 1,
status: 'pending'
});
logger.info(`✅ Found ${orders.length} orders`);
} catch (error) {
if (error instanceof StateSetError) {
logger.error('StateSet API Error:', {
message: error.message,
code: error.code,
statusCode: error.statusCode,
requestId: error.requestId
});
} else {
logger.error('Unexpected error:', error);
}
process.exit(1);
}
}
testConnection();
// app.js
const { StateSetClient } = require('stateset-node');
require('dotenv').config();
// Create client with error handling
let client;
try {
client = new StateSetClient({
apiKey: process.env.STATESET_API_KEY,
environment: process.env.STATESET_ENVIRONMENT || 'sandbox',
timeout: 30000,
maxRetries: 3
});
} catch (error) {
logger.error('Failed to initialize StateSet client:', error.message);
process.exit(1);
}
// Test the connection
client.health.check()
.then(health => {
logger.info('✅ Connected to StateSet:', health.status);
return client.orders.list({ limit: 1 });
})
.then(({ data: orders }) => {
logger.info('✅ API Access verified:', orders.length, 'orders found');
})
.catch(error => {
logger.error('❌ Connection failed:', error.message);
process.exit(1);
});
Advanced Configuration
- Custom HTTP Client
- Proxy Configuration
- Custom Logger
import { StateSetClient } from 'stateset-node';
import axios from 'axios';
import axiosRetry from 'axios-retry';
// Create custom axios instance
const httpClient = axios.create({
timeout: 60000,
headers: {
'User-Agent': 'MyApp/1.0.0'
}
});
// Configure retry logic
axiosRetry(httpClient, {
retries: 5,
retryDelay: axiosRetry.exponentialDelay,
retryCondition: (error) => {
return axiosRetry.isNetworkOrIdempotentRequestError(error) ||
error.response?.status === 429; // Retry on rate limit
}
});
// Use custom HTTP client
const client = new StateSetClient({
apiKey: process.env.STATESET_API_KEY,
httpClient: httpClient
});
import { StateSetClient } from 'stateset-node';
import { HttpsProxyAgent } from 'https-proxy-agent';
const proxyAgent = new HttpsProxyAgent('http://proxy.company.com:8080');
const client = new StateSetClient({
apiKey: process.env.STATESET_API_KEY,
httpAgent: proxyAgent,
httpsAgent: proxyAgent
});
import { StateSetClient } from 'stateset-node';
import winston from 'winston';
// Create Winston logger
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new winston.transports.Console({
format: winston.format.simple()
}),
new winston.transports.File({
filename: 'StateSet-errors.log',
level: 'error'
})
]
});
const client = new StateSetClient({
apiKey: process.env.STATESET_API_KEY,
logger: logger
});
Framework Integration
- Express.js
- Next.js
- NestJS
// server.js
import express from 'express';
import { StateSetClient } from 'stateset-node';
import morgan from 'morgan';
import helmet from 'helmet';
const app = express();
// Security and logging middleware
app.use(helmet());
app.use(morgan('combined'));
app.use(express.json());
// Initialize StateSet client
const StateSet = new StateSetClient({
apiKey: process.env.STATESET_API_KEY,
environment: process.env.NODE_ENV === 'production' ? 'production' : 'sandbox'
});
// StateSet middleware
app.use((req, res, next) => {
req.StateSet = StateSet;
next();
});
// Error handling middleware
const asyncHandler = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
// Routes
app.get('/api/orders', asyncHandler(async (req, res) => {
const { page = 1, limit = 20, status } = req.query;
const { data: orders, pagination } = await req.StateSet.orders.list({
page: parseInt(page),
limit: parseInt(limit),
status: status
});
res.json({
success: true,
data: orders,
pagination
});
}));
app.post('/api/orders', asyncHandler(async (req, res) => {
const order = await req.StateSet.orders.create({
customer_id: req.body.customer_id,
items: req.body.items,
shipping_address: req.body.shipping_address
});
res.status(201).json({
success: true,
data: order
});
}));
// Global error handler
app.use((err, req, res, next) => {
logger.error('Error:', err);
if (err.name === 'StateSetError') {
return res.status(err.statusCode || 400).json({
success: false,
error: {
message: err.message,
code: err.code,
requestId: err.requestId
}
});
}
res.status(500).json({
success: false,
error: {
message: 'Internal server error'
}
});
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
logger.info(`Server running on port ${PORT}`);
});
// lib/StateSet.ts
import { StateSetClient } from 'stateset-node';
// Singleton pattern for client-side usage
let client: StateSetClient;
export function getStateSetClient() {
if (!client) {
client = new StateSetClient({
apiKey: process.env.STATESET_API_KEY!,
environment: process.env.NEXT_PUBLIC_STATESET_ENV as 'sandbox' | 'production' || 'sandbox'
});
}
return client;
}
// Server-side client (for API routes)
export const serverClient = new StateSetClient({
apiKey: process.env.STATESET_API_KEY!,
environment: process.env.STATESET_ENVIRONMENT as 'sandbox' | 'production' || 'sandbox'
});
// Type-safe API wrapper
export async function apiWrapper<T>(
apiCall: () => Promise<T>
): Promise<{ data?: T; error?: string }> {
try {
const data = await apiCall();
return { data };
} catch (error: any) {
logger.error('StateSet API Error:', error);
return {
error: error.message || 'An unexpected error occurred'
};
}
}
// pages/api/orders/index.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { serverClient, apiWrapper } from '../../../lib/StateSet';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
// Enable CORS if needed
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
if (req.method === 'OPTIONS') {
return res.status(200).end();
}
switch (req.method) {
case 'GET': {
const { page = '1', limit = '20', status } = req.query;
const result = await apiWrapper(() =>
serverClient.orders.list({
page: parseInt(page as string),
limit: parseInt(limit as string),
status: status as string
})
);
if (result.error) {
return res.status(400).json({ error: result.error });
}
return res.status(200).json(result.data);
}
case 'POST': {
const result = await apiWrapper(() =>
serverClient.orders.create(req.body)
);
if (result.error) {
return res.status(400).json({ error: result.error });
}
return res.status(201).json(result.data);
}
default:
res.setHeader('Allow', ['GET', 'POST']);
return res.status(405).json({
error: `Method ${req.method} Not Allowed`
});
}
}
// hooks/useStateSet.ts (React Hook)
import { useState, useEffect } from 'react';
import { getStateSetClient } from '../lib/StateSet';
export function useOrders(options = {}) {
const [orders, setOrders] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchOrders = async () => {
try {
const client = getStateSetClient();
const { data } = await client.orders.list(options);
setOrders(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchOrders();
}, []);
return { orders, loading, error };
}
// StateSet/StateSet.module.ts
import { Module, DynamicModule, Global } from '@nestjs/common';
import { StateSetClient } from 'stateset-node';
import { STATESET_CLIENT } from './StateSet.constants';
import { StateSetService } from './StateSet.service';
export interface StateSetModuleOptions {
apiKey: string;
environment?: 'sandbox' | 'production';
isGlobal?: boolean;
}
@Module({})
export class StateSetModule {
static forRoot(options: StateSetModuleOptions): DynamicModule {
const providers = [
{
provide: STATESET_CLIENT,
useFactory: () => new StateSetClient({
apiKey: options.apiKey,
environment: options.environment || 'sandbox'
})
},
StateSetService
];
return {
module: StateSetModule,
global: options.isGlobal ?? true,
providers,
exports: [STATESET_CLIENT, StateSetService]
};
}
static forRootAsync(options: {
useFactory: (...args: any[]) => Promise<StateSetModuleOptions> | StateSetModuleOptions;
inject?: any[];
isGlobal?: boolean;
}): DynamicModule {
const providers = [
{
provide: STATESET_CLIENT,
useFactory: async (...args: any[]) => {
const config = await options.useFactory(...args);
return new StateSetClient({
apiKey: config.apiKey,
environment: config.environment || 'sandbox'
});
},
inject: options.inject || []
},
StateSetService
];
return {
module: StateSetModule,
global: options.isGlobal ?? true,
providers,
exports: [STATESET_CLIENT, StateSetService]
};
}
}
// StateSet/StateSet.service.ts
import { Injectable, Inject, Logger } from '@nestjs/common';
import { StateSetClient } from 'stateset-node';
import { STATESET_CLIENT } from './StateSet.constants';
@Injectable()
export class StateSetService {
private readonly logger = new Logger(StateSetService.name);
constructor(
@Inject(STATESET_CLIENT) private readonly client: StateSetClient
) {}
async createOrder(data: any) {
try {
this.logger.log('Creating order', { customerId: data.customer_id });
const order = await this.client.orders.create(data);
this.logger.log('Order created successfully', { orderId: order.id });
return order;
} catch (error) {
this.logger.error('Failed to create order', error);
throw error;
}
}
async getOrders(filters = {}) {
return this.client.orders.list(filters);
}
async getOrder(id: string) {
return this.client.orders.retrieve(id);
}
async updateOrder(id: string, data: any) {
return this.client.orders.update(id, data);
}
}
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { StateSetModule } from './StateSet/StateSet.module';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true
}),
StateSetModule.forRootAsync({
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
apiKey: config.get('STATESET_API_KEY'),
environment: config.get('STATESET_ENVIRONMENT', 'sandbox')
})
})
]
})
export class AppModule {}
// orders/orders.controller.ts
import {
Controller,
Get,
Post,
Body,
Param,
Query,
UseInterceptors,
ClassSerializerInterceptor
} from '@nestjs/common';
import { StateSetService } from '../StateSet/StateSet.service';
@Controller('orders')
@UseInterceptors(ClassSerializerInterceptor)
export class OrdersController {
constructor(private readonly StateSetService: StateSetService) {}
@Get()
async getOrders(
@Query('page') page = 1,
@Query('limit') limit = 20,
@Query('status') status?: string
) {
return this.StateSetService.getOrders({
page: Number(page),
limit: Number(limit),
status
});
}
@Get(':id')
async getOrder(@Param('id') id: string) {
return this.StateSetService.getOrder(id);
}
@Post()
async createOrder(@Body() createOrderDto: any) {
return this.StateSetService.createOrder(createOrderDto);
}
}
Python SDK
Prerequisites
Check Python Version
python --version
# Should output Python 3.8.0 or higher
# Check pip version
pip --version
# Should be version 20.0 or higher
stateset-python==1.xSet Up Virtual Environment
# Create virtual environment
python -m venv StateSet-env
# Activate on macOS/Linux
source StateSet-env/bin/activate
# Activate on Windows
StateSet-env\Scripts\activate
Installation
- pip
- poetry
- conda
- requirements.txt
# Install StateSet SDK with all optional dependencies
pip install "stateset-python[all]"
# Or install core SDK only
pip install stateset-python
# Install specific extras
pip install "stateset-python[async]" # For async support
pip install "stateset-python[pandas]" # For data analysis
# Add StateSet SDK
poetry add stateset-python
# Add with extras
poetry add "stateset-python[async,pandas]"
# Add development dependencies
poetry add --group dev pytest pytest-asyncio black mypy
# Install via pip in conda environment
pip install stateset-python
# Install conda dependencies first
conda install pandas numpy requests
pip install "stateset-python[async]"
# requirements.txt
stateset-python>=2.0.0
python-dotenv>=0.19.0
requests>=2.28.0
# Optional: async support
httpx>=0.23.0
# Optional: data analysis
pandas>=1.3.0
numpy>=1.21.0
pip install -r requirements.txt
Quick Setup
Create Project Structure
# Create project structure
mkdir my-StateSet-app && cd my-StateSet-app
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install stateset-python python-dotenv
# Create project files
mkdir -p src/{services,utils,config}
touch .env .env.example .gitignore README.md
touch src/__init__.py src/config/__init__.py
Configure Environment
# .env
STATESET_API_KEY=sk_live_your_actual_key_here
STATESET_ENVIRONMENT=production
STATESET_WEBHOOK_SECRET=whsec_3rK9pL7nQ2xS5mT8...
STATESET_LOG_LEVEL=INFO
STATESET_TIMEOUT=30
STATESET_MAX_RETRIES=3
# .gitignore
__pycache__/
*.py[cod]
*$py.class
.env
.venv/
venv/
.pytest_cache/
.mypy_cache/
*.log
Create Configuration Module
# src/config/StateSet.py
import os
import logging
from typing import Optional
from StateSet import StateSetClient, AsyncStateSetClient
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Configure logging
logging.basicConfig(
level=getattr(logging, os.getenv('STATESET_LOG_LEVEL', 'INFO')),
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# Validate required environment variables
REQUIRED_ENV_VARS = ['STATESET_API_KEY']
for var in REQUIRED_ENV_VARS:
if not os.getenv(var):
raise ValueError(f"Missing required environment variable: {var}")
# Configuration
class Config:
API_KEY = os.getenv('STATESET_API_KEY')
ENVIRONMENT = os.getenv('STATESET_ENVIRONMENT', 'sandbox')
WEBHOOK_SECRET = os.getenv('STATESET_WEBHOOK_SECRET')
TIMEOUT = int(os.getenv('STATESET_TIMEOUT', '30'))
MAX_RETRIES = int(os.getenv('STATESET_MAX_RETRIES', '3'))
# Create client instances
def get_client() -> StateSetClient:
"""Get configured StateSet client"""
return StateSetClient(
api_key=Config.API_KEY,
environment=Config.ENVIRONMENT,
timeout=Config.TIMEOUT,
max_retries=Config.MAX_RETRIES
)
def get_async_client() -> AsyncStateSetClient:
"""Get configured async StateSet client"""
return AsyncStateSetClient(
api_key=Config.API_KEY,
environment=Config.ENVIRONMENT,
timeout=Config.TIMEOUT,
max_retries=Config.MAX_RETRIES
)
# Export configured client
client = get_client()
async_client = get_async_client()
Basic Configuration
- Synchronous
- Asynchronous
- Type Hints
# app.py
from src.config.StateSet import client, Config, logger
def test_connection():
"""Test StateSet connection and basic API access"""
try:
# Test health check
health = client.health.check()
logger.info(f"✅ Connected to StateSet: {health}")
# Test API access
orders = client.orders.list(limit=1)
logger.info(f"✅ API Access verified: {len(orders.data)} orders found")
# Display configuration
logger.info(f"Environment: {Config.ENVIRONMENT}")
logger.info(f"Timeout: {Config.TIMEOUT}s")
return True
except Exception as e:
logger.error(f"❌ Connection failed: {str(e)}")
if hasattr(e, 'status_code'):
logger.error(f"Status Code: {e.status_code}")
if hasattr(e, 'request_id'):
logger.error(f"Request ID: {e.request_id}")
return False
if __name__ == "__main__":
if test_connection():
logger.info("StateSet SDK is properly configured!")
else:
exit(1)
# app_async.py
import asyncio
from src.config.StateSet import async_client, Config, logger
async def test_connection():
"""Test StateSet async connection"""
try:
# Test health check
health = await async_client.health.check()
logger.info(f"✅ Connected to StateSet: {health}")
# Test parallel API calls
tasks = [
async_client.orders.list(limit=5),
async_client.customers.list(limit=5),
async_client.products.list(limit=5)
]
results = await asyncio.gather(*tasks, return_exceptions=True)
for i, result in enumerate(results):
if isinstance(result, Exception):
logger.error(f"Task {i} failed: {result}")
else:
logger.info(f"Task {i} succeeded: {len(result.data)} items")
return True
except Exception as e:
logger.error(f"❌ Connection failed: {str(e)}")
return False
finally:
await async_client.close()
if __name__ == "__main__":
asyncio.run(test_connection())
# app_typed.py
from typing import List, Optional, Dict, Any
from dataclasses import dataclass
from src.config.StateSet import client, logger
from StateSet.models import Order, Customer, Product
@dataclass
class OrderService:
"""Type-safe order service"""
client: Any
def get_orders(
self,
status: Optional[str] = None,
limit: int = 20
) -> List[Order]:
"""Get orders with optional filtering"""
try:
response = self.client.orders.list(
status=status,
limit=limit
)
return response.data
except Exception as e:
logger.error(f"Failed to fetch orders: {e}")
raise
def create_order(self, order_data: Dict[str, Any]) -> Order:
"""Create a new order"""
try:
return self.client.orders.create(**order_data)
except Exception as e:
logger.error(f"Failed to create order: {e}")
raise
def get_order_summary(self, order_id: str) -> Dict[str, Any]:
"""Get comprehensive order summary"""
order = self.client.orders.retrieve(order_id)
customer = self.client.customers.retrieve(order.customer_id)
return {
"order": order,
"customer": customer,
"total_amount": order.total,
"status": order.status
}
# Usage
service = OrderService(client=client)
orders = service.get_orders(status="pending", limit=10)
print(f"Found {len(orders)} pending orders")
Framework Integration
- Flask
- FastAPI
- Django
# app.py
from flask import Flask, jsonify, request, g
from functools import wraps
from src.config.StateSet import client, Config, logger
import traceback
app = Flask(__name__)
app.config['JSON_SORT_KEYS'] = False
# Error handling decorator
def handle_errors(f):
@wraps(f)
def decorated_function(*args, **kwargs):
try:
return f(*args, **kwargs)
except Exception as e:
logger.error(f"Error in {f.__name__}: {str(e)}")
logger.error(traceback.format_exc())
if hasattr(e, 'status_code'):
status_code = e.status_code
else:
status_code = 500
return jsonify({
'success': False,
'error': {
'message': str(e),
'type': type(e).__name__
}
}), status_code
return decorated_function
# Routes
@app.route('/api/orders', methods=['GET'])
@handle_errors
def get_orders():
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 20))
status = request.args.get('status')
orders = client.orders.list(
page=page,
limit=limit,
status=status
)
return jsonify({
'success': True,
'data': orders.data,
'pagination': {
'page': orders.page,
'limit': orders.limit,
'total': orders.total
}
})
@app.route('/api/orders', methods=['POST'])
@handle_errors
def create_order():
data = request.get_json()
# Validate required fields
required_fields = ['customer_id', 'items']
for field in required_fields:
if field not in data:
return jsonify({
'success': False,
'error': f'Missing required field: {field}'
}), 400
order = client.orders.create(**data)
return jsonify({
'success': True,
'data': order
}), 201
@app.route('/api/orders/<order_id>', methods=['GET'])
@handle_errors
def get_order(order_id):
order = client.orders.retrieve(order_id)
return jsonify({
'success': True,
'data': order
})
@app.route('/api/health', methods=['GET'])
def health_check():
try:
health = client.health.check()
return jsonify({
'status': 'healthy',
'StateSet': health,
'environment': Config.ENVIRONMENT
})
except Exception as e:
return jsonify({
'status': 'unhealthy',
'error': str(e)
}), 503
if __name__ == '__main__':
app.run(debug=True, port=5000)
# main.py
from fastapi import FastAPI, HTTPException, Query, Depends
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field
from typing import Optional, List, Dict, Any
from src.config.StateSet import async_client, Config, logger
import uvicorn
app = FastAPI(title="StateSet API", version="1.0.0")
# Pydantic models
class OrderCreate(BaseModel):
customer_id: str
items: List[Dict[str, Any]]
shipping_address: Dict[str, str]
metadata: Optional[Dict[str, Any]] = None
class OrderResponse(BaseModel):
id: str
customer_id: str
status: str
total: float
created_at: str
class PaginatedResponse(BaseModel):
data: List[Any]
pagination: Dict[str, int]
# Dependency for StateSet client
async def get_StateSet_client():
return async_client
# Exception handler
@app.exception_handler(Exception)
async def StateSet_exception_handler(request, exc):
logger.error(f"StateSet error: {exc}")
if hasattr(exc, 'status_code'):
status_code = exc.status_code
else:
status_code = 500
return JSONResponse(
status_code=status_code,
content={
"detail": str(exc),
"type": type(exc).__name__
}
)
# Routes
@app.get("/api/orders", response_model=PaginatedResponse)
async def get_orders(
page: int = Query(1, ge=1),
limit: int = Query(20, ge=1, le=100),
status: Optional[str] = None,
client = Depends(get_StateSet_client)
):
"""Get paginated list of orders"""
response = await client.orders.list(
page=page,
limit=limit,
status=status
)
return {
"data": response.data,
"pagination": {
"page": response.page,
"limit": response.limit,
"total": response.total
}
}
@app.post("/api/orders", response_model=OrderResponse, status_code=201)
async def create_order(
order: OrderCreate,
client = Depends(get_StateSet_client)
):
"""Create a new order"""
result = await client.orders.create(**order.dict())
return result
@app.get("/api/orders/{order_id}", response_model=OrderResponse)
async def get_order(
order_id: str,
client = Depends(get_StateSet_client)
):
"""Get order by ID"""
order = await client.orders.retrieve(order_id)
if not order:
raise HTTPException(status_code=404, detail="Order not found")
return order
@app.get("/api/health")
async def health_check(client = Depends(get_StateSet_client)):
"""Health check endpoint"""
try:
health = await client.health.check()
return {
"status": "healthy",
"StateSet": health,
"environment": Config.ENVIRONMENT
}
except Exception as e:
raise HTTPException(status_code=503, detail=str(e))
# Startup/shutdown events
@app.on_event("startup")
async def startup_event():
logger.info("Starting up StateSet FastAPI app")
@app.on_event("shutdown")
async def shutdown_event():
logger.info("Shutting down StateSet FastAPI app")
await async_client.close()
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
# settings.py
import os
from dotenv import load_dotenv
load_dotenv()
# StateSet Configuration
STATESET_API_KEY = os.getenv('STATESET_API_KEY')
STATESET_ENVIRONMENT = os.getenv('STATESET_ENVIRONMENT', 'sandbox')
STATESET_WEBHOOK_SECRET = os.getenv('STATESET_WEBHOOK_SECRET')
# StateSet_client.py
from django.conf import settings
from StateSet import StateSetClient
import logging
logger = logging.getLogger(__name__)
class StateSetService:
"""Singleton StateSet service for Django"""
_instance = None
_client = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
@property
def client(self):
if self._client is None:
self._client = StateSetClient(
api_key=settings.STATESET_API_KEY,
environment=settings.STATESET_ENVIRONMENT
)
return self._client
# views.py
from django.http import JsonResponse
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from .StateSet_client import StateSetService
import json
StateSet_service = StateSetService()
@method_decorator(csrf_exempt, name='dispatch')
class OrdersView(View):
def get(self, request):
try:
page = int(request.GET.get('page', 1))
limit = int(request.GET.get('limit', 20))
status = request.GET.get('status')
response = StateSet_service.client.orders.list(
page=page,
limit=limit,
status=status
)
return JsonResponse({
'success': True,
'data': response.data,
'pagination': {
'page': response.page,
'limit': response.limit,
'total': response.total
}
})
except Exception as e:
return JsonResponse({
'success': False,
'error': str(e)
}, status=500)
def post(self, request):
try:
data = json.loads(request.body)
order = StateSet_service.client.orders.create(**data)
return JsonResponse({
'success': True,
'data': order
}, status=201)
except Exception as e:
return JsonResponse({
'success': False,
'error': str(e)
}, status=400)
class OrderDetailView(View):
def get(self, request, order_id):
try:
order = StateSet_service.client.orders.retrieve(order_id)
return JsonResponse({
'success': True,
'data': order
})
except Exception as e:
status_code = 404 if 'not found' in str(e).lower() else 500
return JsonResponse({
'success': False,
'error': str(e)
}, status=status_code)
# urls.py
from django.urls import path
from .views import OrdersView, OrderDetailView
urlpatterns = [
path('api/orders/', OrdersView.as_view(), name='orders'),
path('api/orders/<str:order_id>/', OrderDetailView.as_view(), name='order-detail'),
]
Ruby SDK
Prerequisites
Check Ruby Version
ruby --version
# Should output ruby 2.7.0 or higher
# Check gem version
gem --version
# Should be version 3.0.0 or higher
# Check bundler
bundle --version
# Should be version 2.0.0 or higher
stateset-ruby v1.xInstall Dependencies
# Install bundler if not present
gem install bundler
# Optional: Install development tools
gem install pry rubocop rspec
Installation
- Gemfile
- Direct Install
- From Source
# Gemfile
source 'https://rubygems.org'
# StateSet SDK
gem 'stateset-ruby', '~> 2.0'
# Environment management
gem 'dotenv-rails', groups: [:development, :test] # For Rails
# OR
gem 'dotenv', groups: [:development, :test] # For non-Rails
# Optional dependencies
gem 'faraday-retry' # Advanced retry logic
gem 'concurrent-ruby' # For async operations
bundle install
# Install directly via gem
gem install stateset-ruby
# Install with specific version
gem install stateset-ruby -v '2.0.0'
# Install development version
gem install stateset-ruby --pre
# Clone and build from source
git clone https://github.com/StateSet/stateset-ruby.git
cd stateset-ruby
bundle install
rake build
gem install pkg/stateset-ruby-*.gem
Quick Setup
Create Project Structure
# Create new Ruby project
mkdir my-StateSet-app && cd my-StateSet-app
# Initialize bundler
bundle init
# Create project structure
mkdir -p lib/StateSet config spec
touch .env .env.example .gitignore README.md
touch config/StateSet.rb lib/StateSet_service.rb
Configure Environment
# .env
STATESET_API_KEY=sk_live_your_actual_key_here
STATESET_ENVIRONMENT=production
STATESET_WEBHOOK_SECRET=whsec_3rK9pL7nQ2xS5mT8...
STATESET_LOG_LEVEL=info
STATESET_TIMEOUT=30
STATESET_MAX_RETRIES=3
# .gitignore
*.gem
*.rbc
/.config
/coverage/
/spec/reports/
/tmp/
.env
.env.local
*.log
.DS_Store
Create Configuration
# config/StateSet.rb
require 'StateSet'
require 'dotenv/load'
require 'logger'
# Configure logger
StateSet.logger = Logger.new(STDOUT)
StateSet.logger.level = ENV.fetch('STATESET_LOG_LEVEL', 'INFO')
# Validate required environment variables
required_vars = ['STATESET_API_KEY']
missing_vars = required_vars.select { |var| ENV[var].nil? || ENV[var].empty? }
unless missing_vars.empty?
raise "Missing required environment variables: #{missing_vars.join(', ')}"
end
# Configure StateSet client
StateSet.configure do |config|
config.api_key = ENV['STATESET_API_KEY']
config.environment = ENV.fetch('STATESET_ENVIRONMENT', 'sandbox')
config.timeout = ENV.fetch('STATESET_TIMEOUT', '30').to_i
config.max_retries = ENV.fetch('STATESET_MAX_RETRIES', '3').to_i
config.webhook_secret = ENV['STATESET_WEBHOOK_SECRET']
# Optional: Configure custom Faraday middleware
config.configure_faraday do |faraday|
faraday.request :retry, max: 3
faraday.use :instrumentation, name: 'StateSet.request'
end
end
# Test connection helper
module StateSetHelper
def self.test_connection
begin
health = StateSet::Health.check
StateSet.logger.info "✅ Connected to StateSet: #{health['status']}"
# Test API access
orders = StateSet::Order.list(limit: 1)
StateSet.logger.info "✅ API Access verified: #{orders.data.length} orders found"
true
rescue => e
StateSet.logger.error "❌ Connection failed: #{e.message}"
false
end
end
end
Basic Configuration
- Simple Usage
- Service Class
- Async Operations
# app.rb
require_relative 'config/StateSet'
# Test the connection
if StateSetHelper.test_connection
puts "StateSet is properly configured!"
else
exit 1
end
$client = getStateSetClient()
begin
# Create a customer
customer = StateSet::Customer.create(
email: '[email protected]',
name: 'Test Customer',
metadata: { source: 'ruby_sdk' }
)
puts "Created customer: #{customer.id}"
# Create an order
order = StateSet::Order.create(
customer_id: customer.id,
items: [
{ product_id: 'prod_123', quantity: 2, price: 1999 }
],
shipping_address: {
line1: '123 Main St',
city: 'San Francisco',
state: 'CA',
postal_code: '94105',
country: 'US'
}
)
puts "Created order: #{order.id}"
# List recent orders
orders = StateSet::Order.list(limit: 10, status: 'pending')
orders.data.each do |order|
puts "Order #{order.id}: #{order.status} - $#{order.total / 100.0}"
end
rescue StateSet::Error => e
puts "StateSet Error: #{e.message}"
puts "Error Code: #{e.code}" if e.code
puts "Request ID: #{e.request_id}" if e.request_id
end
# lib/StateSet_service.rb
require_relative '../config/StateSet'
class StateSetService
class << self
def create_order(customer_id:, items:, shipping_address:, metadata: {})
StateSet::Order.create(
customer_id: customer_id,
items: items,
shipping_address: shipping_address,
metadata: metadata
)
rescue StateSet::Error => e
handle_error(e)
end
def get_order(order_id)
StateSet::Order.retrieve(order_id)
rescue StateSet::NotFoundError => e
nil
rescue StateSet::Error => e
handle_error(e)
end
def list_orders(filters = {})
defaults = { limit: 20, page: 1 }
StateSet::Order.list(defaults.merge(filters))
rescue StateSet::Error => e
handle_error(e)
end
def update_order_status(order_id, status)
StateSet::Order.update(order_id, status: status)
rescue StateSet::Error => e
handle_error(e)
end
private
def handle_error(error)
StateSet.logger.error "StateSet API Error: #{error.message}"
StateSet.logger.error "Error Type: #{error.class}"
StateSet.logger.error "Request ID: #{error.request_id}" if error.request_id
# Re-raise or return error response based on your needs
raise error
end
end
end
# lib/async_StateSet.rb
require 'concurrent'
require_relative '../config/StateSet'
class AsyncStateSet
def self.batch_create_orders(orders_data)
promises = orders_data.map do |order_data|
Concurrent::Promise.execute do
StateSet::Order.create(order_data)
end
end
# Wait for all promises to complete
results = promises.map(&:value!)
# Handle results
successful = results.select { |r| !r.is_a?(Exception) }
failed = results.select { |r| r.is_a?(Exception) }
{
successful: successful,
failed: failed,
total: orders_data.length
}
end
def self.parallel_fetch(resource_ids)
promises = resource_ids.map do |id|
Concurrent::Promise.execute do
{
id: id,
order: StateSet::Order.retrieve(id),
customer: StateSet::Customer.retrieve(
StateSet::Order.retrieve(id).customer_id
)
}
end
end
promises.map(&:value!)
end
end
# Usage example
if __FILE__ == $0
# Create multiple orders in parallel
orders_data = 5.times.map do |i|
{
customer_id: 'cus_123',
items: [{ product_id: "prod_#{i}", quantity: 1, price: 1000 }],
shipping_address: { line1: "#{i} Main St", city: 'SF', state: 'CA' }
}
end
results = AsyncStateSet.batch_create_orders(orders_data)
puts "Created #{results[:successful].length} orders successfully"
puts "Failed: #{results[:failed].length}"
end
Framework Integration
- Rails
- Sinatra
# config/initializers/StateSet.rb
require 'StateSet'
Rails.application.config.before_initialize do
StateSet.configure do |config|
config.api_key = Rails.application.credentials.StateSet[:api_key]
config.environment = Rails.env.production? ? 'production' : 'sandbox'
config.logger = Rails.logger
config.timeout = 30
# Add Rails-specific middleware
config.configure_faraday do |faraday|
faraday.request :retry, max: 3
faraday.use :instrumentation, name: 'StateSet.request'
end
end
end
# app/services/StateSet_service.rb
class StateSetService
include ActiveSupport::Rescuable
rescue_from StateSet::Error, with: :handle_StateSet_error
def create_order(user, items, shipping_address)
order = StateSet::Order.create(
customer_id: user.StateSet_customer_id,
items: items.map(&:to_StateSet_item),
shipping_address: shipping_address.to_StateSet_format,
metadata: {
user_id: user.id,
created_via: 'rails_app'
}
)
# Store order reference
user.orders.create!(
StateSet_order_id: order.id,
total: order.total,
status: order.status
)
order
end
def sync_order_status(local_order)
StateSet_order = StateSet::Order.retrieve(local_order.StateSet_order_id)
if local_order.status != StateSet_order.status
local_order.update!(
status: StateSet_order.status,
updated_at: Time.at(StateSet_order.updated)
)
end
StateSet_order
end
private
def handle_StateSet_error(error)
Rails.logger.error "StateSet Error: #{error.message}"
Bugsnag.notify(error) if defined?(Bugsnag)
case error
when StateSet::RateLimitError
raise CustomErrors::RateLimitExceeded
when StateSet::AuthenticationError
raise CustomErrors::ServiceUnavailable
else
raise error
end
end
end
# app/controllers/api/orders_controller.rb
class Api::OrdersController < ApplicationController
before_action :authenticate_user!
def index
orders = current_user.orders.includes(:items)
# Optionally sync with StateSet
if params[:sync] == 'true'
service = StateSetService.new
orders.each { |order| service.sync_order_status(order) }
end
render json: orders
end
def create
service = StateSetService.new
order = service.create_order(
current_user,
order_params[:items],
order_params[:shipping_address]
)
render json: order, status: :created
rescue StateSet::Error => e
render json: { error: e.message }, status: :unprocessable_entity
end
private
def order_params
params.require(:order).permit(
items: [:product_id, :quantity, :price],
shipping_address: [:line1, :line2, :city, :state, :postal_code, :country]
)
end
end
# app/jobs/StateSet_webhook_job.rb
class StateSetWebhookJob < ApplicationJob
def perform(event)
case event['type']
when 'order.updated'
handle_order_update(event['data'])
when 'order.completed'
handle_order_completion(event['data'])
end
end
private
def handle_order_update(order_data)
order = Order.find_by(StateSet_order_id: order_data['id'])
order&.update!(status: order_data['status'])
end
def handle_order_completion(order_data)
order = Order.find_by(StateSet_order_id: order_data['id'])
OrderMailer.completion_notification(order).deliver_later if order
end
end
# app.rb
require 'sinatra'
require 'sinatra/json'
require_relative 'config/StateSet'
class StateSetApp < Sinatra::Base
configure do
set :show_exceptions, false
set :raise_errors, false
end
# Error handling
error StateSet::Error do |e|
status e.http_status || 400
json error: { message: e.message, type: e.class.name }
end
error do |e|
status 500
json error: { message: 'Internal server error' }
end
# Middleware for common headers
before do
content_type :json
headers['Access-Control-Allow-Origin'] = '*'
end
# Routes
get '/api/orders' do
page = params[:page]&.to_i || 1
limit = params[:limit]&.to_i || 20
status = params[:status]
orders = StateSet::Order.list(
page: page,
limit: limit,
status: status
)
json(
data: orders.data,
pagination: {
page: orders.page,
limit: orders.limit,
total: orders.total
}
)
end
post '/api/orders' do
data = JSON.parse(request.body.read)
order = StateSet::Order.create(
customer_id: data['customer_id'],
items: data['items'],
shipping_address: data['shipping_address']
)
status 201
json data: order
end
get '/api/orders/:id' do |id|
order = StateSet::Order.retrieve(id)
json data: order
end
patch '/api/orders/:id' do |id|
data = JSON.parse(request.body.read)
order = StateSet::Order.update(id, data)
json data: order
end
get '/api/health' do
health = StateSet::Health.check
json(
status: 'healthy',
StateSet: health,
environment: ENV['STATESET_ENVIRONMENT']
)
rescue => e
status 503
json(
status: 'unhealthy',
error: e.message
)
end
# Webhook endpoint
post '/webhooks/StateSet' do
payload = request.body.read
sig_header = request.env['HTTP_STATESET_SIGNATURE']
begin
event = StateSet::Webhook.construct_event(
payload,
sig_header,
ENV['STATESET_WEBHOOK_SECRET']
)
# Process webhook event asynchronously
# WebhookProcessor.perform_async(event)
status 200
json received: true
rescue StateSet::SignatureVerificationError => e
status 400
json error: 'Invalid signature'
end
end
run! if app_file == $0
end
PHP SDK
Prerequisites
Check PHP Version
php --version
# Should output PHP 8.0.0 or higher
# Check Composer version
composer --version
# Should be version 2.0.0 or higher
# Check required extensions
php -m | grep -E "(curl|json|mbstring)"
# Should show curl, json, and mbstring
stateset-php v1.xInstall Required Extensions
# Ubuntu/Debian
sudo apt-get install php8.1-curl php8.1-json php8.1-mbstring
# macOS with Homebrew
brew install [email protected]
# Windows with XAMPP/WAMP
# Extensions are usually pre-installed
Installation
- Composer
- Manual Installation
- Laravel
# Install via Composer
composer require stateset/stateset-php
# Install with specific version
composer require stateset/stateset-php:^2.0
# Install development dependencies
composer require --dev phpunit/phpunit phpstan/phpstan
# Download the latest release
wget https://github.com/StateSet/stateset-php/archive/v2.0.0.zip
unzip v2.0.0.zip
# Include in your project
require_once 'path/to/stateset-php/init.php';
# Install via Composer
composer require stateset/stateset-php
# Publish configuration
php artisan vendor:publish --provider="StateSet\Laravel\StateSetServiceProvider"
# Add to .env
STATESET_API_KEY=your_api_key_here
STATESET_ENVIRONMENT=sandbox
Quick Setup
Create Project Structure
# Create new PHP project
mkdir my-StateSet-app && cd my-StateSet-app
composer init
# Create project structure
mkdir -p src/{Services,Controllers,Models} config tests
touch .env .env.example .gitignore README.md
touch config/StateSet.php src/Services/StateSetService.php
Configure Environment
# .env
STATESET_API_KEY=sk_live_your_actual_key_here
STATESET_ENVIRONMENT=production
STATESET_WEBHOOK_SECRET=whsec_3rK9pL7nQ2xS5mT8...
STATESET_LOG_LEVEL=info
STATESET_TIMEOUT=30
STATESET_MAX_RETRIES=3
# .gitignore
/vendor/
/.env
/.env.local
/composer.lock
/.phpunit.result.cache
/logs/
*.log
.DS_Store
Create Configuration
<?php
// config/StateSet.php
require_once __DIR__ . '/../vendor/autoload.php';
use StateSet\StateSetClient;
use StateSet\Exception\StateSetException;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
// Load environment variables
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');
$dotenv->load();
// Validate required environment variables
$dotenv->required(['STATESET_API_KEY'])->notEmpty();
// Configure logger
$logger = new Logger('StateSet');
$logger->pushHandler(new StreamHandler(
__DIR__ . '/../logs/StateSet.log',
$_ENV['STATESET_LOG_LEVEL'] ?? Logger::INFO
));
// Configure StateSet client
function getStateSetClient(): StateSetClient {
global $logger;
return new StateSetClient([
'api_key' => $_ENV['STATESET_API_KEY'],
'environment' => $_ENV['STATESET_ENVIRONMENT'] ?? 'sandbox',
'timeout' => (int)($_ENV['STATESET_TIMEOUT'] ?? 30),
'max_retries' => (int)($_ENV['STATESET_MAX_RETRIES'] ?? 3),
'logger' => $logger,
'webhook_secret' => $_ENV['STATESET_WEBHOOK_SECRET'] ?? null
]);
}
// Test connection helper
function testStateSetConnection(): bool {
global $logger;
try {
$client = getStateSetClient();
$health = $client->health->check();
$logger->info("✅ Connected to StateSet: " . json_encode($health));
// Test API access
$orders = $client->orders->list(['limit' => 1]);
$logger->info("✅ API Access verified: " . count($orders->data) . " orders found");
return true;
} catch (StateSetException $e) {
$logger->error("❌ Connection failed: " . $e->getMessage());
return false;
}
}
Basic Configuration
- Simple Usage
- Service Class
- Async Operations
<?php
// app.php
require_once __DIR__ . '/config/StateSet.php';
use StateSet\Exception\StateSetException;
// Test the connection
if (!testStateSetConnection()) {
exit(1);
}
$client = getStateSetClient();
try {
// Create a customer
$customer = $client->customers->create([
'email' => '[email protected]',
'name' => 'Test Customer',
'metadata' => ['source' => 'php_sdk']
]);
echo "Created customer: {$customer->id}\n";
// Create an order
$order = $client->orders->create([
'customer_id' => $customer->id,
'items' => [
[
'product_id' => 'prod_123',
'quantity' => 2,
'price' => 1999
]
],
'shipping_address' => [
'line1' => '123 Main St',
'city' => 'San Francisco',
'state' => 'CA',
'postal_code' => '94105',
'country' => 'US'
]
]);
echo "Created order: {$order->id}\n";
// List recent orders
$orders = $client->orders->list([
'limit' => 10,
'status' => 'pending'
]);
foreach ($orders->data as $order) {
$total = number_format($order->total / 100, 2);
echo "Order {$order->id}: {$order->status} - \${$total}\n";
}
} catch (StateSetException $e) {
echo "StateSet Error: {$e->getMessage()}\n";
if ($e->getCode()) {
echo "Error Code: {$e->getCode()}\n";
}
if ($e->getRequestId()) {
echo "Request ID: {$e->getRequestId()}\n";
}
}
<?php
// src/Services/StateSetService.php
namespace App\Services;
use StateSet\StateSetClient;
use StateSet\Exception\StateSetException;
use Psr\Log\LoggerInterface;
class StateSetService
{
private StateSetClient $client;
private LoggerInterface $logger;
public function __construct(StateSetClient $client, LoggerInterface $logger)
{
$this->client = $client;
$this->logger = $logger;
}
public function createOrder(array $data): ?object
{
try {
$order = $this->client->orders->create($data);
$this->logger->info("Order created", ['order_id' => $order->id]);
return $order;
} catch (StateSetException $e) {
$this->handleError($e);
return null;
}
}
public function getOrder(string $orderId): ?object
{
try {
return $this->client->orders->retrieve($orderId);
} catch (StateSetException $e) {
if ($e->getHttpStatus() === 404) {
return null;
}
$this->handleError($e);
throw $e;
}
}
public function listOrders(array $filters = []): array
{
$defaults = ['limit' => 20, 'page' => 1];
$params = array_merge($defaults, $filters);
try {
$response = $this->client->orders->list($params);
return [
'data' => $response->data,
'pagination' => [
'page' => $response->page,
'limit' => $response->limit,
'total' => $response->total
]
];
} catch (StateSetException $e) {
$this->handleError($e);
throw $e;
}
}
public function updateOrderStatus(string $orderId, string $status): ?object
{
try {
return $this->client->orders->update($orderId, ['status' => $status]);
} catch (StateSetException $e) {
$this->handleError($e);
throw $e;
}
}
private function handleError(StateSetException $e): void
{
$this->logger->error("StateSet API Error", [
'message' => $e->getMessage(),
'code' => $e->getCode(),
'request_id' => $e->getRequestId(),
'http_status' => $e->getHttpStatus()
]);
}
}
<?php
// src/Services/AsyncStateSet.php
namespace App\Services;
use React\Promise\Promise;
use React\EventLoop\Loop;
use StateSet\StateSetClient;
class AsyncStateSet
{
private StateSetClient $client;
public function __construct(StateSetClient $client)
{
$this->client = $client;
}
public function batchCreateOrders(array $ordersData)
{
$promises = [];
$results = ['successful' => [], 'failed' => []];
foreach ($ordersData as $index => $orderData) {
$promises[$index] = new Promise(function ($resolve, $reject) use ($orderData) {
try {
$order = $this->client->orders->create($orderData);
$resolve($order);
} catch (\Exception $e) {
$reject($e);
}
});
}
// Wait for all promises
foreach ($promises as $index => $promise) {
$promise->then(
function ($order) use (&$results) {
$results['successful'][] = $order;
},
function ($error) use (&$results) {
$results['failed'][] = $error;
}
);
}
// Run event loop
Loop::run();
return $results;
}
public function parallelFetch(array $orderIds)
{
$results = [];
$promises = array_map(function ($orderId) {
return new Promise(function ($resolve) use ($orderId) {
try {
$order = $this->client->orders->retrieve($orderId);
$customer = $this->client->customers->retrieve($order->customer_id);
$resolve([
'order' => $order,
'customer' => $customer
]);
} catch (\Exception $e) {
$resolve(['error' => $e->getMessage()]);
}
});
}, $orderIds);
foreach ($promises as $index => $promise) {
$promise->then(function ($result) use (&$results, $orderIds, $index) {
$results[$orderIds[$index]] = $result;
});
}
Loop::run();
return $results;
}
}
Framework Integration
- Laravel
- WordPress
<?php
// config/StateSet.php
return [
'api_key' => env('STATESET_API_KEY'),
'environment' => env('STATESET_ENVIRONMENT', 'sandbox'),
'webhook_secret' => env('STATESET_WEBHOOK_SECRET'),
'timeout' => env('STATESET_TIMEOUT', 30),
'max_retries' => env('STATESET_MAX_RETRIES', 3),
];
<?php
// app/Providers/StateSetServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use StateSet\StateSetClient;
class StateSetServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton(StateSetClient::class, function ($app) {
return new StateSetClient([
'api_key' => config('StateSet.api_key'),
'environment' => config('StateSet.environment'),
'timeout' => config('StateSet.timeout'),
'max_retries' => config('StateSet.max_retries'),
]);
});
}
public function boot()
{
$this->publishes([
__DIR__.'/../../config/StateSet.php' => config_path('StateSet.php'),
], 'config');
}
}
<?php
// app/Services/StateSetService.php
namespace App\Services;
use StateSet\StateSetClient;
use StateSet\Exception\StateSetException;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
class StateSetService
{
protected StateSetClient $client;
public function __construct(StateSetClient $client)
{
$this->client = $client;
}
public function createOrder(array $data)
{
try {
$order = $this->client->orders->create($data);
// Cache the order
Cache::put("order_{$order->id}", $order, now()->addMinutes(5));
// Dispatch event
event(new \App\Events\OrderCreated($order));
return $order;
} catch (StateSetException $e) {
Log::error('StateSet order creation failed', [
'error' => $e->getMessage(),
'data' => $data
]);
throw $e;
}
}
public function getOrder(string $orderId)
{
// Check cache first
return Cache::remember("order_{$orderId}", 300, function () use ($orderId) {
return $this->client->orders->retrieve($orderId);
});
}
}
<?php
// app/Http/Controllers/Api/OrderController.php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Services\StateSetService;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
class OrderController extends Controller
{
protected StateSetService $StateSetService;
public function __construct(StateSetService $StateSetService)
{
$this->StateSetService = $StateSetService;
}
public function index(Request $request): JsonResponse
{
$validated = $request->validate([
'page' => 'integer|min:1',
'limit' => 'integer|min:1|max:100',
'status' => 'string|in:pending,processing,completed,cancelled'
]);
try {
$orders = $this->StateSetService->listOrders($validated);
return response()->json($orders);
} catch (\Exception $e) {
return response()->json([
'error' => $e->getMessage()
], 500);
}
}
public function store(Request $request): JsonResponse
{
$validated = $request->validate([
'customer_id' => 'required|string',
'items' => 'required|array',
'items.*.product_id' => 'required|string',
'items.*.quantity' => 'required|integer|min:1',
'shipping_address' => 'required|array'
]);
try {
$order = $this->StateSetService->createOrder($validated);
return response()->json($order, 201);
} catch (\Exception $e) {
return response()->json([
'error' => $e->getMessage()
], 422);
}
}
public function show(string $id): JsonResponse
{
try {
$order = $this->StateSetService->getOrder($id);
return response()->json($order);
} catch (\Exception $e) {
return response()->json([
'error' => 'Order not found'
], 404);
}
}
}
<?php
// app/Http/Controllers/WebhookController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use StateSet\Webhook;
use Illuminate\Support\Facades\Log;
class WebhookController extends Controller
{
public function handle(Request $request)
{
$payload = $request->getContent();
$sigHeader = $request->header('StateSet-Signature');
$secret = config('StateSet.webhook_secret');
try {
$event = Webhook::constructEvent($payload, $sigHeader, $secret);
// Process webhook event
switch ($event->type) {
case 'order.updated':
$this->handleOrderUpdate($event->data);
break;
case 'order.completed':
$this->handleOrderCompleted($event->data);
break;
default:
Log::info('Unhandled webhook event type: ' . $event->type);
}
return response()->json(['received' => true]);
} catch (\Exception $e) {
Log::error('Webhook error: ' . $e->getMessage());
return response()->json(['error' => 'Invalid signature'], 400);
}
}
private function handleOrderUpdate($orderData)
{
// Update local database
\App\Models\Order::where('StateSet_id', $orderData->id)
->update(['status' => $orderData->status]);
}
private function handleOrderCompleted($orderData)
{
// Send notification email
\App\Jobs\SendOrderCompletionEmail::dispatch($orderData->id);
}
}
<?php
/**
* Plugin Name: StateSet Integration
* Description: Integrate StateSet API with WordPress
* Version: 1.0.0
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Define plugin constants
define('STATESET_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('STATESET_PLUGIN_URL', plugin_dir_url(__FILE__));
// Load Composer autoloader
require_once STATESET_PLUGIN_DIR . 'vendor/autoload.php';
// Main plugin class
class StateSetPlugin {
private static $instance = null;
private $client;
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
$this->initClient();
$this->setupHooks();
}
private function initClient() {
$this->client = new \StateSet\StateSetClient([
'api_key' => get_option('StateSet_api_key'),
'environment' => get_option('StateSet_environment', 'sandbox'),
'timeout' => 30
]);
}
private function setupHooks() {
// Admin menu
add_action('admin_menu', [$this, 'addAdminMenu']);
// REST API endpoints
add_action('rest_api_init', [$this, 'registerRestRoutes']);
// WooCommerce integration
if (class_exists('WooCommerce')) {
add_action('woocommerce_order_status_changed', [$this, 'syncOrderStatus'], 10, 3);
add_action('woocommerce_new_order', [$this, 'createStateSetOrder']);
}
}
public function addAdminMenu() {
add_menu_page(
'StateSet Settings',
'StateSet',
'manage_options',
'StateSet-settings',
[$this, 'settingsPage'],
'dashicons-cloud',
30
);
}
public function settingsPage() {
?>
<div class="wrap">
<h1>StateSet Settings</h1>
<form method="post" action="options.php">
<?php settings_fields('StateSet_settings'); ?>
<table class="form-table">
<tr>
<th scope="row">API key</th>
<td>
<input type="text"
name="StateSet_api_key"
value="<?php echo esc_attr(get_option('StateSet_api_key')); ?>"
class="regular-text" />
</td>
</tr>
<tr>
<th scope="row">Environment</th>
<td>
<select name="StateSet_environment">
<option value="sandbox" <?php selected(get_option('StateSet_environment'), 'sandbox'); ?>>
Sandbox
</option>
<option value="production" <?php selected(get_option('StateSet_environment'), 'production'); ?>>
Production
</option>
</select>
</td>
</tr>
</table>
<?php submit_button(); ?>
</form>
</div>
<?php
}
public function registerRestRoutes() {
register_rest_route('StateSet/v1', '/orders', [
'methods' => 'GET',
'callback' => [$this, 'getOrders'],
'permission_callback' => function() {
return current_user_can('manage_options');
}
]);
register_rest_route('StateSet/v1', '/orders/(?P<id>[a-zA-Z0-9_-]+)', [
'methods' => 'GET',
'callback' => [$this, 'getOrder'],
'permission_callback' => function() {
return current_user_can('manage_options');
}
]);
}
public function getOrders($request) {
try {
$params = [
'limit' => $request->get_param('limit') ?: 20,
'page' => $request->get_param('page') ?: 1
];
$orders = $this->client->orders->list($params);
return new WP_REST_Response([
'success' => true,
'data' => $orders->data,
'pagination' => [
'total' => $orders->total,
'page' => $orders->page,
'limit' => $orders->limit
]
], 200);
} catch (\Exception $e) {
return new WP_Error('StateSet_error', $e->getMessage(), ['status' => 500]);
}
}
public function createStateSetOrder($orderId) {
$order = wc_get_order($orderId);
if (!$order) {
return;
}
try {
$StateSetOrder = $this->client->orders->create([
'customer_id' => $this->getOrCreateCustomer($order),
'items' => $this->formatOrderItems($order),
'shipping_address' => $this->formatAddress($order->get_address('shipping')),
'metadata' => [
'woocommerce_order_id' => $orderId,
'source' => 'wordpress_plugin'
]
]);
// Save StateSet order ID
update_post_meta($orderId, '_StateSet_order_id', $StateSetOrder->id);
} catch (\Exception $e) {
error_log('StateSet order creation failed: ' . $e->getMessage());
}
}
private function getOrCreateCustomer($order) {
$email = $order->get_billing_email();
try {
// Try to find existing customer
$customers = $this->client->customers->list(['email' => $email]);
if (!empty($customers->data)) {
return $customers->data[0]->id;
}
// Create new customer
$customer = $this->client->customers->create([
'email' => $email,
'name' => $order->get_billing_first_name() . ' ' . $order->get_billing_last_name(),
'phone' => $order->get_billing_phone()
]);
return $customer->id;
} catch (\Exception $e) {
throw $e;
}
}
private function formatOrderItems($order) {
$items = [];
foreach ($order->get_items() as $item) {
$product = $item->get_product();
$items[] = [
'product_id' => 'prod_' . $product->get_id(),
'name' => $product->get_name(),
'quantity' => $item->get_quantity(),
'price' => (int)($product->get_price() * 100) // Convert to cents
];
}
return $items;
}
private function formatAddress($address) {
return [
'line1' => $address['address_1'],
'line2' => $address['address_2'],
'city' => $address['city'],
'state' => $address['state'],
'postal_code' => $address['postcode'],
'country' => $address['country']
];
}
}
// Initialize plugin
add_action('plugins_loaded', function() {
StateSetPlugin::getInstance();
});
// Register settings
add_action('admin_init', function() {
register_setting('StateSet_settings', 'StateSet_api_key');
register_setting('StateSet_settings', 'StateSet_environment');
register_setting('StateSet_settings', 'StateSet_webhook_secret');
});
Environment Configuration
Development Environment
# .env.development
STATESET_API_KEY=sk_test_your_actual_key_here
STATESET_ENVIRONMENT=sandbox
STATESET_WEBHOOK_SECRET=whsec_test_3rK9pL7nQ2xS5mT8...
LOG_LEVEL=debug
Production Environment
# .env.production
STATESET_API_KEY=sk_live_your_actual_key_here
STATESET_ENVIRONMENT=production
STATESET_WEBHOOK_SECRET=whsec_prod_3rK9pL7nQ2xS5mT8...
LOG_LEVEL=info
Docker Configuration
# Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- STATESET_API_KEY=${STATESET_API_KEY}
- STATESET_ENVIRONMENT=production
volumes:
- .:/app
- /app/node_modules
Troubleshooting
Common Issues
Authentication Errors
Authentication Errors
401 Unauthorized errorsSolutions:# 1. Verify API key format
echo $STATESET_API_KEY
# Should start with sk_test_ or sk_live_
# 2. Check environment variables are loaded
node -e "logger.info(process.env.STATESET_API_KEY);"
# 3. Verify API key permissions in dashboard
# https://app.stateset.com/settings/api-keys
# 4. Ensure correct environment
# sandbox keys: sk_test_*
# production keys: sk_live_*
// Ensure environment variables are loaded
import dotenv from 'dotenv';
dotenv.config({ path: '.env.local' }); // Try different path
// Debug API key
logger.info('API key exists:', !!process.env.STATESET_API_KEY);
logger.info('API key prefix:', process.env.STATESET_API_KEY?.substring(0, 7););
Network Timeouts
Network Timeouts
ETIMEDOUT errorsSolutions:// 1. Increase timeout
const client = new StateSetClient({
apiKey: process.env.STATESET_API_KEY,
timeout: 60000, // 60 seconds
maxRetries: 5
});
// 2. Check network connectivity
// Run: curl https://api.stateset.com/v1/health
// 3. Configure proxy if behind firewall
import { HttpsProxyAgent } from 'https-proxy-agent';
const client = new StateSetClient({
apiKey: process.env.STATESET_API_KEY,
httpAgent: new HttpsProxyAgent(process.env.HTTP_PROXY)
});
// 4. Implement custom retry logic
async function retryableRequest(fn, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (error) {
if (i === retries - 1) throw error;
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
}
}
}
Module Import Errors
Module Import Errors
# 1. Clear cache and reinstall
rm -rf node_modules package-lock.json
npm cache clean --force
npm install
# 2. Check Node.js version
node --version # Must be 16.0.0+
# 3. Verify installation
npm list stateset-node
# 4. Fix TypeScript paths
# tsconfig.json
{
"compilerOptions": {
"moduleResolution": "node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
}
}
# 5. For ESM issues, add to package.json:
{
"type": "module"
}
Rate Limiting
Rate Limiting
429 Too Many Requests errorsSolutions:// 1. Implement exponential backoff
import pRetry from 'p-retry';
const createOrderWithRetry = async (data) => {
return pRetry(
() => client.orders.create(data),
{
retries: 5,
onFailedAttempt: error => {
if (error.statusCode === 429) {
logger.info(`Rate limited, retrying in ${error.retriesLeft * 1000}ms`);
}
}
}
);
};
// 2. Implement request queuing
import PQueue from 'p-queue';
const queue = new PQueue({
concurrency: 2, // Max 2 concurrent requests
interval: 1000, // Per second
intervalCap: 10 // Max 10 requests per second
});
// 3. Cache frequently accessed data
const cache = new Map();
async function getCachedOrder(id) {
if (cache.has(id)) {
return cache.get(id);
}
const order = await client.orders.retrieve(id);
cache.set(id, order);
return order;
}
Webhook Signature Verification
Webhook Signature Verification
// 1. Ensure raw body is used
app.use('/webhooks/StateSet', express.raw({ type: 'application/json' }));
// 2. Correct signature verification
app.post('/webhooks/StateSet', (req, res) => {
const sig = req.headers['StateSet-signature'];
const body = req.body; // Must be raw Buffer
try {
const event = StateSet.webhooks.constructEvent(
body,
sig,
process.env.STATESET_WEBHOOK_SECRET
);
// Process event
res.json({ received: true });
} catch (err) {
logger.error('Webhook Error:', err.message);
res.status(400).send(`Webhook Error: ${err.message}`);
}
});
// 3. Debug webhook secret
logger.info('Webhook secret exists:', !!process.env.STATESET_WEBHOOK_SECRET);
logger.info('Secret prefix:', process.env.STATESET_WEBHOOK_SECRET?.substring(0, 7););
Platform-Specific Issues
- Vercel
- AWS Lambda
- Docker
// vercel.json
{
"functions": {
"api/webhooks/StateSet.js": {
"maxDuration": 30
}
},
"headers": [
{
"source": "/api/(.*)",
"headers": [
{ "key": "Access-Control-Allow-Origin", "value": "*" }
]
}
]
}
// api/webhooks/StateSet.js
export const config = {
api: {
bodyParser: false, // Important for raw body
},
};
// serverless.yml
functions:
webhook:
handler: handler.webhook
events:
- http:
path: webhooks/StateSet
method: post
cors: true
environment:
STATESET_API_KEY: ${env:STATESET_API_KEY}
STATESET_WEBHOOK_SECRET: ${env:STATESET_WEBHOOK_SECRET}
// handler.js
const getRawBody = require('raw-body');
exports.webhook = async (event) => {
const body = Buffer.from(event.body, 'base64');
const sig = event.headers['StateSet-signature'];
try {
const webhookEvent = StateSet.webhooks.constructEvent(
body,
sig,
process.env.STATESET_WEBHOOK_SECRET
);
return {
statusCode: 200,
body: JSON.stringify({ received: true })
};
} catch (err) {
return {
statusCode: 400,
body: JSON.stringify({ error: err.message })
};
}
};
# Dockerfile debugging
FROM node:18-alpine
# Install debugging tools
RUN apk add --no-cache curl openssl
WORKDIR /app
# Copy and install dependencies
COPY package*.json ./
RUN npm ci --only=production
# Copy application
COPY . .
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node -e "require('./health-check.js')" || exit 1
EXPOSE 3000
CMD ["node", "index.js"]
# docker-compose.yml with debugging
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- STATESET_API_KEY=${STATESET_API_KEY}
- DEBUG=StateSet:*
volumes:
- ./logs:/app/logs
command: >
sh -c "
echo 'Testing StateSet connection...' &&
node test-connection.js &&
node index.js
"
Testing Your Integration
Unit Testing
- Jest (Node.js)
- Pytest (Python)
- PHPUnit (PHP)
// __tests__/StateSet.test.js
import { StateSetClient } from 'stateset-node';
import nock from 'nock';
describe('StateSet Integration', () => {
let client;
beforeEach(() => {
client = new StateSetClient({
apiKey: 'sk_test_123',
environment: 'sandbox'
});
});
afterEach(() => {
nock.cleanAll();
});
test('should create an order', async () => {
const orderData = {
customer_id: 'cus_123',
items: [{ product_id: 'prod_123', quantity: 1 }]
};
nock('https://api.stateset.com')
.post('/v1/orders', orderData)
.reply(201, {
id: 'ord_123',
...orderData,
status: 'pending'
});
const order = await client.orders.create(orderData);
expect(order.id).toBe('ord_123');
expect(order.status).toBe('pending');
});
test('should handle errors gracefully', async () => {
nock('https://api.stateset.com')
.post('/v1/orders')
.reply(400, {
error: {
message: 'Invalid customer_id',
code: 'invalid_request'
}
});
await expect(client.orders.create({}))
.rejects
.toThrow('Invalid customer_id');
});
});
# test_StateSet.py
import pytest
from unittest.mock import Mock, patch
from StateSet import StateSetClient
from StateSet.exceptions import StateSetError
@pytest.fixture
def client():
return StateSetClient(api_key='sk_test_123')
@pytest.fixture
def mock_response():
mock = Mock()
mock.json.return_value = {
'id': 'ord_123',
'status': 'pending'
}
mock.status_code = 201
return mock
def test_create_order(client, mock_response):
with patch('requests.post', return_value=mock_response):
order = client.orders.create(
customer_id='cus_123',
items=[{'product_id': 'prod_123', 'quantity': 1}]
)
assert order.id == 'ord_123'
assert order.status == 'pending'
def test_handle_error(client):
mock_response = Mock()
mock_response.status_code = 400
mock_response.json.return_value = {
'error': {
'message': 'Invalid request',
'code': 'invalid_request'
}
}
with patch('requests.post', return_value=mock_response):
with pytest.raises(StateSetError) as exc_info:
client.orders.create()
assert 'Invalid request' in str(exc_info.value)
@pytest.mark.asyncio
async def test_async_client():
from StateSet import AsyncStateSetClient
async_client = AsyncStateSetClient(api_key='sk_test_123')
with patch('httpx.AsyncClient.post') as mock_post:
mock_post.return_value.json.return_value = {'id': 'ord_123'}
mock_post.return_value.status_code = 201
order = await async_client.orders.create(
customer_id='cus_123'
)
assert order.id == 'ord_123'
<?php
// tests/StateSetTest.php
use PHPUnit\Framework\TestCase;
use StateSet\StateSetClient;
use StateSet\Exception\StateSetException;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
class StateSetTest extends TestCase
{
private $client;
protected function setUp(): void
{
$this->client = new StateSetClient([
'api_key' => 'sk_test_123',
'environment' => 'sandbox'
]);
}
public function testCreateOrder()
{
// Create mock handler
$mock = new MockHandler([
new Response(201, [], json_encode([
'id' => 'ord_123',
'status' => 'pending',
'customer_id' => 'cus_123'
]))
]);
$handlerStack = HandlerStack::create($mock);
$mockClient = new Client(['handler' => $handlerStack]);
// Inject mock client
$this->client->setHttpClient($mockClient);
$order = $this->client->orders->create([
'customer_id' => 'cus_123',
'items' => [
['product_id' => 'prod_123', 'quantity' => 1]
]
]);
$this->assertEquals('ord_123', $order->id);
$this->assertEquals('pending', $order->status);
}
public function testHandleError()
{
$mock = new MockHandler([
new Response(400, [], json_encode([
'error' => [
'message' => 'Invalid customer_id',
'code' => 'invalid_request'
]
]))
]);
$handlerStack = HandlerStack::create($mock);
$mockClient = new Client(['handler' => $handlerStack]);
$this->client->setHttpClient($mockClient);
$this->expectException(StateSetException::class);
$this->expectExceptionMessage('Invalid customer_id');
$this->client->orders->create([]);
}
}
Integration Testing
// integration-test.js
import { StateSetClient } from 'stateset-node';
import assert from 'assert';
const runIntegrationTests = async () => {
logger.info('🧪 Running StateSet Integration Tests...\n');
const client = new StateSetClient({
apiKey: process.env.STATESET_API_KEY,
environment: 'sandbox'
});
const tests = [
{
name: 'Health Check',
fn: async () => {
const health = await client.health.check();
assert(health.status === 'healthy', 'API should be healthy');
}
},
{
name: 'Create Customer',
fn: async () => {
const customer = await client.customers.create({
email: `test-${Date.now()}@example.com`,
name: 'Test Customer'
});
assert(customer.id, 'Customer should have an ID');
return customer;
}
},
{
name: 'Create Order',
fn: async (customer) => {
const order = await client.orders.create({
customer_id: customer.id,
items: [
{
product_id: 'prod_test',
quantity: 1,
price: 1000
}
]
});
assert(order.id, 'Order should have an ID');
assert(order.status === 'pending', 'Order should be pending');
return order;
}
},
{
name: 'Retrieve Order',
fn: async (order) => {
const retrieved = await client.orders.retrieve(order.id);
assert(retrieved.id === order.id, 'Should retrieve the same order');
}
},
{
name: 'List Orders',
fn: async () => {
const orders = await client.orders.list({ limit: 5 });
assert(Array.isArray(orders.data), 'Should return an array of orders');
assert(orders.data.length <= 5, 'Should respect limit parameter');
}
}
];
let context = {};
for (const test of tests) {
try {
const result = await test.fn(context);
if (result) context = result;
logger.info(`✅ ${test.name}`);
} catch (error) {
logger.error(`❌ ${test.name}: ${error.message}`);
process.exit(1);
}
}
logger.info('\n✨ All integration tests passed!');
};
runIntegrationTests().catch(console.error);
Load Testing
// load-test.js
import { StateSetClient } from 'stateset-node';
import pLimit from 'p-limit';
const runLoadTest = async () => {
const client = new StateSetClient({
apiKey: process.env.STATESET_API_KEY,
environment: 'sandbox'
});
const limit = pLimit(10); // Max 10 concurrent requests
const totalRequests = 100;
const startTime = Date.now();
logger.info(`🚀 Starting load test with ${totalRequests} requests...\n`);
const requests = Array.from({ length: totalRequests }, (_, i) =>
limit(async () => {
const start = Date.now();
try {
await client.orders.list({ limit: 1 });
return { success: true, duration: Date.now() - start };
} catch (error) {
return { success: false, error: error.message, duration: Date.now() - start };
}
})
);
const results = await Promise.all(requests);
const endTime = Date.now();
// Calculate statistics
const successful = results.filter(r => r.success).length;
const failed = results.filter(r => !r.success).length;
const avgDuration = results.reduce((sum, r) => sum + r.duration, 0) / results.length;
const totalDuration = endTime - startTime;
const requestsPerSecond = (totalRequests / totalDuration) * 1000;
logger.info('📊 Load Test Results:');
logger.info(`Total Requests: ${totalRequests}`);
logger.info(`Successful: ${successful} (${(successful/totalRequests*100);.toFixed(1)}%)`);
logger.info(`Failed: ${failed}`);
logger.info(`Average Response Time: ${avgDuration.toFixed(0);}ms`);
logger.info(`Total Duration: ${totalDuration}ms`);
logger.info(`Requests/Second: ${requestsPerSecond.toFixed(1);}`);
if (failed > 0) {
logger.info('\n❌ Failed requests:');
results.filter(r => !r.success).forEach((r, i) => {
logger.info(` ${i + 1}. ${r.error}`);
});
}
};
runLoadTest().catch(console.error);
Best Practices
Security
- Environment Management
- API key Rotation
// config/StateSet.js
import { StateSetClient } from 'stateset-node';
// Validate environment
const validateEnvironment = () => {
const required = ['STATESET_API_KEY'];
const missing = required.filter(key => !process.env[key]);
if (missing.length > 0) {
throw new Error(`Missing environment variables: ${missing.join(', ')}`);
}
// Validate API key format
const apiKey = process.env.STATESET_API_KEY;
if (!apiKey.startsWith('sk_test_') && !apiKey.startsWith('sk_live_')) {
throw new Error('Invalid API key format');
}
// Ensure test keys aren't used in production
if (process.env.NODE_ENV === 'production' && apiKey.startsWith('sk_test_')) {
throw new Error('Test API keys cannot be used in production');
}
};
validateEnvironment();
export const client = new StateSetClient({
apiKey: process.env.STATESET_API_KEY,
environment: process.env.NODE_ENV === 'production' ? 'production' : 'sandbox'
});
// Implement key rotation without downtime
class StateSetClientManager {
constructor() {
this.clients = new Map();
this.primaryKey = process.env.STATESET_API_KEY_PRIMARY;
this.secondaryKey = process.env.STATESET_API_KEY_SECONDARY;
}
getClient(useSecondary = false) {
const key = useSecondary ? this.secondaryKey : this.primaryKey;
if (!this.clients.has(key)) {
this.clients.set(key, new StateSetClient({ apiKey: key }));
}
return this.clients.get(key);
}
async rotateKeys() {
// Test secondary key
try {
const secondaryClient = this.getClient(true);
await secondaryClient.health.check();
// Swap keys
this.primaryKey = this.secondaryKey;
this.secondaryKey = process.env.STATESET_API_KEY_NEW;
// Clear old client
this.clients.clear();
logger.info('Key rotation completed successfully');
} catch (error) {
logger.error('Key rotation failed:', error);
throw error;
}
}
}
Error Handling
Retry Strategy
import { StateSetClient } from 'stateset-node';
import retry from 'async-retry';
const client = new StateSetClient({
apiKey: process.env.STATESET_API_KEY,
maxRetries: 0 // Disable built-in retry
});
// Custom retry with exponential backoff
async function resilientRequest(operation) {
return retry(
async (bail) => {
try {
return await operation();
} catch (error) {
// Don't retry client errors
if (error.statusCode >= 400 && error.statusCode < 500) {
bail(error);
}
throw error;
}
},
{
retries: 5,
factor: 2,
minTimeout: 1000,
maxTimeout: 30000,
onRetry: (error, attempt) => {
logger.info(`Attempt ${attempt} failed:`, error.message);
}
}
);
}
// Usage
const order = await resilientRequest(() =>
client.orders.create(orderData)
);
Circuit Breaker
import CircuitBreaker from 'opossum';
const options = {
timeout: 3000,
errorThresholdPercentage: 50,
resetTimeout: 30000
};
const breaker = new CircuitBreaker(
async (data) => client.orders.create(data),
options
);
breaker.on('open', () =>
logger.info('Circuit breaker opened');
);
breaker.on('halfOpen', () =>
logger.info('Circuit breaker half-open');
);
// Usage with fallback
try {
const order = await breaker.fire(orderData);
} catch (error) {
if (breaker.opened) {
// Use fallback behavior
logger.info('Service unavailable, using cache');
return getCachedOrder(orderId);
}
throw error;
}
Performance Optimization
- Connection Pooling
- Request Batching
- Caching Strategy
// Reuse HTTP connections
import https from 'https';
const agent = new https.Agent({
keepAlive: true,
keepAliveMsecs: 1000,
maxSockets: 50,
maxFreeSockets: 10,
timeout: 60000
});
const client = new StateSetClient({
apiKey: process.env.STATESET_API_KEY,
httpsAgent: agent
});
class BatchProcessor {
constructor(client, options = {}) {
this.client = client;
this.batchSize = options.batchSize || 100;
this.flushInterval = options.flushInterval || 1000;
this.queue = [];
this.timer = null;
}
add(operation) {
this.queue.push(operation);
if (this.queue.length >= this.batchSize) {
this.flush();
} else if (!this.timer) {
this.timer = setTimeout(() => this.flush(), this.flushInterval);
}
}
async flush() {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
if (this.queue.length === 0) return;
const batch = this.queue.splice(0, this.batchSize);
try {
const results = await Promise.all(
batch.map(op => op.execute())
);
batch.forEach((op, i) => {
op.resolve(results[i]);
});
} catch (error) {
batch.forEach(op => op.reject(error));
}
}
}
import { LRUCache } from 'lru-cache';
const cache = new LRUCache({
max: 500,
ttl: 1000 * 60 * 5, // 5 minutes
updateAgeOnGet: true
});
class CachedStateSetClient {
constructor(client) {
this.client = client;
}
async getOrder(orderId, skipCache = false) {
const cacheKey = `order:${orderId}`;
if (!skipCache && cache.has(cacheKey)) {
return cache.get(cacheKey);
}
const order = await this.client.orders.retrieve(orderId);
cache.set(cacheKey, order);
return order;
}
async createOrder(data) {
const order = await this.client.orders.create(data);
// Cache the created order
cache.set(`order:${order.id}`, order);
// Invalidate list cache
cache.delete('orders:list');
return order;
}
async listOrders(params = {}) {
const cacheKey = `orders:list:${JSON.stringify(params)}`;
if (cache.has(cacheKey)) {
return cache.get(cacheKey);
}
const orders = await this.client.orders.list(params);
cache.set(cacheKey, orders);
return orders;
}
}
Monitoring & Logging
// monitoring.js
import { StateSetClient } from 'stateset-node';
import winston from 'winston';
import { StatsD } from 'node-statsd';
// Configure logger
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'StateSet-error.log', level: 'error' }),
new winston.transports.File({ filename: 'StateSet-combined.log' })
]
});
// Configure metrics
const metrics = new StatsD({
host: 'localhost',
port: 8125,
prefix: 'StateSet.'
});
// Wrap client with monitoring
class MonitoredStateSetClient extends StateSetClient {
async request(method, path, data) {
const startTime = Date.now();
const metricName = `api.${method.toLowerCase()}.${path.replace(/\//g, '.')}`;
try {
const result = await super.request(method, path, data);
// Log success
const duration = Date.now() - startTime;
metrics.timing(metricName, duration);
metrics.increment(`${metricName}.success`);
logger.info('API Request Success', {
method,
path,
duration,
status: result.status
});
return result;
} catch (error) {
// Log error
const duration = Date.now() - startTime;
metrics.timing(metricName, duration);
metrics.increment(`${metricName}.error`);
logger.error('API Request Failed', {
method,
path,
duration,
error: {
message: error.message,
code: error.code,
statusCode: error.statusCode,
requestId: error.requestId
}
});
throw error;
}
}
}
Next Steps
Now that you have StateSet SDK installed and configured:Build Your Application
Support & Resources
Documentation
Direct Support
- Email: [email protected]
- Enterprise: [email protected]
- Security: [email protected]