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.New to StateSet? Start with our 5-minute quickstart to understand the basics before diving into SDK installation.
🚀 Quick Start
Choose your language and get started in under 5 minutes:Copy
npm install StateSet-node
📦 Supported SDKs
StateSet provides official SDKs for the following languages:Node.js / TypeScript
Most Popular
Full TypeScript support with async/await
Python
AI/ML Ready
Perfect for data science and backend APIs
Ruby
Rails Optimized
Native Rails and Sinatra integration
PHP
WordPress Ready
Compatible with Laravel and WordPress
🎯 Which SDK Should I Use?
I'm building a modern web application
I'm building a modern web application
Recommended: Node.js/TypeScript SDK
- ✅ 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
Recommended: Python SDK
- ✅ 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
Recommended: Ruby SDK
- ✅ 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
Recommended: PHP SDK
- ✅ PSR-compliant implementation
- ✅ WordPress plugin ready
- ✅ Laravel service provider included
- ✅ Composer autoloading
Node.js SDK
Prerequisites
Check Node.js Version
Copy
node --version
# Should output v16.0.0 or higher
Node.js 16+ is required. For older versions, use
StateSet-node@legacyVerify Package Manager
Copy
npm --version # Should be 7.0.0+
# OR
yarn --version # Should be 1.22.0+
# OR
pnpm --version # Should be 6.0.0+
Installation
- npm
- yarn
- pnpm
Copy
# 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
Copy
# 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
Copy
# 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
Copy
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
Copy
# .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
Copy
# .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
Copy
# .gitignore
node_modules/
.env
.env.local
dist/
*.log
Create Configuration Module
Copy
// 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
Copy
// 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();
Copy
// 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();
Copy
// 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
Copy
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
});
Copy
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
});
Copy
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
Copy
// 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}`);
});
Copy
// 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'
};
}
}
Copy
// 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`
});
}
}
Copy
// 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 };
}
Copy
// 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]
};
}
}
Copy
// 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);
}
}
Copy
// 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 {}
Copy
// 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
Copy
python --version
# Should output Python 3.8.0 or higher
# Check pip version
pip --version
# Should be version 20.0 or higher
Python 3.8+ is required. For older versions, use
StateSet-python==1.xInstallation
- pip
- poetry
- conda
- requirements.txt
Copy
# 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
Copy
# 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
Copy
# Install via pip in conda environment
pip install StateSet-python
# Install conda dependencies first
conda install pandas numpy requests
pip install "StateSet-python[async]"
Copy
# 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
Copy
pip install -r requirements.txt
Quick Setup
Create Project Structure
Copy
# 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
Copy
# .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
Copy
# .gitignore
__pycache__/
*.py[cod]
*$py.class
.env
.venv/
venv/
.pytest_cache/
.mypy_cache/
*.log
Create Configuration Module
Copy
# 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
Copy
# 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)
Copy
# 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())
Copy
# 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
Copy
# 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)
Copy
# 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)
Copy
# 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
Copy
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
Ruby 2.7+ is required. For older versions, use
StateSet-ruby v1.xInstallation
- Gemfile
- Direct Install
- From Source
Copy
# 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
Copy
bundle install
Copy
# 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
Copy
# 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
Copy
# 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
Copy
# .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
Copy
# .gitignore
*.gem
*.rbc
/.config
/coverage/
/spec/reports/
/tmp/
.env
.env.local
*.log
.DS_Store
Create Configuration
Copy
# 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
Copy
# 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
Copy
# 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
Copy
# 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
Copy
# 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
Copy
# 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
Copy
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
PHP 8.0+ is required. For older versions, use
StateSet-php v1.xInstall Required Extensions
Copy
# 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
Copy
# 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
Copy
# 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';
Copy
# 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
Copy
# 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
Copy
# .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
Copy
# .gitignore
/vendor/
/.env
/.env.local
/composer.lock
/.phpunit.result.cache
/logs/
*.log
.DS_Store
Create Configuration
Copy
<?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
Copy
<?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";
}
}
Copy
<?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()
]);
}
}
Copy
<?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
Copy
<?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),
];
Copy
<?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');
}
}
Copy
<?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);
});
}
}
Copy
<?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);
}
}
}
Copy
<?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);
}
}
Copy
<?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
Copy
# .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
Copy
# .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
Copy
# Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
Copy
# 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
Problem: Code fix:
401 Unauthorized errorsSolutions:Copy
# 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_*
Copy
// 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
Problem: Requests timing out or
ETIMEDOUT errorsSolutions:Copy
// 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
Problem: Cannot import StateSet modules or TypeScript errorsSolutions:
Copy
# 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
Problem:
429 Too Many Requests errorsSolutions:Copy
// 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
Problem: Webhook signature verification failingSolutions:
Copy
// 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
Copy
// 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
},
};
Copy
// 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 })
};
}
};
Copy
# 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"]
Copy
# 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)
Copy
// __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');
});
});
Copy
# 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'
Copy
<?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
Copy
// 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
Copy
// 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
Never commit API keys or secrets to version control. Always use environment variables.
- Environment Management
- API key Rotation
Copy
// 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'
});
Copy
// 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
Copy
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
Copy
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
Copy
// 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
});
Copy
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));
}
}
}
Copy
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
Copy
// 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
Check out our example applications:
Support & Resources
Documentation
Direct Support
- Email: [email protected]
- Enterprise: [email protected]
- Security: [email protected]
Status & Updates
Need help? Our support team is available Monday-Friday, 9 AM - 5 PM PST. Enterprise customers have 24/7 support.