spaps-sdk
Version:
Sweet Potato Authentication & Payment Service SDK - Zero-config client with built-in permission checking, role-based access control, and dayrate scheduling
696 lines (690 loc) • 25.6 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;
}
interface EmailSendOptions {
templateKey: string;
to: string;
context: Record<string, string | number | boolean>;
userId?: string;
}
interface EmailSendResult {
success: boolean;
messageId?: string;
error?: string;
}
interface TemplateVariable {
name: string;
required?: boolean;
description?: string;
}
interface EmailTemplate {
id: string;
applicationId?: string;
templateKey: string;
name: string;
description?: string;
subject: string;
htmlBody?: string;
textBody?: string;
fromEmail?: string;
fromName?: string;
replyTo?: string;
variables?: TemplateVariable[];
sampleContext?: Record<string, unknown>;
category?: string;
isActive?: boolean;
createdAt?: string;
updatedAt?: string;
}
interface EmailTemplatePreview {
subject: string;
html: string;
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>[]>;
};
email: {
send: (options: EmailSendOptions) => Promise<EmailSendResult>;
listTemplates: () => Promise<EmailTemplate[]>;
getTemplate: (templateKey: string) => Promise<EmailTemplate>;
previewTemplate: (templateKey: string, context?: Record<string, unknown>) => Promise<EmailTemplatePreview>;
};
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>;
/**
* Set or change password for authenticated users.
* For magic link / wallet users setting their first password, only new_password is required.
* For users changing an existing password, current_password is also required.
*/
setPassword: (payload: {
current_password?: string;
new_password: string;
}) => Promise<{
message: string;
}>;
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;
/**
* Send an email using a template
*/
private sendEmail;
/**
* List all email templates for the application
*/
private listEmailTemplates;
/**
* Get a single email template by key
*/
private getEmailTemplate;
/**
* Preview a rendered email template
*/
private previewEmailTemplate;
/**
* 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 EmailSendOptions, type EmailSendResult, type EmailTemplate, type EmailTemplatePreview, type PermissionCheckResult, PermissionChecker, SPAPSClient as SPAPS, SPAPSClient, type SPAPSConfig, type TemplateVariable, TokenManager, WalletUtils, canAccessAdmin, createBrowserClient, createPermissionChecker, createServerClient, SPAPSClient as default, defaultPermissionChecker, detectKeyType, getRoleAwareErrorMessage, getUserDisplay, getUserRole, hasPermission, isAdminAccount, verifyCryptoWebhookSignature };