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
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_51H9x2C2QlDjKpM2WYw5...
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();
console.log('✅ 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 });
console.log('✅ API Access verified:', orders.length, 'orders found');
} catch (error) {
console.error('❌ Connection failed:', {
message: error.message,
code: error.code,
statusCode: error.statusCode
});
process.exit(1);
}
}
// Run tests
testConnection();
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
});
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) => {
console.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, () => {
console.log(`Server running on port ${PORT}`);
});
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
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_51H9x2C2QlDjKpM2WYw5...
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)
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)
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
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_51H9x2C2QlDjKpM2WYw5...
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: 'test@example.com',
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
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
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 php@8.1
# 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
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_51H9x2C2QlDjKpM2WYw5...
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' => 'test@example.com',
'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";
}
}
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);
}
}
Environment Configuration
Development Environment
# .env.development
STATESET_API_KEY=sk_test_51H9x2C2QlDjKpM2WYw5...
STATESET_ENVIRONMENT=sandbox
STATESET_WEBHOOK_SECRET=whsec_test_3rK9pL7nQ2xS5mT8...
LOG_LEVEL=debug
Production Environment
# .env.production
STATESET_API_KEY=sk_live_51H9x2C2QlDjKpM2WYw5...
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 "console.log(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
console.log('API Key exists:', !!process.env.STATESET_API_KEY);
console.log('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) {
console.log(`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) {
console.error('Webhook Error:', err.message);
res.status(400).send(`Webhook Error: ${err.message}`);
}
});
// 3. Debug webhook secret
console.log('Webhook secret exists:', !!process.env.STATESET_WEBHOOK_SECRET);
console.log('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
},
};
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');
});
});
Integration Testing
// integration-test.js
import { StateSetClient } from 'stateset-node';
import assert from 'assert';
const runIntegrationTests = async () => {
console.log('🧪 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;
console.log(`✅ ${test.name}`);
} catch (error) {
console.error(`❌ ${test.name}: ${error.message}`);
process.exit(1);
}
}
console.log('\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();
console.log(`🚀 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;
console.log('📊 Load Test Results:');
console.log(`Total Requests: ${totalRequests}`);
console.log(`Successful: ${successful} (${(successful/totalRequests*100).toFixed(1)}%)`);
console.log(`Failed: ${failed}`);
console.log(`Average Response Time: ${avgDuration.toFixed(0)}ms`);
console.log(`Total Duration: ${totalDuration}ms`);
console.log(`Requests/Second: ${requestsPerSecond.toFixed(1)}`);
if (failed > 0) {
console.log('\n❌ Failed requests:');
results.filter(r => !r.success).forEach((r, i) => {
console.log(` ${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'
});
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) => {
console.log(`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', () =>
console.log('Circuit breaker opened')
);
breaker.on('halfOpen', () =>
console.log('Circuit breaker half-open')
);
// Usage with fallback
try {
const order = await breaker.fire(orderData);
} catch (error) {
if (breaker.opened) {
// Use fallback behavior
console.log('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
});
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: support@stateset.com
- Enterprise: enterprise@stateset.com
- Security: security@stateset.com