@maxnate/provider-snippe
v0.3.1
Published
Snippe.sh payment provider for @maxnate/payments-core. Supports Mobile Money (M-Pesa, Airtel, Mixx, Halotel), Card, and Dynamic QR payments in Tanzania.
Maintainers
Readme
@maxnate/provider-snippe
Snippe.sh payment provider for @maxnate/payments-core.
Supports receiving payments only (collection). Payouts/disbursements are not part of this package.
Payment Types
| Type | paymentType | Currency | Flow |
|---|---|---|---|
| Mobile Money | "mobile" | TZS | USSD push to customer phone |
| Card | "card" | TZS | Redirect to payment_url |
| Dynamic QR | "dynamic-qr" | TZS | Scan QR code with mobile money app |
Hosted Sessions
Snippe Sessions are now supported as an optional hosted-checkout mode.
- enable with
credentials.useSessions = 'true' - optionally set
profileIdandallowedMethodsin the gateway config createPaymentIntent()returnsredirectUrl = checkout_urlplus short-link metadata when Sessions mode is active- additional helpers:
getSession(),listSessions(),cancelSession(),cancelAttempt(),createPaymentLink()
UI Integration Guide
This provider supports two UI models. What you can build depends on which one you choose.
Shared Terminology
- Customer UI: the payment or checkout experience in your own application.
- Admin UI: your tenant/operator-facing setup and operations screens.
- Provider-hosted checkout: Snippe's hosted Sessions/checkout page.
- Provider dashboard: Snippe dashboard for API keys, webhook secret, and payment profiles.
1. Fully Custom UI In Your Project
Use Snippe direct payments when you want your own checkout screens inside your app.
- You control: payment method picker, form layout, branding, validation, order summary, success/cancel pages, and overall customer journey in your frontend.
- You can build:
- your own mobile-money checkout page that asks for phone number then triggers USSD push
- your own card handoff page before redirecting to Snippe/card gateway
- your own QR payment page that renders the returned QR data in your UI
- your own payment status polling / instructions UI around the Snippe transaction
- You still rely on Snippe for: payment processing, webhook events, account balance, provider-side ledger, and the actual downstream payment rails.
- Best when: you want a branded product experience and your team is comfortable owning the checkout UX in your application.
2. Snippe Hosted Checkout UI
Use Sessions when you want Snippe to host the checkout page.
- You control: the page before redirect, session creation options, redirect destination, allowed methods, line items, custom fields, and whether to use full checkout URL or short payment link.
- Snippe controls: the actual hosted checkout page UI, payment form, and payment-attempt screens.
- You can build in your project:
- a checkout launch page that creates the session and redirects to
checkout_url - a share flow using
payment_link_urlfor WhatsApp/SMS/email - a post-payment success page driven by your
redirect_url - an admin setup screen that stores
profileId,allowedMethods, anduseSessions
- a checkout launch page that creates the session and redirects to
What You Can Customize
- In your own app UI: everything before and after payment handoff.
- In direct-payment mode: almost the entire customer-facing experience in your frontend.
- In Sessions mode:
allowed_methodsredirect_urlwebhook_urldescriptionline_itemscustom_fieldsdisplaybillingprofile_id
What You Cannot Customize Through This Package
- You cannot CRUD Snippe payment profiles from API in
2026-01-25; profiles are managed in the Snippe dashboard. - You cannot fully replace Snippe's hosted checkout page UI when using Sessions.
- You should not treat
url_metadataas secret data; it is transport metadata, not encrypted application state.
Dashboard vs. Project UI
- Use your project UI for: custom checkout pages, payment-method selection, order context, manual payment guidance, and branded customer journeys.
- Use the Snippe dashboard for: API keys, webhook signing key, payment profile creation/editing, and provider-account operational settings.
Recommended Pattern
- Choose direct payments if you want a fully custom UI in your application.
- Choose Sessions if you want faster rollout, lower checkout implementation effort, and are comfortable with Snippe hosting the payment page.
Authentication
Snippe uses Bearer token authentication. The provider includes the API key on every request.
| Header | Value |
|---|---|
| Authorization | Bearer <apiKey> |
| Idempotency-Key | Auto-generated as a hashed key derived from tenantId + orderId (max 30 chars) |
Credentials
| Field | Description |
|---|---|
| apiKey | Snippe API key. Get it from Settings > API Keys in the Snippe dashboard. |
| webhookSecret | Optional. Webhook signing key (whsec_...). Used for HMAC verification. |
Usage
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,
testMode: false,
credentials: { apiKey: process.env.SNIPPE_API_KEY! },
webhookSecret: process.env.SNIPPE_WEBHOOK_SECRET,
currencies: ['TZS'], countries: ['TZ']
})
// Mobile Money (USSD push)
const result = await registry.createPaymentIntent('snippe', {
orderId: 'ORD-001', amount: 5000, currency: 'TZS',
paymentType: 'mobile',
customerEmail: '[email protected]',
customerPhone: '255712345678',
metadata: { webhook_url: 'https://site.com/webhooks/snippe' }
})
// Card (redirect to checkout)
const cardResult = await registry.createPaymentIntent('snippe', {
orderId: 'ORD-002', amount: 25000, currency: 'TZS',
paymentType: 'card',
returnUrl: 'https://site.com/success',
cancelUrl: 'https://site.com/cancel',
customerAddress: {
address: '123 Street', city: 'Dar es Salaam',
state: 'DSM', postcode: '14101', country: 'TZ'
}
})
// Dynamic QR
const qrResult = await registry.createPaymentIntent('snippe', {
orderId: 'ORD-003', amount: 10000, currency: 'TZS',
paymentType: 'dynamic-qr'
})
// → qrResult.qrCodeData contains the QR code data string
// Hosted Session (Snippe UI)
const sessionResult = await registry.createPaymentIntent('snippe', {
orderId: 'ORD-004', amount: 15000, currency: 'TZS',
metadata: {
useSessions: true,
description: 'Order #004',
allowed_methods: ['mobile_money', 'qr'],
line_items: [{ name: 'Premium Package', quantity: 1, unit_price: 15000 }]
}
})
// -> redirect the customer to sessionResult.redirectUrlWebhook Handling
Snippe sends webhook events with HMAC-SHA256 signature verification and replay attack protection (5 minute window).
import { handlePaymentWebhook } from '@maxnate/payments-core'
app.post('/webhooks/snippe', async (req, res) => {
const rawBody = JSON.stringify(req.body) // RAW body, not parsed JSON
const signature = req.headers['x-webhook-signature']
const headers = {
'x-webhook-timestamp': req.headers['x-webhook-timestamp'],
'x-webhook-event': req.headers['x-webhook-event']
}
const result = await handlePaymentWebhook(
rawBody, signature, 'snippe', 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
},
headers
)
res.status(result.success ? 200 : 400).send(result.success ? 'OK' : result.error)
})Webhook Events
| Snippe Event | Mapped Type |
|---|---|
| payment.completed | payment.succeeded |
| payment.failed | payment.failed |
| payment.voided | payment.cancelled |
| payment.expired | payment.failed |
Admin Discovery And Self-Test
SnippeProvider.getCapabilities()exposes a machine-readable descriptor for admin UIs, including supported methods, currencies, credentials, and webhook URL template.SnippeProvider.selfTest(config)validates credentials withGET /v1/payments/balance, so hosts can check connectivity without creating a real payment.SnippeProvider.getBalance(),listPayments(),searchPayments(), andpushUssd(reference)expose the main direct-payment admin operations from the Snippe2026-01-25API.SnippeProvideralso exposes hosted-checkout helpers for Sessions, Payment Links, and session cancellation flows.
Enriched Webhook Data
handleWebhook() now maps additional Snippe fields into the normalized event surface so hosts can persist them without digging through raw payloads:
externalReferencefailureReasonsettlement.gross / fees / netchannel.type / provider- decoded
metadata.url_metadata - customer details copied into
providerData.customer
Status Mapping
| Snippe Status | Mapped Status |
|---|---|
| pending | pending |
| completed | succeeded |
| failed | failed |
| voided | cancelled |
| expired | failed |
Requirements
- Node.js >= 18
@maxnate/payments-core^0.3.0
