@sodasoft/meshtally
v2.7.10
Published
Meshtally Node.js SDK — Smart Payment Routing, Orchestration & Pricing Intelligence
Maintainers
Readme
@sodasoft/meshtally
Official Node.js SDK for the Meshtally Smart Payment Routing & Orchestration API.
Zero dependencies. Full TypeScript support. 77 tests. Node.js 18+ (uses native fetch).
Features
- Zero runtime dependencies — no supply chain risk
- Full TypeScript — strict types, exported interfaces, JSDoc on every method
- Automatic retries — exponential backoff with jitter, respects
Retry-After - Idempotency — UUID v4 keys auto-generated for POST/PATCH
- Webhook verification — HMAC-SHA256 with timing-safe comparison and 5-min replay protection
- Async pagination —
paginate()generator for automatic page traversal - Debug safe — sensitive fields (IBAN, email, wallet) redacted from debug logs
- 77 tests — unit, validation, integration (mock HTTP server), webhook verification
Install
npm install @sodasoft/meshtallyQuick Start
import { Meshtally } from '@sodasoft/meshtally';
const meshtally = new Meshtally({
apiKey: process.env.MESHTALLY_API_KEY!,
environment: 'sandbox',
});
const session = await meshtally.checkoutSessions.create({
amount: 99.99,
currency: 'EUR',
customer_email: '[email protected]',
success_url: 'https://yoursite.com/success',
cancel_url: 'https://yoursite.com/cancel',
});
// Redirect customer to hosted checkout
res.redirect(session.checkout_url);ML-Powered Routing Context
Pass transaction context to enable intelligent PSP routing. The more context you provide, the better the ML model optimizes approval rates:
const session = await meshtally.checkoutSessions.create({
amount: 499.99,
currency: 'EUR',
// Routing context — improves ML predictions
customer_country: 'DE',
card_bin: '411111', // First 6 digits (Visa/MC/Amex detection)
channel: 'web', // 'web' | 'mobile' | 'api' | 'in_store'
risk_level: 'low', // 'low' | 'medium' | 'high'
device_type: 'desktop', // 'desktop' | 'mobile' | 'tablet'
payment_type: 'subscription', // 'one_time' | 'subscription' | 'recurring' | 'deposit' | 'withdrawal'
// Product context — for analytics & routing
product_category: 'trading_deposit',
product_name: 'Pro Plan',
price_tier: 'high', // 'micro' | 'low' | 'medium' | 'high' | 'premium'
subscription_interval: 'monthly',
// Customer context
customer_email: '[email protected]',
is_returning_customer: true,
customer_lifetime_payments: 12,
billing_address: { country: 'DE', postal_code: '10115' },
// URLs
success_url: 'https://yoursite.com/success',
cancel_url: 'https://yoursite.com/cancel',
metadata: { order_id: 'ORD-123' },
});Multi-Item Checkout (Line Items)
For orders with multiple products (e-commerce, airline tickets, etc.):
const session = await meshtally.checkoutSessions.create({
currency: 'EUR',
line_items: [
{ name: 'Flight BEG-LHR', quantity: 1, unit_price: 289.00, currency: 'EUR' },
{ name: 'Extra Baggage 23kg', quantity: 2, unit_price: 35.00, currency: 'EUR' },
{ name: 'Seat Selection 14A', quantity: 1, unit_price: 12.00, currency: 'EUR' },
],
customer_country: 'RS',
payment_type: 'one_time',
product_category: 'airline_ticket',
success_url: 'https://airline.com/booking/success',
});
// Total: €371.00 (auto-calculated from line items)Configuration
const meshtally = new Meshtally({
apiKey: process.env.MESHTALLY_API_KEY!,
environment: 'sandbox', // 'sandbox' | 'production'
timeout: 30000, // request timeout in ms (default: 30000)
maxRetries: 3, // retry count for 429/5xx (default: 3)
debug: false, // enable request/response logging (default: false)
});Resources
Checkout Sessions
// Create
const session = await meshtally.checkoutSessions.create({ amount: 99.99, currency: 'EUR' });
// Retrieve
const detail = await meshtally.checkoutSessions.retrieve('cs_abc123');
// List
const { sessions, total } = await meshtally.checkoutSessions.list({ status: 'completed', limit: 20 });
// Expire
await meshtally.checkoutSessions.expire('cs_abc123');Transactions
// Retrieve
const txn = await meshtally.transactions.retrieve('txn_abc123');
// List with filters
const { transactions, total } = await meshtally.transactions.list({
status: 'captured',
currency: 'EUR',
limit: 50,
});
// Update metadata
await meshtally.transactions.update('txn_abc123', {
metadata: { order_id: 'order_456' },
});Refunds
// Create (full or partial)
const refund = await meshtally.refunds.create({
transaction_id: 'txn_abc123',
amount: 25.00,
reason: 'Customer request',
});
// List refunds for a transaction
const { refunds } = await meshtally.refunds.list({ transaction_id: 'txn_abc123' });Payouts
// Create payout to beneficiary
const payout = await meshtally.payouts.create({
beneficiary_id: 'ben_abc123',
amount: 500.00,
currency: 'EUR',
});
// List payouts
const { payouts } = await meshtally.payouts.list({ status: 'completed' });
// Approve / Cancel
await meshtally.payouts.approve('po_abc123');
await meshtally.payouts.cancel('po_abc123');
// Batch payouts
await meshtally.payouts.createBatch({
instructions: [
{ beneficiary_id: 'ben_1', amount: 100, currency: 'EUR' },
{ beneficiary_id: 'ben_2', amount: 200, currency: 'EUR' },
],
});Beneficiaries
// Create
const beneficiary = await meshtally.beneficiaries.create({
first_name: 'John',
last_name: 'Doe',
type: 'trader',
email: '[email protected]',
});
// Retrieve
const ben = await meshtally.beneficiaries.retrieve('ben_abc123');
// Update
await meshtally.beneficiaries.update('ben_abc123', { email: '[email protected]' });
// List
const { beneficiaries, total } = await meshtally.beneficiaries.list({ limit: 50 });
// Delete (soft-delete — sets status to blocked)
await meshtally.beneficiaries.delete('ben_abc123');
// ─── Payout methods ─────────────────────────────────────────
// Add a bank account
await meshtally.beneficiaries.addPayoutMethod('ben_abc123', {
method_type: 'bank',
currency: 'EUR',
iban: 'DE89370400440532013000',
swift_bic: 'COBADEFFXXX',
bank_name: 'Commerzbank',
bank_country: 'DE',
});
// Add a crypto wallet
await meshtally.beneficiaries.addPayoutMethod('ben_abc123', {
method_type: 'crypto',
currency: 'USDT',
crypto_network: 'tron',
wallet_address: 'TEbatdMV3...',
});
// List payout methods for a beneficiary
const { payout_methods } = await meshtally.beneficiaries.listPayoutMethods({
beneficiary_id: 'ben_abc123',
});
// Update label or set as default
await meshtally.beneficiaries.updatePayoutMethod('pm_xyz789', {
label: 'Primary EUR account',
is_default: true,
});
// Delete (soft-delete — sets status to inactive)
await meshtally.beneficiaries.deletePayoutMethod('pm_xyz789');Settlement
// Get all balances
const balances = await meshtally.settlement.getBalances();
// Get balance for specific currency
const eurBalance = await meshtally.settlement.getBalance('EUR');
// Get settlement ledger
const ledger = await meshtally.settlement.getLedger({ currency: 'EUR', limit: 100 });Account
const account = await meshtally.account.retrieve();
console.log(account.name, account.status);Fees — contracted fee profiles (Pricing Intelligence)
Manage the contracted fees you pay each PSP. Profiles go through a dual-control approval
workflow — the submitter cannot also be the approver. Only approved profiles are
evaluated when routing or when calling calculate().
// Create a draft fee profile
const draft = await meshtally.fees.create({
psp_connector_id: pspId,
fee_model: 'interchange_plus',
payment_method: 'card',
card_scheme: 'visa',
merchant_country: 'AE',
fixed_fee: 0.15, // currency units, e.g. 0.15 EUR
percentage_fee: 0.025, // 2.5%
interchange_fee_percentage: 0.003,
three_ds_fee: 0.02,
cross_border_fee: 0.10,
});
// Submit for Finance approval
await meshtally.fees.submit(draft.id);
// Finance approves (must be a different user)
await meshtally.fees.approve(draft.id, 'Matches signed schedule v3');
// Estimate the fee for a given transaction context
const estimate = await meshtally.fees.calculate({
psp_connector_id: pspId,
amount: 250,
currency: 'EUR',
merchant_country: 'AE',
issuer_country: 'DE',
payment_method: 'card',
card_scheme: 'visa',
requires_3ds: true,
});
// estimate.estimated_total, estimate.breakdown.{fixed, percent, scheme, ...}
// estimate.confidence_hint: 'low' | 'medium' | 'high'Observed Fees — settlement-report import
Observed fees are the effective cost recorded on each transaction by the PSP — the
source of truth for cost_variance analytics. Import via CSV with a saved column
mapping, or map ad-hoc.
const preview = await meshtally.observedFees.previewImport({
tenant_id,
psp_connector_id: pspId,
csv_base64, // base64-encoded CSV contents
filename: 'adyen-2026-04.csv',
mapping: {
transaction_ref: 'PSP Reference',
fee_total: 'Total Fee',
settlement_currency: 'Settlement Currency',
reported_at: 'Report Date',
},
});
// preview.matched_count, preview.unmatched_count, preview.preview_unmatched_sample[]
// Commit after review
const result = await meshtally.observedFees.commitImport({ /* same params */ });
// result.inserted — count of psp_observed_fees rows; backfills observed_cost on attemptsPricing Intelligence health
const health = await meshtally.pricingHealth.get();
// health.ml_retrain.healthy, health.insights_engine.healthy,
// health.fx_rates.healthy, health.psp_fee_sync.healthy
// Deeper diagnostics if a pipe is unhealthy
const cron = await meshtally.pricingHealth.cronStatus();
// cron.pg_cron_enabled, cron.pg_net_enabled
// cron.jobs[i].jobname / schedule / last_run_at / last_status / last_return_messagePricing insights
Automatically surfaced findings from the Pricing Intelligence feedback loop —
variance alerts, savings opportunities, activation suggestions, and stale-profile
warnings. Recomputed every 2 hours on a schedule; ad-hoc via recompute().
const open = await meshtally.pricingInsights.list();
for (const i of open) {
console.log(`[${i.severity}] ${i.title}`);
console.log(i.body);
// i.kind: 'variance_alert' | 'savings_opportunity' | 'activation_suggestion' | 'profile_stale'
// i.cta_target: 'fees' | 'observed-fees' | 'cost-readiness' (where to deep-link)
}
// Clear once handled
await meshtally.pricingInsights.markActioned(open[0].id);PSP fee sync
Configure how observed fee data flows into Meshtally per PSP. Four channels are available; webhook and CSV work for any PSP, API pull needs a PSP-specific adapter (currently Stripe + Adyen).
// Enable automatic API pull for Stripe every hour
await meshtally.pspFeeSync.upsert({
psp_connector_id: stripeConnectorId,
sync_method: 'api_pull',
sync_enabled: true,
pull_frequency: 'hourly',
pull_lookback_days: 3,
});
// Inspect current config for every PSP
const configs = await meshtally.pspFeeSync.list();
// Trigger a pull right now (no need to wait for the cron)
const result = await meshtally.pspFeeSync.pullNow(stripeConnectorId);
// result.results[0].fees = number of new observed fees ingestedBIN lookup
const info = await meshtally.bin.resolve('411111');
// { card_scheme: 'visa', issuer_country: 'US', card_type: 'credit', confidence: 'medium', matched: true }The smart routing optimizer auto-enriches the transaction context when only
card_bin is provided — you do not need to call this explicitly before routing.
Cost Readiness — per-bucket gate management
Cost-aware routing is never automatic. An admin must enable a cost routing gate per
bucket (country × currency × brand × amount-bucket × PSP) after confidence reaches high.
The server rejects enabling a gate on a below-threshold bucket.
const matrix = await meshtally.costReadiness.matrix();
// matrix[i].cost_confidence_level: 'low' | 'medium' | 'high'
// Only 'high' buckets can be enabled
await meshtally.costReadiness.setGate({
psp_connector_id: pspId,
bucket_key: matrix[0].bucket_key,
enabled: true,
reason: 'Two weeks of fresh observed fees, variance within ±2%.',
});
// Force a recomputation (otherwise refreshes every 30 minutes)
await meshtally.costReadiness.refresh();Webhook Verification
Verify incoming webhook signatures (HMAC-SHA256):
import { WebhooksResource } from '@sodasoft/meshtally';
app.post('/webhooks/meshtally', express.raw({ type: 'application/json' }), (req, res) => {
try {
const event = WebhooksResource.constructEvent(
req.body,
req.headers['x-meshtally-signature'] as string,
process.env.MESHTALLY_WEBHOOK_SECRET!,
);
switch (event.payload.event_type) {
case 'payment.succeeded':
console.log('Payment captured:', event.payload.data.transaction);
break;
case 'payment.failed':
console.log('Payment failed:', event.payload.data.transaction);
break;
case 'refund.processed':
console.log('Refund completed');
break;
case 'payout.completed':
console.log('Payout sent:', event.payload.data.payout);
break;
}
res.json({ received: true });
} catch (err) {
res.status(400).send('Invalid signature');
}
});Error Handling
import { MeshtallyError, AuthenticationError, InvalidRequestError, RateLimitError, NetworkError } from '@sodasoft/meshtally';
try {
await meshtally.checkoutSessions.create({ amount: 100, currency: 'EUR' });
} catch (err) {
if (err instanceof AuthenticationError) {
// Invalid or expired API key (401/403)
} else if (err instanceof InvalidRequestError) {
// Bad parameters — check err.message for details (400/422)
} else if (err instanceof RateLimitError) {
// Too many requests — SDK retries automatically with backoff
} else if (err instanceof NetworkError) {
// DNS failure, timeout, connection reset
}
// All errors have: err.message, err.status, err.code, err.requestId
}Error Properties
| Property | Type | Description |
|----------|------|-------------|
| message | string | Human-readable error message |
| status | number | HTTP status code |
| code | string | Machine-readable error code |
| requestId | string | Server request ID for support |
Pagination
// Manual pagination
const page1 = await meshtally.transactions.list({ limit: 50, offset: 0 });
const page2 = await meshtally.transactions.list({ limit: 50, offset: 50 });
// Auto-pagination (async generator)
for await (const txn of meshtally.paginate(meshtally.transactions, 'list', { status: 'captured' })) {
console.log(txn.id, txn.amount);
}Environment
- Runtime: Node.js 18+ only (uses native
fetchandnode:crypto) - Not compatible with browsers — use the hosted checkout redirect flow for browser-based integrations
- TypeScript: Full type definitions included, strict mode compatible
- Dependencies: Zero runtime dependencies
Retry Behavior
Automatic retries with exponential backoff:
- Retried:
429(rate limited),5xx(server errors), network errors, timeouts - Not retried:
400,401,403,404,422(client errors) - Backoff: 500ms → 1s → 2s (with 0-25% jitter)
- Max retries: 3 (configurable)
Idempotency
All write operations automatically include an Idempotency-Key header (UUID v4). You can also provide your own:
const session = await meshtally.checkoutSessions.create(
{ amount: 99.99, currency: 'EUR' },
{ idempotencyKey: 'order-123-checkout' },
);Industry Examples
FX Broker — Trader Deposit
await meshtally.checkoutSessions.create({
amount: 1000, currency: 'USD',
payment_type: 'deposit',
product_category: 'trading_deposit',
customer_country: 'AE',
is_returning_customer: true,
});iGaming — Player Top-Up
await meshtally.checkoutSessions.create({
amount: 50, currency: 'EUR',
payment_type: 'deposit',
product_category: 'gaming_deposit',
channel: 'mobile',
risk_level: 'low',
});Prop Trading — Challenge Fee
await meshtally.checkoutSessions.create({
amount: 499.99, currency: 'USD',
payment_type: 'one_time',
product_category: 'challenge_fee',
product_name: '100K Funded Challenge',
price_tier: 'high',
});SaaS — Monthly Subscription
await meshtally.checkoutSessions.create({
amount: 29.99, currency: 'EUR',
payment_type: 'subscription',
subscription_interval: 'monthly',
product_name: 'Pro Plan',
is_returning_customer: true,
customer_lifetime_payments: 6,
});Changelog
v2.7.6
- Feature:
pricingHealthresource —get()returns the system-level pipeline snapshot (ML retrain / insights / FX / fee sync),cronStatus()returns the full scheduled-job diagnostic (jobs, last runs, pg_cron / pg_net availability). - Types:
PricingIntelligenceHealth,PricingIntelligenceHealthPipe,PricingIntelligenceCronStatus,PricingIntelligenceCronJob.
v2.7.5
- Feature: Learning progress visibility —
estimate_bucket_time_to_high_confidenceRPC exposes per-bucket samples, velocity, days remaining and blocking reasons so merchants can track cost-aware readiness. - Feature: Public health snapshot —
pricing_intelligence_health()RPC feeds the Pricing Intelligence section on the public/statuspage with ML retrain freshness, insights engine heartbeat, FX rates, and PSP fee sync counts. Aggregate only, no tenant-identifying data. - Feature: Every ML retrain run now logged to
ml_training_runs(success/failure, rows trained, observed vs estimated split).
v2.7.4
- Feature: ML training now ingests
observed_costfrom transaction_attempts (preferred over contract estimate). Recordscost_data_sourceandcost_observed_coverageso routing can weight the cost signal by trust. - Feature: Pricing Insights engine — automatic variance alerts, savings opportunities, activation suggestions, and stale-profile warnings. New
pricingInsightsSDK resource. - Feature: Cost-per-approved reporting backed by ML weights, broken down by PSP × country × brand with source badge (observed / mixed / estimated).
- Types:
PricingInsight,PricingInsightKind,PricingInsightSeverity,PricingInsightStatus.
v2.7.3
- Feature: Automatic fee extraction from Stripe, Adyen, and Checkout.com payment webhooks — observed cost populates on every attempt within seconds.
- Feature: Scheduled PSP API pull for settlement fees (Stripe
/v1/balance_transactions, Adyen/payoutSettlements) every 15 minutes via pg_cron. - Feature: New
pspFeeSyncresource —list(),upsert(),pullNow()— for managing per-PSP ingestion mode. - Types:
PspFeeSyncConfig,PspFeeSyncMethod,PspFeeSyncFrequency,PspFeeSyncUpsertParams,PspFeeSyncRunResult.
v2.7.2
- Feature: Volume-based fee tiering on fee profiles (
FeeProfile.volume_tiers[]) — server picks the right tier from rolling 30-day volume and tx count. - Feature: Contract CSV import —
fees.previewCsvImport()/fees.commitCsvImport(). Rows land as drafts; Finance approval preserved. - Feature:
bin.resolve(bin)resource, and auto-enrichment insidesmart-routing-optimizerwhen onlycard_binis supplied. - Feature: Mid-market FX rate conversion —
calculate_transaction_costconverts transaction amount to settlement currency before applying percent fees;fx_rate_appliedandamount_in_settlementadded to the response. - Types:
FeeVolumeTier,BinLookupResult,ContractImportParams,ContractImportPreview,ContractImportResult.
v2.7.1
- Feature (Pricing Intelligence): New
fees,observedFeesandcostReadinessresources.fees.list / get / create / submit / approve / reject / archive / calculate— contracted fee profiles with dual-control approval (DB trigger blocks self-approval).observedFees.list / coverage / imports / previewImport / commitImport— ingest PSP settlement reports via CSV with reusable column-mapping templates; observed fees take priority over contracted estimates in the confidence engine.costReadiness.matrix / refresh / gates / setGate— per-bucket readiness signals (approved profile, sample count, coverage %, freshness) and gated cost-aware routing activation. Enabling a gate below the required confidence is rejected server-side.
- Feature (Routing): Smart routing optimizer now returns
cost_estimate,cost_estimate_breakdown,cost_confidence_level,effective_goal,cost_gate_applied,bucket_keyanddecision_explanationin every decision. - Types: Exported
FeeProfile,FeeProfileStatus,FeeEstimate,FeeEstimateBreakdown,ObservedFee,SettlementImportParams,SettlementImportPreview,SettlementImportResult,CostReadinessBucket,CostRoutingGate,CostRoutingGateToggleParams,CostConfidenceLevel,RoutingMode.
v2.6.2
- Security: Removed email-based beneficiary filtering to prevent enumeration attacks. Use
external_idinstead.
v2.6.1
- Security: Debug logging now redacts sensitive fields (IBAN, email, wallet address, card numbers, phone).
- Safety: Production warning printed to stderr when
debug: trueused withsk_live_key orenvironment: 'production'.
v2.6.0
- Feature: Payout method management —
addPayoutMethod(),retrievePayoutMethod(),listPayoutMethods(),updatePayoutMethod(),deletePayoutMethod(). - Feature: New types:
PayoutMethod,PayoutMethodStatus,CreatePayoutMethodParams,UpdatePayoutMethodParams,ListPayoutMethodsParams. - Server-side validation per method type (bank IBAN, crypto wallet, card last 4, mobile number).
- 11 new validation tests (77 total).
v2.5.0
- Initial public release with checkout sessions, transactions, refunds, payouts (including batch), beneficiaries, settlement, account, and webhook verification.
Links
License
MIT — Sodasoft LLC
