@payscribe/sdk
v0.3.3
Published
Node.js/TypeScript SDK and local Swagger UI playground for Payscribe virtual accounts, virtual cards, and bills payments.
Maintainers
Readme
Payscribe SDK
Node.js and TypeScript SDK for Payscribe virtual account collections, virtual card issuing, and bills payments.
npm install @payscribe/sdkimport { Payscribe } from "@payscribe/sdk";
const payscribe = new Payscribe({
secretKey: process.env.PAYSCRIBE_SECRET_KEY!,
environment: "sandbox",
});Quick Links
Features
- Static and dynamic NGN virtual accounts
- Virtual account lookup, activation, deactivation, payment confirmation, and sandbox transfer simulation
- USD virtual card creation, top-up, withdrawal, replacement, freeze, unfreeze, and termination
- Stablecoin-funded virtual card creation and top-up
- Card transaction lookup and cardholder contact updates
- Bills lookup, validation, vending, and requery flows
- Webhook signature verification
- TypeScript request/response types
- Local Swagger playground and typed consumer test app
Value Guide
Use these values in the examples below:
customerId: the ID of a customer already registered under your Payscribe business.reference: your unique transaction/order reference. Generate a new one per operation.banks: virtual account bank providers, for example["9psb"]or["palmpay"].currency: currentlyNGNfor virtual accounts andUSDfor virtual cards unless Payscribe enables more currencies for your account.brand: card brand, usuallyVISAorMASTERCARD.type: card type, usuallyvirtual.amount: amount to debit or fund. Use the unit expected by the Payscribe API for that endpoint.servicefor cable:dstv,gotv,startimes, ordstvshowmax.network:mtn,glo,airtel, or9mobile.meterType:prepaidorpostpaid.electricity service: common values includeikedc,ekedc,eedc,phedc,aedc,ibedc,kedco, andjed.planId,plan,sku,providerCode: product identifiers returned by Payscribe lookup/list endpoints. Do not hardcode demo values in production.
Requirements
- Node.js 18+
- A Payscribe secret API key
Install
For application usage:
npm install @payscribe/sdkFor local development in this repository:
npm installEnvironment
Create a .env file:
cp .env.example .envSet:
PAYSCRIBE_SECRET_KEY=ps_sk_test_YOUR_SECRET_KEY
PAYSCRIBE_ENVIRONMENT=sandbox
PORT=3000Supported environments:
"sandbox" | "production";Environment URLs:
sandbox -> https://sandbox.payscribe.ng/api/v1/
production -> https://api.payscribe.ng/api/v1/If environment is omitted, the SDK defaults to sandbox.
Client
import { Payscribe } from "@payscribe/sdk";
const payscribe = new Payscribe({
secretKey: process.env.PAYSCRIBE_SECRET_KEY!, // Your Payscribe secret key. Keep this server-side only.
environment: "sandbox", // Use "sandbox" for testing or "production" for live requests.
});Custom base URL for internal testing:
const payscribe = new Payscribe({
secretKey: process.env.PAYSCRIBE_SECRET_KEY!, // Your Payscribe secret key.
baseUrl: "https://sandbox.payscribe.ng/api/v1/", // Optional. Use only for trusted internal testing.
});Create Static Virtual Account
Use this for a permanent reusable account tied to an existing Payscribe customer.
const account = await payscribe.virtualAccounts.createStatic({
customerId: "customer-uuid", // Required. ID of a customer already created/registered on Payscribe.
banks: ["9psb"], // Required. Bank provider code. Common values: "9psb", "palmpay".
currency: "NGN", // Optional. Defaults to NGN where supported.
});With Palmpay identity fields:
const account = await payscribe.virtualAccounts.createStatic({
customerId: "customer-uuid", // Required. ID of a customer already created/registered on Payscribe.
banks: ["palmpay"], // Palmpay requires an identity object.
currency: "NGN", // Optional. Defaults to NGN where supported.
identity: {
type: "bvn", // Required for Palmpay. Common values: "bvn" or "nin".
number: "2233990011", // Required. Customer BVN/NIN matching the selected identity type.
},
});Type:
import type { CreateStaticVirtualAccountInput } from "@payscribe/sdk";
const body: CreateStaticVirtualAccountInput = {
customerId: "customer-uuid", // Required. Existing Payscribe customer ID.
banks: ["9psb"], // Required. One or more supported bank provider codes.
currency: "NGN", // Optional.
};Create Dynamic Virtual Account
Use this for checkout/order payments with an expiry window.
const account = await payscribe.virtualAccounts.createDynamic({
reference: "order_123", // Required. Your unique order/transaction reference.
amount: 2500, // Required. Amount expected from the customer.
amountType: "EXACT", // Optional. "EXACT" requires the exact amount; "ANY" allows any amount.
description: "Payment for order_123", // Optional. Shown in payment context where supported.
currency: "NGN", // Optional. Defaults to NGN where supported.
expiresIn: {
duration: 1, // Required. Expiry duration value.
type: "hours", // Required. Accepted examples: "minutes", "minute", "hours", "hour".
},
customer: {
name: "Ada Lovelace", // Required. Customer name for the payment.
email: "[email protected]", // Required. Customer email.
phone: "08099228833", // Required. Customer phone number.
},
});amountType can be:
"EXACT" | "ANY";Type:
import type { CreateDynamicVirtualAccountInput } from "@payscribe/sdk";
const body: CreateDynamicVirtualAccountInput = {
reference: "order_123", // Required. Must be unique for the transaction/order.
amount: 2500, // Required. Amount expected.
amountType: "EXACT", // Optional. "EXACT" or "ANY".
expiresIn: {
duration: 1, // Required.
type: "hours", // Required. "minutes" | "minute" | "hours" | "hour".
},
customer: {
name: "Ada Lovelace", // Required.
email: "[email protected]", // Required.
phone: "08099228833", // Required.
},
};Other Virtual Account Methods
const account = await payscribe.virtualAccounts.get("5031240100"); // Virtual account number to retrieve.
await payscribe.virtualAccounts.deactivate("5031240100"); // Disable receiving payments on this account.
await payscribe.virtualAccounts.activate("5031240100"); // Re-enable a deactivated account.
const payment = await payscribe.virtualAccounts.confirmPayment({
sessionId: "100004240807072606117680115283", // NIP/session ID from the bank transfer/payment notification.
amount: 5000, // Amount received.
accountNumber: "5300000217", // Payscribe virtual account number that received the payment.
});
const paymentWithTransaction = await payscribe.virtualAccounts.confirmPayment({
transId: "transaction-id", // Optional Payscribe transaction ID if already known.
sessionId: "100004240807072606117680115283", // NIP/session ID.
amount: 5000, // Amount received.
accountNumber: "5300000217", // Receiving virtual account number.
});Simulate Transfer
For sandbox testing static account transfers:
const simulation = await payscribe.virtualAccounts.simulateTransfer({
reference: "test-ref", // Required. Unique sandbox test reference.
amount: "4500.00", // Required. Amount to simulate.
description: "A test transfer", // Required. Transfer narration.
currency: "NGN", // Optional. Defaults to NGN where supported.
account: "4804760006", // Required. Static virtual account number to credit.
name: "Ada Lovelace", // Required. Receiver/account name.
bank: "120001", // Required. Bank code used by the sandbox simulation endpoint.
senderAccountNumber: "1100000309", // Required. Simulated sender account number.
senderName: "Ada Lovelace", // Required. Simulated sender name.
hash: "generated-hash", // Required. Sandbox hash generated according to Payscribe simulation docs.
});Create Virtual Card
Use this to issue a USD virtual card for an existing Payscribe customer.
const card = await payscribe.virtualCards.create({
customerId: "customer-uuid", // Required. Existing Payscribe customer ID.
currency: "USD", // Optional. Payscribe cards currently use USD unless otherwise enabled.
brand: "VISA", // Required. Accepted common values: "VISA" or "MASTERCARD".
amount: 10, // Required. Initial card funding amount.
type: "virtual", // Optional. Usually "virtual".
reference: "card_ref_123", // Required. Your unique card creation reference.
});For a contactless card, include contactless and optional card limits:
const card = await payscribe.virtualCards.create({
customerId: "customer-uuid", // Required. Existing Payscribe customer ID.
brand: "MASTERCARD", // Required. Accepted common values: "VISA" or "MASTERCARD".
amount: 25, // Required. Initial card funding amount.
reference: "contactless_card_ref_123", // Required. Unique reference.
contactless: true, // Optional. Enables contactless card support where available.
cardLimits: {
dailyLimit: 100, // Optional. Maximum daily spend allowed on the card.
transactionLimit: 25, // Optional. Maximum single transaction amount.
},
});Type:
import type { CreateVirtualCardInput } from "@payscribe/sdk";
const body: CreateVirtualCardInput = {
customerId: "customer-uuid", // Required. Existing Payscribe customer ID.
brand: "VISA", // Required. "VISA" or "MASTERCARD".
amount: 10, // Required. Initial funding amount.
reference: "card_ref_123", // Required. Unique reference.
};Card Creation Using Stablecoin
const order = await payscribe.virtualCards.createWithStablecoin({
customerId: "customer-uuid", // Required. Existing Payscribe customer ID.
currency: "USD", // Optional. Card currency.
brand: "VISA", // Required. "VISA" or "MASTERCARD".
amount: 5, // Required. Card funding amount expected from the stablecoin payment.
type: "virtual", // Optional. Usually "virtual".
reference: "stablecoin_card_ref_123", // Required. Unique reference.
stablecoinCurrency: "USDT", // Optional. Common values: "USDT", "USDC".
stablecoinNetwork: "Tron", // Optional. Common values: "Tron", "Ethereum", "BNB".
stablecoinChain: "TRC20", // Optional. Common values: "TRC20", "ERC20", "BEP20".
});Payscribe returns a deposit address and amount. The card is created after the stablecoin deposit is confirmed.
Card Funding And Withdrawals
In the examples below, card-uuid is the card ID returned by Payscribe when the card was created.
await payscribe.virtualCards.topUp("card-uuid", {
amount: 10, // Required. Amount to add to the card.
reference: "topup_ref_123", // Optional but recommended. Unique top-up reference.
});
await payscribe.virtualCards.topUpWithStablecoin("card-uuid", {
amount: 10, // Required. Amount to add to the card after stablecoin payment.
reference: "stablecoin_topup_ref_123", // Optional but recommended. Unique reference.
stablecoinCurrency: "USDT", // Optional. "USDT" or "USDC".
stablecoinNetwork: "Tron", // Optional. "Tron", "Ethereum", or "BNB".
stablecoinChain: "TRC20", // Optional. "TRC20", "ERC20", or "BEP20".
});
await payscribe.virtualCards.withdraw("card-uuid", {
amount: 5, // Required. Amount to withdraw from the card.
reference: "withdraw_ref_123", // Required. Unique withdrawal reference.
});Card Lookup And Controls
Use the Payscribe card ID for every card lookup/control method.
const card = await payscribe.virtualCards.get("card-uuid"); // Card ID returned by Payscribe when the card was created.
const transactions = await payscribe.virtualCards.transactions("card-uuid", {
startDate: "2024-07-01", // Required. Start date for transaction search.
endDate: "2024-07-30", // Required. End date for transaction search.
pageSize: 20, // Optional. Number of records per page.
page: 1, // Optional. Page number.
});
await payscribe.virtualCards.freeze("card-uuid", {
reference: "freeze_ref_123", // Required. Unique freeze reference.
});
await payscribe.virtualCards.unfreeze("card-uuid", {
reference: "unfreeze_ref_123", // Required. Unique unfreeze reference.
});
await payscribe.virtualCards.terminate("card-uuid", {
reference: "terminate_ref_123", // Required. Unique termination reference.
});
await payscribe.virtualCards.replace("card-uuid"); // Replace a card by its Payscribe card ID.
await payscribe.virtualCards.regularize("card-uuid"); // Alias for replace, kept for API naming compatibility.Update Virtual Card Contact
Use this when you need to update the cardholder contact or billing details attached to an issued card.
const result = await payscribe.virtualCards.updateContact("card-uuid", {
email: "[email protected]", // Optional. New cardholder email.
mobile: "+2348012345678", // Optional. New cardholder phone number.
billingDetails: {
address1: "12 Broad Street", // Optional. Billing street address.
address2: "Suite 4", // Optional. Additional address line.
city: "Lagos", // Optional. Billing city.
state: "Lagos", // Optional. Billing state.
zipcode: "100001", // Optional. Postal/ZIP code.
country: "NG", // Optional. ISO country code.
},
});Bills are exposed under payscribe.bills, grouped by product category.
Cable TV
const bouquets = await payscribe.bills.cable.fetchBouquets({
service: "dstv", // Required. Cable provider. Common values: "dstv", "gotv", "startimes", "dstvshowmax".
});
const validation = await payscribe.bills.cable.validateSmartCard({
service: "dstv", // Required. Same provider selected by the customer.
account: "8062415043", // Required. Customer smartcard/IUC number.
month: 1, // Optional. Number of months to subscribe for.
planId: "PLAN_CODE", // Required. Plan ID returned by fetchBouquets().
});
const payment = await payscribe.bills.cable.pay({
planId: "PLAN_CODE", // Required. Plan ID returned by fetchBouquets().
customerName: "Ada Lovelace", // Required. Name returned from validation or entered by customer.
account: "8062415043", // Required. Customer smartcard/IUC number.
service: "dstv", // Required. Cable provider.
reference: "cable_ref_123", // Required. Your unique transaction reference.
phone: "08199228811", // Optional. Customer phone number.
email: "[email protected]", // Optional. Customer email.
month: 1, // Optional. Number of months.
});Data
const plans = await payscribe.bills.data.lookup({
network: "mtn", // Optional. Network provider. Common values: "mtn", "glo", "airtel", "9mobile".
category: "sme", // Optional. Plan category if supported by the provider.
});
const data = await payscribe.bills.data.vend({
network: "mtn", // Required. Network provider.
plan: "PSPLAN_177", // Required. Plan code returned by data.lookup().
recipient: ["08169254598", "07038067493"], // Required. One phone number or an array of phone numbers.
reference: "data_ref_123", // Optional but recommended. Unique transaction reference.
});Electricity
const meter = await payscribe.bills.electricity.validate({
meterNumber: "45083082250", // Required. Customer meter number.
meterType: "prepaid", // Required. "prepaid" or "postpaid".
amount: 1000, // Required. Amount to validate/pay.
service: "phedc", // Required. Disco/provider code, e.g. "ikedc", "ekedc", "eedc", "phedc", "aedc".
});
const vend = await payscribe.bills.electricity.pay({
meterNumber: "45083082250", // Required. Customer meter number.
meterType: "prepaid", // Required. "prepaid" or "postpaid".
amount: 1000, // Required. Amount to vend.
service: "phedc", // Required. Electricity provider/disco code.
customerName: "Ada Lovelace", // Required. Name returned from validation or entered by customer.
phone: "07038067493", // Optional. Customer phone number.
reference: "electricity_ref_123", // Optional but recommended. Unique transaction reference.
});Airtime
const airtime = await payscribe.bills.airtime.vend({
network: "mtn", // Required. Network provider: "mtn", "glo", "airtel", or "9mobile".
amount: 50, // Required. Airtime amount.
recipient: "08169254598", // Required. Phone number, or an array of phone numbers.
ported: false, // Optional. Set true if the number was ported to another network.
reference: "airtime_ref_123", // Optional but recommended. Unique transaction reference.
});
const bulkAirtime = await payscribe.bills.airtime.bulkVend({
rows: [
{ phone: "08169254598", amount: 50, provider: "mtn", country: "NG" }, // country is optional.
{ phone: "08012345678", amount: 100, provider: "airtel", country: "NG" },
],
reference: "bulk_airtime_ref_123", // Optional but recommended. Unique batch reference.
});Betting
const providers = await payscribe.bills.betting.listProviders();
const account = await payscribe.bills.betting.lookup({
betId: "bet9ja", // Required. Betting provider ID returned by listProviders().
customerId: "422984", // Required. Customer betting wallet/account ID, not a Payscribe customer ID.
});
const funding = await payscribe.bills.betting.fundWallet({
betId: "bet9ja", // Required. Betting provider ID.
customerId: "422984", // Required. Customer betting wallet/account ID.
customerName: "Ada Lovelace", // Required. Name returned from lookup or entered by customer.
amount: 100, // Required. Wallet funding amount.
reference: "betting_ref_123", // Required. Unique transaction reference.
});International Bills
const countries = await payscribe.bills.international.countries();
const providers = await payscribe.bills.international.providers({
iso: "GH", // Required. Destination country ISO code from countries(), e.g. "GH".
});
const products = await payscribe.bills.international.products({
iso: "GH", // Required. Destination country ISO code.
code: "MTGH", // Required. Provider code returned by international.providers().
});
const rate = await payscribe.bills.international.rate({
iso: "GH", // Required. Destination country ISO code.
sku: "GH_MT_TopUp", // Required. Product SKU returned by international.products().
amount: 1.2, // Required. Amount to price/convert.
});
const internationalVend = await payscribe.bills.international.vend({
iso: "GH", // Required. Destination country ISO code.
providerCode: "MTGH", // Required. Provider code returned by international.providers().
sku: "GH_MT_TopUp", // Required. Product SKU returned by international.products().
amount: "1.5", // Required. Amount to vend.
account: "233547011800", // Required. Recipient account/phone/customer identifier required by the product.
debitCurrency: "ngn", // Optional. Currency to debit from your wallet where supported.
reference: "intl_ref_123", // Required. Unique transaction reference.
});Other Bills Helpers
await payscribe.bills.internet.listServices();
await payscribe.bills.internet.spectranetPinPlans();
await payscribe.bills.internet.purchaseSpectranetPins({
planId: "PSPLAN_1270", // Required. Plan ID returned by spectranetPinPlans().
quantity: 1, // Required. Number of pins to buy.
reference: "spectranet_ref_123", // Required. Unique transaction reference.
});
await payscribe.bills.epins.list();
await payscribe.bills.epins.purchase({
id: "neco", // Required. E-pin product ID returned by epins.list().
quantity: 1, // Required. Number of pins to buy.
reference: "epin_ref_123", // Required. Unique transaction reference.
});
await payscribe.bills.airtimeToWallet.lookup();
await payscribe.bills.sms.send({
to: "08169254598", // Required. Recipient phone number.
message: "Hello Payscribe!", // Required. SMS body.
reference: "sms_ref_123", // Optional but recommended. Unique transaction reference.
});
await payscribe.bills.requery({
transactionId: "transaction-id", // Required. Payscribe transaction ID to requery.
});Webhook Verification
Payscribe webhooks include an X-Payscribe-Signature header. Verify it against the raw request body before processing the event.
const valid = payscribe.webhooks.verifySignature(
rawBody,
req.headers["x-payscribe-signature"] as string,
);
if (!valid) {
throw new Error("Invalid webhook signature");
}Parse and verify in one call:
const event = payscribe.webhooks.constructEvent({
rawBody,
signature: req.headers["x-payscribe-signature"],
});
if (event.event_type === "accounts.payment.status") {
// Process virtual account payment.
}Important: use the raw request body for webhook verification, not a re-stringified JSON object.
Errors
Failed Payscribe API responses throw PayscribeApiError.
import { PayscribeApiError } from "@payscribe/sdk";
try {
await payscribe.virtualAccounts.get("5031240100");
} catch (error) {
if (error instanceof PayscribeApiError) {
console.log(error.statusCode);
console.log(error.message);
console.log(error.response);
}
}Local Swagger Playground
The repository includes a local Express server with Swagger UI. It calls the SDK internally for virtual account, virtual card, bills, and webhook requests.
npm run devOpen:
http://localhost:3000/docsIf port 3000 is busy:
PORT=3001 npm run devTyped Consumer Test App
consumer-test/ is a separate TypeScript Node.js app that installs the published SDK from npm and imports it like a real external user.
Because the generic package name is @payscribe/sdk, publish that package name first before running the consumer install flow from a clean machine.
From the SDK root:
cd consumer-test
npm install
cp .env.example .envSet consumer-test/.env:
PAYSCRIBE_SECRET_KEY=ps_sk_test_YOUR_SECRET_KEY
PAYSCRIBE_ENVIRONMENT=sandbox
PORT=4001Start the consumer server:
npm startOpen the consumer Swagger UI:
http://localhost:4001/docsThe consumer Swagger UI calls the installed SDK package through the consumer-test Express server.
In another terminal:
npm run test:endpointsThe editable request bodies are in:
consumer-test/test-endpoints.tsThey are typed using the SDK exports:
import type {
CreateDynamicVirtualAccountInput,
CreateStaticVirtualAccountInput,
} from "@payscribe/sdk";Development Commands
Root SDK:
npm run typecheck
npm run test
npm run buildConsumer app:
cd consumer-test
npm run typecheck
npm start
npm run test:endpointsBuild Output
Build files are emitted to:
dist/The package exports both ESM and CommonJS:
import { Payscribe } from "@payscribe/sdk";const { Payscribe } = require("@payscribe/sdk");Publish To npm
Package name:
"@payscribe/sdk"Publishing requires access to the @payscribe npm organization.
Release flow:
npm login
npm whoami
npm run typecheck
npm run test
npm run build
npm pack --dry-run
npm publish --access publicFor a new version:
npm version patch
npm publish --access publicUse minor for backwards-compatible features and major for breaking changes.
