@maxnate/payments-core
v0.3.0
Published
Framework-agnostic payment provider abstraction. Define adapters, register providers, handle webhooks — zero runtime dependencies.
Maintainers
Readme
@maxnate/payments-core
Zero-dependency TypeScript library for building payment provider plugins. Define adapters, register providers, handle webhooks — framework-agnostic, works in Node.js, Edge, Deno, and Bun.
npm install @maxnate/payments-coreWhy payments-core?
| Concern | payments-core handles | You handle |
|---|---|---|
| Provider interface | PaymentProviderAdapter with lifecycle hooks | Implementing per provider |
| Registry | createPaymentProviderRegistry() — register, configure, route | Creating and wiring the registry |
| Webhook orchestration | handlePaymentWebhook() — verify, dedup, update | Providing minimal DB callbacks |
| Crypto | HMAC-SHA256, timing-safe compare — Web Crypto API | Nothing |
| Idempotency | In-memory key dedup with configurable TTL | Nothing |
| Validation | Amount, currency, min/max config checks | Nothing |
Available Providers
| Package | Provider | Currencies | Countries | Payment Methods |
|---|---|---|---|---|
| @maxnate/provider-snippe | Snippe.sh | TZS | TZ | Mobile Money, Card, Dynamic QR |
| @maxnate/provider-clickpesa | ClickPesa | TZS, USD | TZ | Mobile Money, Card, BillPay, Checkout Link |
| @maxnate/provider-selcom | Selcom | TZS | TZ | Mobile Money, Card, Utility Payments, Wallet Pull, Selcom Pesa |
Quick Start
import { createPaymentProviderRegistry } from '@maxnate/payments-core'
import { SnippeProvider } from '@maxnate/provider-snippe'
const registry = createPaymentProviderRegistry()
registry.register(new SnippeProvider())
await registry.setConfig({
id: 'snippe', name: 'Snippe', type: 'mobile_money', enabled: true,
credentials: { apiKey: process.env.SNIPPE_API_KEY! },
currencies: ['TZS'], countries: ['TZ']
})
const result = await registry.createPaymentIntent('snippe', {
orderId: 'ORD-001', amount: 5000, currency: 'TZS',
customerEmail: '[email protected]'
})Architecture
@maxnate/payments-core ← This package (interface + registry + webhooks)
@maxnate/provider-snippe ← Adapter implementing PaymentProviderAdapter
@maxnate/provider-clickpesa ← Adapter
@maxnate/provider-selcom ← Adapter
@maxnate/provider-* ← Community adaptersKey Concepts
PaymentProviderAdapter
interface PaymentProviderAdapter {
providerId: string
providerName: string
initialize(config: PaymentProviderConfig): void | Promise<void>
createPaymentIntent(input: CreatePaymentIntentInput): Promise<PaymentResult>
getPaymentIntent(intentId: string): Promise<PaymentIntent>
cancelPayment(intentId: string): Promise<PaymentResult>
refundPayment(intentId: string, amount?: number, reason?: string): Promise<RefundResult>
verifyWebhook?(payload, signature, headers?): Promise<boolean>
handleWebhook?(payload, signature?, headers?): Promise<WebhookEvent>
supportsFeature?(feature: keyof PaymentProviderFeature): boolean
}Registry
registry.register(provider) // Add a provider
registry.unregister(providerId) // Remove a provider
registry.get(providerId) // Get by ID
registry.getEnabled() // All enabled providers
registry.getByCurrency('TZS') // Filter by currency
registry.getByType('mobile_money') // Filter by type
registry.setConfig(config) // Configure a provider (async — awaits provider.initialize)
registry.createPaymentIntent(...) // Validate + route to providerWebhook Handler
const result = await handlePaymentWebhook(
rawBody, // Raw body as string
signature, // Signature header value
providerId, // e.g. 'snippe'
registry, // Provider registry
{
findPaymentIntent: (id) => db.payments.findById(id),
updatePaymentIntent: (id, data) => db.payments.update(id, data),
idempotencyStore, // Replica-safe; system-core's IdempotencyStore satisfies the duck-typed shape
onPaymentStatusChange: (intentId, status) => emit('payment.status', { intentId, status })
},
headers // Optional — forwarded to provider verifyWebhook
)
// Returns: { success, event?, error? }API
Core Functions
| Function | Returns | Description |
|---|---|---|
| createPaymentProviderRegistry() | PaymentProviderRegistry | Create a new registry instance |
| handlePaymentWebhook(payload, sig, id, registry, deps, headers?) | WebhookResult | Process incoming webhook |
Crypto Utilities
import { hmacSha256Hex, timingSafeEqualHex, verifyWebhookSignature, toBase64 } from '@maxnate/payments-core'All functions use the Web Crypto API — no Node.js dependencies.
Creating a Provider
import { PaymentProviderAdapter, PaymentProviderConfig, PaymentResult } from '@maxnate/payments-core'
export class MyProvider implements PaymentProviderAdapter {
providerId = 'my-provider'
providerName = 'My Provider'
// ... implement all methods
}See src/provider-template.ts for a base class with defaults.
Documentation
| Guide | File |
|---|---|
| Architecture | docs/ARCHITECTURE.md |
| E-Commerce Integration | docs/ECOMMERCE-INTEGRATION.md |
| Membership / Subscriptions | docs/MEMBERSHIP-INTEGRATION.md |
| Donations | docs/DONATION-INTEGRATION.md |
| Booking / Deposit | docs/BOOKING-DEPOSIT-INTEGRATION.md |
| Unified Payment Pattern | docs/UNIFIED-PATTERN.md |
Requirements
- Node.js >= 18 (or any runtime with Web Crypto API)
- TypeScript 5.x recommended
- Zero runtime dependencies
License
MIT
