@ossamve/pvit-payment-sdk
v0.2.0
Published
Node.js SDK for the PVIT payment API
Downloads
26
Maintainers
Readme
@ossamve/pvit-payment-sdk
Node.js/TypeScript SDK for the PVIT payment API (Mobile Money, XAF).
Installation
npm install @ossamve/pvit-payment-sdkQuick start
import { PvitClient } from '@ossamve/pvit-payment-sdk'
const pvit = new PvitClient({
transactionCodeUrl: {
live: 'mon-commerce',
test: 'mon-commerce-test',
},
secretCodeUrl: {
live: 'mon-commerce',
test: 'mon-commerce-test',
},
operationAccount: {
moov: 'ACC_MOOV_XXXXX',
airtel: 'ACC_AIRTEL_XXXXX',
test: 'ACC_TEST_XXXXX', // used in sandbox mode
},
callbackUrlCode: 'cb-production',
receptionCodeUrl: 'reception-01',
accountPassword: process.env.PVIT_PASSWORD!,
// secretKey: 'sk_live_xxx' — optional at startup, see Bootstrap below
})Bootstrap — getting your first secret key
PVIT does not expose a secret key from the dashboard. You must request the first renewal:
// 1. Trigger key delivery — PVIT sends the key to your reception URL
await pvit.auth.renewSecretKey({
operationAccountCode: 'ACC_XXXXX',
receptionUrlCode: 'reception-01',
password: process.env.PVIT_PASSWORD,
})
// 2. In your reception URL handler, store the delivered key:
app.post('/pvit/secret', (req, res) => {
pvit.auth.updateSecretKey(req.body.secret)
res.sendStatus(200)
})Initiating a payment
The operator is auto-detected from the phone number prefix (06 → MOOV_MONEY, 07 → AIRTEL_MONEY):
const result = await pvit.payments.initiatePayment({
amount: 5000, // XAF, minimum 500
reference: 'CMD-001', // max 15 chars, must be unique
customerAccountNumber: '066820866',
})
console.log(result.status) // 'PENDING'
console.log(result.reference_id) // 'PAY240420250001'initiatePayment() always returns PENDING. The final result (SUCCESS or FAILED) arrives via webhook.
You can also pass the operator explicitly when needed:
await pvit.payments.initiatePayment({
amount: 5000,
reference: 'CMD-001',
customerAccountNumber: '066820866',
operatorCode: 'MOOV_MONEY', // must be paired with:
merchantOperationAccountCode: 'ACC_MOOV_XX', // both or neither
})Give change (refund / disbursement)
await pvit.payments.giveChange({
amount: 2000,
reference: 'RMB-001',
customerAccountNumber: '066820866',
})Handling webhooks
app.post('/pvit/callback', (req, res) => {
const event = pvit.webhooks.parse(req.body)
console.log(event.status) // 'SUCCESS' | 'FAILED'
console.log(event.amount) // 5000
console.log(event.operator) // 'MOOV_MONEY'
// Acknowledgment is mandatory in production
res.json(pvit.webhooks.acknowledge(event.transactionId))
})Secret key rotation (cron every ~55 min)
The secret key expires after 3600 seconds. Schedule a renewal before expiry:
import cron from 'node-cron'
cron.schedule('*/55 * * * *', async () => {
await pvit.auth.renewSecretKey({
operationAccountCode: 'ACC_XXXXX',
receptionUrlCode: 'reception-01',
password: process.env.PVIT_PASSWORD,
})
// New key is delivered to your reception URL handler → updateSecretKey()
})Error handling
import {
PvitValidationError,
PvitNetworkError,
PvitError,
} from '@ossamve/pvit-payment-sdk'
try {
await pvit.payments.initiatePayment({ ... })
} catch (err) {
if (err instanceof PvitValidationError) {
console.error(err.issues) // [{ field: 'amount', message: '...' }]
} else if (err instanceof PvitNetworkError) {
console.error('Timeout or network failure')
} else if (err instanceof PvitError) {
console.error(err.statusCode, err.message)
}
}API reference
new PvitClient(config)
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| transactionCodeUrl | { live: string, test: string } | ✅ | URL codes for payment transactions |
| secretCodeUrl | { live: string, test: string } | ✅ | URL codes for secret key renewal |
| operationAccount | { moov?: string, airtel?: string, test: string } | ✅ | Merchant operation account codes per operator |
| callbackUrlCode | string | ✅ | Callback URL code registered on PVIT dashboard |
| receptionCodeUrl | string | ✅ | Reception URL code for secret key delivery |
| accountPassword | string | ✅ | PVIT account password |
| secretKey | string | ❌ | Secret key — omit on first run, set via auth.updateSecretKey() |
| sandbox | boolean | ❌ | Use sandbox base URL (default: false) |
| timeout | number | ❌ | Request timeout in ms (default: 30000) |
pvit.payments.initiatePayment(params) → PvitTransactionResponse
pvit.payments.giveChange(params) → PvitTransactionResponse
pvit.webhooks.parse(raw) → PvitWebhookPayload
pvit.webhooks.acknowledge(transactionId) → PvitWebhookAck
pvit.auth.renewSecretKey(params) → RenewSecretKeyResponse
pvit.auth.updateSecretKey(newKey) → void
License
MIT
