spaps-sdk
Version:
Sweet Potato Authentication & Payment Service SDK - Zero-config client with built-in permission checking, role-based access control, and dayrate scheduling
623 lines (617 loc) • 23.5 kB
TypeScript
import * as spaps_types from 'spaps-types';
import { CreateProductRequest, Product, UpdateProductRequest, CreatePriceRequest, Price, ProductSyncResult, CryptoReconcileRequest, CreateSecureMessageRequest, SecureMessage, AuthResponse, User as User$1, CreateCryptoInvoiceRequest, CryptoInvoiceStatusSnapshot, CheckoutSession, DayrateAvailabilityResponse, DayrateBookingRequest, DayrateBookingResponse, DayrateMultiBookingRequest, DayrateMultiBookingResponse, Subscription, UsageBalance, VerifyCryptoWebhookSignatureOptions } from 'spaps-types';
export { AdminPermission, AdminRole, AdminUser, ApiResponse, AuthResponse, CheckoutSession, CreateCryptoInvoiceRequest, CreatePriceRequest, CreateProductRequest, CreateSecureMessageInput, CreateSecureMessageRequest, CryptoInvoice, CryptoInvoiceResponse, CryptoInvoiceStatusSnapshot, CryptoReconcileRequest, DayrateAvailabilityResponse, DayrateAvailableSlot, DayrateBookingRequest, DayrateBookingResponse, DayrateDayOfWeek, DayrateMultiBookingRequest, DayrateMultiBookingResponse, DayratePriceBreakdown, DayrateSlotType, Price, Product, ProductSyncResult, SecureMessage, SecureMessageOutput, Subscription, TokenPair, UpdateProductRequest, UsageBalance, User, UserProfile, UserRole, UserWallet, VerifyCryptoWebhookSignatureOptions, createSecureMessageRequestSchema, secureMessageMetadataSchema, secureMessageSchema } from 'spaps-types';
/**
* Permission checking utilities for SPAPS SDK
* Client-side role and permission management
*/
interface User {
id: string;
email?: string;
wallet_address?: string;
role?: string;
permissions?: string[];
tier?: string;
}
interface AdminConfig {
email: string;
wallets: {
ethereum: string;
solana: string;
};
}
interface PermissionCheckResult {
allowed: boolean;
reason?: string;
userRole: string;
requiredRole?: string;
}
declare const DEFAULT_ADMIN_ACCOUNTS: AdminConfig;
/**
* Check if an identifier (email/wallet) is an admin account
*/
declare function isAdminAccount(identifier: string, customAdmins?: (string | AdminConfig)[]): boolean;
/**
* Get user role based on identifier
*/
declare function getUserRole(identifier?: string, customAdmins?: (string | AdminConfig)[]): string;
/**
* Check if user has specific permissions
*/
declare function hasPermission(user: User | null, requiredPermissions: string | string[], customAdmins?: (string | AdminConfig)[]): boolean;
/**
* Check if user can access admin features
*/
declare function canAccessAdmin(user: User | null, customAdmins?: (string | AdminConfig)[]): PermissionCheckResult;
/**
* Get role-aware error message
*/
declare function getRoleAwareErrorMessage(requiredRole: string, userRole: string, action?: string): string;
/**
* Get user display information with role indicators
*/
declare function getUserDisplay(user: User | null, customAdmins?: (string | AdminConfig)[]): {
displayName: string;
role: string;
badge: string | null;
isAdmin: boolean;
};
/**
* Permission checker class for easier usage
*/
declare class PermissionChecker {
private customAdmins;
constructor(customAdmins?: (string | AdminConfig)[]);
isAdmin(identifier: string): boolean;
getRole(identifier?: string): string;
hasPermission(user: User | null, permissions: string | string[]): boolean;
canAccessAdmin(user: User | null): PermissionCheckResult;
getErrorMessage(requiredRole: string, userRole: string, action?: string): string;
getUserDisplay(user: User | null): {
displayName: string;
role: string;
badge: string | null;
isAdmin: boolean;
};
requiresAuth(user: User | null): boolean;
requiresAdmin(user: User | null): boolean;
addCustomAdmin(admin: string | AdminConfig): void;
removeCustomAdmin(admin: string | AdminConfig): void;
}
/**
* Create a permission checker instance
*/
declare function createPermissionChecker(customAdmins?: (string | AdminConfig)[]): PermissionChecker;
declare const defaultPermissionChecker: PermissionChecker;
type ApiKeyType = 'publishable' | 'secret';
interface SPAPSConfig {
apiUrl?: string;
/** @deprecated Use publishableKey or secretKey instead */
apiKey?: string;
/** Browser-safe key for client-side usage (spaps_pub_xxx) */
publishableKey?: string;
/** Server-only key for full access (spaps_sec_xxx) */
secretKey?: string;
autoDetect?: boolean;
timeout?: number;
}
interface CheckoutLineItemPriceData {
currency: string;
unit_amount: number;
product_data: {
name: string;
description?: string;
metadata?: Record<string, string>;
};
}
interface CheckoutLineItem {
price_id?: string;
product_id?: string;
quantity: number;
price_data?: CheckoutLineItemPriceData;
}
interface CreateCheckoutSessionPayload {
mode: 'payment' | 'subscription';
line_items: CheckoutLineItem[];
success_url: string;
cancel_url: string;
metadata?: Record<string, string>;
customer_email?: string;
client_reference_id?: string;
payment_intent_data?: {
metadata?: Record<string, string>;
};
subscription_data?: {
metadata?: Record<string, string>;
trial_period_days?: number;
};
allow_promotion_codes?: boolean;
locale?: string;
require_legal_consent?: boolean;
legal_consent_text?: string;
}
declare class SPAPSClient<SecureMessageMetadata extends Record<string, any> = Record<string, any>> {
private client;
private apiKey?;
private accessToken?;
private refreshToken?;
private _isLocalMode;
private unwrapApiResponse;
private isAxiosResponse;
private isResponseLikeWithData;
private isApiResponse;
admin: {
createProduct: (productData: CreateProductRequest) => Promise<{
data: Product;
}>;
updateProduct: (productId: string, updates: UpdateProductRequest) => Promise<{
data: Product;
}>;
deleteProduct: (productId: string) => Promise<{
data: {
id: string;
active: boolean;
archived: boolean;
};
}>;
createPrice: (priceData: CreatePriceRequest) => Promise<{
data: Price;
}>;
syncProducts: () => Promise<{
data: ProductSyncResult;
}>;
getProducts: () => Promise<{
data: {
products: Product[];
total: number;
adminMetadata?: any;
};
}>;
triggerCryptoReconcile: (opts?: CryptoReconcileRequest) => Promise<{
job_id: string;
scheduled_at: string;
cursor?: Record<string, unknown>;
}>;
};
secureMessages: {
create: (payload: CreateSecureMessageRequest<SecureMessageMetadata>) => Promise<SecureMessage<SecureMessageMetadata>>;
list: () => Promise<SecureMessage<SecureMessageMetadata>[]>;
};
constructor(config?: SPAPSConfig);
/** Raw API request helper that returns an ApiResponse-like shape */
request<T = any>(method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH', url: string, data?: any, requiresAuth?: boolean): Promise<{
success: boolean;
data?: T;
error?: {
code: string;
message: string;
details?: any;
};
}>;
/** Health check helper that returns boolean */
healthCheck(): Promise<boolean>;
login(email: string, password: string): Promise<{
data: AuthResponse;
}>;
register(email: string, password: string): Promise<{
data: AuthResponse;
}>;
walletSignIn(walletAddress: string, signature: string, message: string, chainType?: 'solana' | 'ethereum'): Promise<{
data: AuthResponse;
}>;
refresh(refreshToken?: string): Promise<{
data: AuthResponse;
}>;
logout(): Promise<void>;
getUser(): Promise<{
data: User$1;
}>;
auth: {
/**
* Verify magic link token without mutating token state.
* Returns a simple success object from the API.
*/
getNonce: (walletAddress: string) => Promise<any>;
signInWithWallet: (req: {
wallet_address: string;
signature: string;
message: string;
chain_type?: "solana" | "ethereum" | "bitcoin" | "base";
username?: string;
}) => Promise<any>;
authenticateWallet: (walletAddress: string, signFn: (message: string) => Promise<string> | string, chainType?: "solana" | "ethereum" | "bitcoin" | "base", username?: string) => Promise<any>;
signInWithPassword: (payload: {
email: string;
password: string;
}) => Promise<any>;
requestMagicLink: (payload: {
email: string;
redirect_url?: string;
}) => Promise<void>;
requestPasswordReset: (payload: {
email: string;
redirect_url?: string;
}) => Promise<void>;
confirmPasswordReset: (payload: {
token: string;
new_password: string;
}) => Promise<void>;
register: (payload: {
email: string;
password: string;
}) => Promise<any>;
/**
* Verify a magic link token and authenticate the user.
* Returns full auth response with tokens and auto-stores them.
*/
verifyMagicLink: (payload: {
token: string;
type?: string;
}) => Promise<AuthResponse>;
solana: {
linkWallet: (payload: {
wallet_address: string;
signature: string;
message: string;
}) => Promise<any>;
verifySignature: (payload: {
wallet_address: string;
signature: string;
message: string;
}) => Promise<any>;
generateMessage: (wallet_address: string) => Promise<any>;
getWallets: () => Promise<any>;
networkInfo: () => Promise<any>;
};
ethereum: {
linkWallet: (payload: {
wallet_address: string;
signature: string;
message: string;
}) => Promise<any>;
verifySignature: (payload: {
wallet_address: string;
signature: string;
message: string;
}) => Promise<any>;
verifyTypedData: (payload: {
wallet_address: string;
signature: string;
typed_data: any;
}) => Promise<any>;
generateMessage: (wallet_address: string) => Promise<any>;
generateTypedData: (wallet_address: string) => Promise<any>;
getWallets: () => Promise<any>;
networkInfo: () => Promise<any>;
balance: (wallet_address: string) => Promise<any>;
contractCheck: (wallet_address: string, contract_address: string) => Promise<any>;
};
refreshToken: (refreshToken: string) => Promise<any>;
/**
* Logout and clear tokens. Network errors are intentionally swallowed
* to avoid trapping users in a bad state during sign‑out flows.
*/
logout: () => Promise<void>;
getCurrentUser: () => Promise<User$1>;
isAuthenticated: () => boolean;
};
payments: {
crypto: {
createInvoice: (payload: CreateCryptoInvoiceRequest) => Promise<spaps_types.CryptoInvoice>;
getInvoice: (invoiceId: string) => Promise<spaps_types.CryptoInvoice>;
getInvoiceStatus: (invoiceId: string) => Promise<CryptoInvoiceStatusSnapshot>;
reconcile: (options?: CryptoReconcileRequest) => Promise<{
job_id: string;
scheduled_at: string;
cursor?: Record<string, unknown>;
}>;
};
createCheckoutSession: (payload: CreateCheckoutSessionPayload) => Promise<CheckoutSession>;
createPaymentCheckout: (params: {
price_id: string;
quantity?: number;
success_url: string;
cancel_url: string;
require_legal_consent?: boolean;
legal_consent_text?: string;
}) => Promise<CheckoutSession>;
createSubscriptionCheckout: (params: {
price_id: string;
success_url: string;
cancel_url: string;
trial_period_days?: number;
require_legal_consent?: boolean;
legal_consent_text?: string;
}) => Promise<CheckoutSession>;
getCheckoutSession: (sessionId: string) => Promise<CheckoutSession>;
listCheckoutSessions: (query?: {
limit?: number;
starting_after?: string;
}) => Promise<{
sessions: any[];
has_more: boolean;
next_cursor?: string;
}>;
expireCheckoutSession: (sessionId: string) => Promise<{
id: string;
status: string;
expired: boolean;
}>;
listProducts: (query?: {
category?: string;
active?: boolean;
limit?: number;
starting_after?: string;
}) => Promise<{
products: Product[];
total: number;
adminMetadata?: any;
}>;
getProduct: (productId: string) => Promise<Product>;
createCustomerPortalSession: (payload: {
return_url: string;
}) => Promise<{
id: string;
url: string;
}>;
createGuestCheckoutSession: (payload: any) => Promise<any>;
getGuestCheckoutSession: (sessionId: string) => Promise<any>;
listGuestCheckoutSessions: (query?: {
limit?: number;
starting_after?: string;
}) => Promise<any>;
convertGuestCheckoutSession: (payload: {
session_id: string;
}) => Promise<any>;
convertGuestCheckout: (payload: {
session_id: string;
}) => Promise<any>;
listAllProductsSuperAdmin: () => Promise<any>;
updateProductSuperAdmin: (productId: string, updates: any) => Promise<any>;
deleteProductSuperAdmin: (productId: string) => Promise<any>;
createProductWithPrice: (payload: any) => Promise<any>;
createProductWithPriceSuperAdmin: (productId: string, payload: any) => Promise<any>;
setDefaultPrice: (productId: string, payload: {
price_id: string;
}) => Promise<any>;
setDefaultPriceSuperAdmin: (productId: string, payload: {
price_id: string;
}) => Promise<any>;
createDefaultNewPrice: (productId: string, payload: any) => Promise<any>;
superAdminListAllProducts: () => Promise<any>;
superAdminUpdateProduct: (productId: string, updates: any) => Promise<any>;
superAdminArchiveProduct: (productId: string) => Promise<any>;
superAdminCreateProductWithPrice: (applicationId: string, payload: any) => Promise<any>;
superAdminCreatePriceAndSetDefault: (productId: string, payload: any) => Promise<any>;
superAdminSetDefaultPrice: (productId: string, payload: {
price_id: string;
}) => Promise<any>;
};
sessions: {
getCurrent: () => Promise<any>;
list: (params?: {
limit?: number;
starting_after?: string;
}) => Promise<any>;
validate: () => Promise<any>;
revoke: (sessionId: string) => Promise<any>;
revokeAll: () => Promise<any>;
touch: () => Promise<any>;
};
/**
* CFO (QuickBooks) namespace for managing multi-account connections
*/
cfo: {
/**
* Get all QuickBooks connections for the current user
*/
getConnections: () => Promise<{
connections: Array<{
id: string;
status: string;
companyName?: string;
realmId?: string;
connectedAt?: string;
}>;
}>;
/**
* Get connection status (returns primary connection for backward compatibility)
*/
getStatus: () => Promise<{
connection: {
id: string;
status: string;
companyName?: string;
realmId?: string;
} | null;
totalConnections: number;
}>;
/**
* Start adding a new QuickBooks connection
* Returns auth URL to redirect user to QuickBooks OAuth
*/
addConnection: () => Promise<{
authUrl: string;
connectionId: string;
existingConnections: number;
}>;
/**
* Disconnect a specific QuickBooks account
*/
disconnect: (connectionId: string) => Promise<{
success: boolean;
message: string;
}>;
};
/**
* DayRate (Dynamic Scheduling) namespace
* For booking half-day sessions with dynamic pricing
*/
dayrate: {
/**
* Get available slots with current pricing
* Public endpoint - no authentication required
*/
getAvailability: () => Promise<DayrateAvailabilityResponse>;
/**
* Create a single-slot booking and get Stripe checkout URL
* Returns a 10-minute reservation with checkout link
*/
createBooking: (payload: DayrateBookingRequest) => Promise<DayrateBookingResponse>;
/**
* Create a multi-slot booking and get Stripe checkout URL
* Reserves multiple slots with a single checkout session
*/
createMultiBooking: (payload: DayrateMultiBookingRequest) => Promise<DayrateMultiBookingResponse>;
};
createCheckoutSession(priceId: string, successUrl: string, cancelUrl?: string): Promise<{
data: CheckoutSession;
}>;
getSubscription(): Promise<{
data: Subscription;
}>;
cancelSubscription(): Promise<void>;
getUsageBalance(): Promise<{
data: UsageBalance;
}>;
recordUsage(feature: string, amount: number): Promise<void>;
private createSecureMessage;
private listSecureMessages;
/**
* Create a new Stripe product (Admin required)
*/
createProduct(productData: CreateProductRequest): Promise<{
data: Product;
}>;
/**
* Update an existing Stripe product (Admin required)
*/
updateProduct(productId: string, updates: UpdateProductRequest): Promise<{
data: Product;
}>;
/**
* Archive (soft delete) a Stripe product (Admin required)
*/
deleteProduct(productId: string): Promise<{
data: {
id: string;
active: boolean;
archived: boolean;
};
}>;
/**
* Create a new price for a product (Admin required)
*/
createPrice(priceData: CreatePriceRequest): Promise<{
data: Price;
}>;
/**
* Sync all products from Stripe to local database (Super Admin required)
*/
syncProducts(): Promise<{
data: ProductSyncResult;
}>;
/**
* Get products with admin metadata (if user is admin)
*/
getProducts(): Promise<{
data: {
products: Product[];
total: number;
adminMetadata?: any;
};
}>;
isAuthenticated(): boolean;
getAccessToken(): string | undefined;
setAccessToken(token: string): void;
setRefreshToken(token: string): void;
/**
* Restore tokens (for SSO/OAuth flows)
* Sets both access and refresh tokens
*/
restoreTokens(accessToken: string, refreshToken?: string): void;
isLocalMode(): boolean;
/**
* Check if current user has admin privileges
* Note: This requires the user object from authentication
*/
isAdmin(user?: User$1): boolean;
health(): Promise<{
data: any;
}>;
}
declare function verifyCryptoWebhookSignature(options: VerifyCryptoWebhookSignatureOptions): boolean;
declare class TokenManager {
private static readonly ACCESS_TOKEN_KEY;
private static readonly REFRESH_TOKEN_KEY;
private static readonly USER_KEY;
/**
* Cross-platform base64 decode (works in both Node.js and browser)
*/
private static base64Decode;
private static getStorage;
static storeTokens(tokens: AuthResponse): void;
static getAccessToken(): string | null;
static getRefreshToken(): string | null;
static getStoredUser(): User$1 | null;
static clearTokens(): void;
static isTokenExpired(token: string): boolean;
static autoRefreshToken(sdk: SPAPSClient): Promise<boolean>;
/**
* Parse auth tokens from URL fragment (for OAuth callbacks)
* URL format: https://app.com/callback#access_token=xxx&refresh_token=xxx
*/
static parseTokensFromUrlFragment(url?: string): {
access_token?: string;
refresh_token?: string;
user_id?: string;
} | null;
/**
* Handle OAuth callback - parse tokens from URL and store them
* Returns user info decoded from JWT, or null if no tokens found
*/
static handleOAuthCallback(sdk: SPAPSClient): {
id: string;
email?: string;
role?: string;
} | null;
}
declare class WalletUtils {
static detectChainType(address: string): 'solana' | 'ethereum' | 'bitcoin' | null;
static isValidAddress(address: string, chainType?: 'solana' | 'ethereum' | 'bitcoin' | 'base'): boolean;
}
/**
* Create a SPAPS client for browser/client-side usage
* Uses publishable key which is safe to expose in client bundles
*
* @example
* ```typescript
* // In your frontend code
* const spaps = createBrowserClient('spaps_pub_xxx');
*
* // Use for authentication and checkout
* const { user } = await spaps.auth.signInWithPassword({ email, password });
* const checkout = await spaps.payments.createCheckoutSession({...});
* ```
*/
declare function createBrowserClient(publishableKey: string, options?: Omit<SPAPSConfig, 'publishableKey' | 'secretKey' | 'apiKey'>): SPAPSClient;
/**
* Create a SPAPS client for server-side usage
* Uses secret key which provides full access to all endpoints
*
* @example
* ```typescript
* // In your API routes (Next.js, Express, etc.)
* const spaps = createServerClient('spaps_sec_xxx');
*
* // Full access to admin operations
* await spaps.admin.createProduct({...});
* await spaps.payments.crypto.reconcile();
* ```
*/
declare function createServerClient(secretKey: string, options?: Omit<SPAPSConfig, 'publishableKey' | 'secretKey' | 'apiKey'>): SPAPSClient;
/**
* Detect key type from key prefix
*/
declare function detectKeyType(key: string): ApiKeyType | null;
export { type AdminConfig, type ApiKeyType, type CheckoutLineItem, type CheckoutLineItemPriceData, type CreateCheckoutSessionPayload, DEFAULT_ADMIN_ACCOUNTS, type PermissionCheckResult, PermissionChecker, SPAPSClient as SPAPS, SPAPSClient, type SPAPSConfig, TokenManager, WalletUtils, canAccessAdmin, createBrowserClient, createPermissionChecker, createServerClient, SPAPSClient as default, defaultPermissionChecker, detectKeyType, getRoleAwareErrorMessage, getUserDisplay, getUserRole, hasPermission, isAdminAccount, verifyCryptoWebhookSignature };