Skip to main content

SDK Integration

This guide shows how to integrate token-based payments into your service using the SolvaPay SDK. The SDK handles the full verify → execute → settle lifecycle automatically through the payable() API.

Installation

npm install @solvapay/server

Quick Start

import { createSolvaPay } from '@solvapay/server'

const solvaPay = createSolvaPay({
apiKey: process.env.SOLVAPAY_SECRET_KEY,
})

The SDK reads SOLVAPAY_SECRET_KEY from the environment by default, so you can omit the apiKey parameter if the env var is set.

Payment Schemes

The payable() method accepts a scheme option that determines how payments are processed:

limits (Default)

Subscription-based access with usage quotas. No token locking — the SDK checks if the consumer's plan allows the request and tracks usage.

const handler = solvaPay.payable({
product: 'prd_myapi',
plan: 'pln_premium',
scheme: 'limits', // default, can be omitted
})

upto — Per-Request Token Billing

Two-phase token locking from the consumer's wallet. The SDK reserves tokens before your code runs and settles the actual amount after.

const handler = solvaPay.payable({
scheme: 'upto',
product: 'prd_myapi',
providerId: 'prv_xxx',
maxPrice: 500, // max 500 tokens per request
ttlSeconds: 1800, // lock TTL (default: 30 minutes)
})

voucher — Prepaid Voucher Tokens

Two-phase payment using a voucher token presented by the consumer. The consumer creates a voucher in their wallet, receives an encrypted token (spay_...), and presents it to the provider.

const handler = solvaPay.payable({
scheme: 'voucher',
product: 'prd_myapi',
providerId: 'prv_xxx',
maxPrice: 500,
})

Framework Adapters

The payable() method returns a PayableFunction with adapters for different frameworks.

Express / Fastify

const payable = solvaPay.payable({
scheme: 'upto',
product: 'prd_myapi',
providerId: 'prv_xxx',
maxPrice: 1000,
})

app.post('/analyze', payable.http(async (args) => {
const result = await runAnalysis(args.body)
await args.settle(result.tokenCost)
return result
}))

Next.js App Router

const payable = solvaPay.payable({
scheme: 'upto',
product: 'prd_myapi',
providerId: 'prv_xxx',
maxPrice: 1000,
})

export const POST = payable.next(async (args) => {
const result = await runAnalysis(args)
await args.settle(result.tokenCost)
return Response.json(result)
})

MCP Server

const payable = solvaPay.payable({
scheme: 'upto',
product: 'prd_myapi',
providerId: 'prv_xxx',
maxPrice: 500,
})

server.tool('analyze', payable.mcp(async (args) => {
const result = await expensiveAnalysis(args)
await args.settle(result.tokenCost)
return result
}))

Pure Function

const payable = solvaPay.payable({
scheme: 'upto',
product: 'prd_myapi',
providerId: 'prv_xxx',
maxPrice: 500,
})

const protectedFn = await payable.function(async (args) => {
const result = await doWork(args)
await args.settle(result.cost)
return result
})

// Call with auth context
const result = await protectedFn({
auth: { account_ref: 'acc_xxx' },
input: 'some data',
})

The settle Callback

When using upto or voucher schemes, your business logic receives a settle function in the args:

await args.settle(amount, description?)
ParameterTypeDescription
amountnumberActual tokens consumed (must be ≤ maxPrice)
descriptionstring?Optional description for the ledger entry

Important behaviors:

  • settle can only be called once per request
  • If your handler completes without calling settle, the lock is automatically released (no charge)
  • If your handler throws an error, the lock is automatically released (no charge)
  • Settling with amount: 0 is valid — the lock is released and the provider receives nothing

Voucher Authentication

For the voucher scheme, consumers present their voucher token in the request:

MCP / Function Args

// Consumer passes voucher token in auth
const result = await protectedFn({
auth: { voucher_token: 'spay_abc123...' },
input: 'analyze this',
})

HTTP Header

POST /analyze HTTP/1.1
x-solvapay-voucher: spay_abc123...
Content-Type: application/json

The SDK extracts the token from args.auth.voucher_token or the x-solvapay-voucher header automatically.

Additional Context in Args

For upto and voucher schemes, the SDK injects additional context into your handler args:

FieldSchemeDescription
args._lockIdbothThe token lock ID
args.settlebothSettlement callback
args._voucherIdvoucherThe voucher ID
args._accountRefvoucherConsumer account reference
args._identityvoucherConsumer identity (fingerprint, publicKey)
args._remainingvoucherRemaining voucher balance after this reservation

Low-Level Client Methods

If you need direct control over the verify/settle/release lifecycle (without the payable() wrapper), you can use the API client directly:

Token Lock (upto)

// Verify (lock tokens)
const lock = await solvaPay.verifyPayment({
accountRef: 'acc_xxx',
productRef: 'prd_myapi',
providerId: 'prv_xxx',
maxAmount: 500,
ttlSeconds: 1800,
})

// ... execute business logic ...

// Settle (charge actual amount)
const result = await solvaPay.settlePayment({
lockId: lock.lockId,
amount: 350,
description: 'API analysis',
})

// Or release (no charge)
await solvaPay.releasePayment({
lockId: lock.lockId,
reason: 'cancelled',
})

Voucher Payment

// Verify voucher
const lock = await solvaPay.apiClient.verifyVoucherPayment({
token: 'spay_abc123...',
maxAmount: 500,
productRef: 'prd_myapi',
providerId: 'prv_xxx',
})

// ... execute business logic ...

// Settle
const result = await solvaPay.apiClient.settleVoucherPayment({
lockId: lock.lockId,
amount: 200,
description: 'Analysis complete',
})

Read-Only Voucher Resolution

To inspect a voucher without locking tokens (e.g., to check balance before committing):

const info = await solvaPay.apiClient.resolveVoucher({
token: 'spay_abc123...',
productRef: 'prd_myapi',
})

console.log(info.balance) // remaining tokens
console.log(info.accountRef) // consumer account
console.log(info.status) // 'active'

Wallet Queries

const wallet = await solvaPay.getTokenWallet('acc_xxx')

console.log(wallet.balance) // total tokens
console.log(wallet.lockedAmount) // reserved tokens
console.log(wallet.availableBalance) // balance - locked
console.log(wallet.walletStatus) // 'active' or 'frozen'

Error Handling

The SDK throws SolvaPayError for API failures. Common error scenarios:

ErrorCause
Insufficient tokensConsumer's available balance < maxAmount
Lock not foundInvalid lock ID
Lock is already settledTrying to settle/release a completed lock
Lock is already releasedTrying to settle a released lock
Wallet is frozenConsumer's wallet is frozen (chargeback)
Voucher has expiredVoucher past its expiry date
Insufficient voucher balanceVoucher remaining < maxAmount

Next Steps