@gigchain/pay-mpesa-ramp
v0.1.0
Published
In-house M-Pesa → GigKES → USDC ramp for GigChain Pay on Hedera
Readme
@gigchain/pay-mpesa-ramp
M-Pesa -> GigKES -> USDC ramping library for GigChain Pay on Hedera.
This package helps you:
- Integrate with Safaricom Daraja (OAuth, C2B URL registration, STK Push).
- Verify and parse Daraja webhook payloads.
- Process confirmed C2B payments into on-chain transfers (GigKES + USDC).
Installation
npm install @gigchain/pay-mpesa-rampRequired environment variables
The package validates configuration at runtime through getConfig(). Set these variables before use:
Daraja
DARAJA_CONSUMER_KEYDARAJA_CONSUMER_SECRETDARAJA_PAYBILL_NUMBERDARAJA_PASS_KEYDARAJA_CALLBACK_URLDARAJA_VALIDATION_URLDARAJA_CONFIRMATION_URLDARAJA_ENV(optional, default:sandbox, allowed:sandbox|production)
Hedera
HEDERA_NETWORK(optional, default:testnet, allowed:mainnet|testnet|previewnet)HEDERA_OPERATOR_IDHEDERA_OPERATOR_PRIVATE_KEYHEDERA_GIGKES_TOKEN_IDHEDERA_USDC_TOKEN_IDHEDERA_TREASURY_ACCOUNT_ID
App
WEBHOOK_SECRETLOG_LEVEL(optional, default:info, allowed:debug|info|warn|error)KES_PER_USDC(optional, default:130)
Example .env:
DARAJA_CONSUMER_KEY=your_consumer_key
DARAJA_CONSUMER_SECRET=your_consumer_secret
DARAJA_PAYBILL_NUMBER=600999
DARAJA_PASS_KEY=your_daraja_passkey
DARAJA_CALLBACK_URL=https://api.example.com/mpesa/stk/callback
DARAJA_VALIDATION_URL=https://api.example.com/mpesa/c2b/validation
DARAJA_CONFIRMATION_URL=https://api.example.com/mpesa/c2b/confirmation
DARAJA_ENV=sandbox
HEDERA_NETWORK=testnet
HEDERA_OPERATOR_ID=0.0.12345
HEDERA_OPERATOR_PRIVATE_KEY=302e...
HEDERA_GIGKES_TOKEN_ID=0.0.111111
HEDERA_USDC_TOKEN_ID=0.0.222222
HEDERA_TREASURY_ACCOUNT_ID=0.0.12345
WEBHOOK_SECRET=replace_me
LOG_LEVEL=info
KES_PER_USDC=130Quick start
1) Validate configuration
import { getConfig } from "@gigchain/pay-mpesa-ramp";
const config = getConfig();
console.log("Loaded config for", config.daraja.environment, config.hedera.network);2) Register C2B callback URLs (once per paybill or on URL change)
import { DarajaClient } from "@gigchain/pay-mpesa-ramp";
const client = new DarajaClient();
const result = await client.registerC2BUrls();
console.log(result.ResponseCode, result.ResponseDescription);3) Initiate STK Push
import { DarajaClient, normaliseKenyanPhone, validateAmount, validateAccountReference } from "@gigchain/pay-mpesa-ramp";
const client = new DarajaClient();
const phone = normaliseKenyanPhone("0712345678");
const amount = 250;
const reference = "GC12345";
validateAmount(amount);
validateAccountReference(reference);
const stk = await client.initiateStkPush(phone, amount, reference, "GigChain top-up");
console.log(stk.CheckoutRequestID, stk.CustomerMessage);4) Process a confirmed C2B payment into GigKES + USDC
import { processC2BConfirmation } from "@gigchain/pay-mpesa-ramp";
// Use raw Daraja confirmation JSON body
const darajaConfirmationPayload = {
TransactionType: "Pay Bill",
TransID: "LGR219G3EY",
TransTime: "20260323153000",
TransAmount: "5000",
BusinessShortCode: "600999",
BillRefNumber: "0.0.12345",
MSISDN: "254712345678",
};
// recipientAccount is optional.
// If omitted, the package uses BillRefNumber from payload.
const result = await processC2BConfirmation(darajaConfirmationPayload, "0.0.12345");
console.log(result);
/*
{
mpesaTransactionId,
msisdn,
kesAmount,
gigKesAmount,
microUsdcAmount,
gigKesTxId,
usdcTxId
}
*/Webhook integration (Express example)
Use raw request body for signature verification, then parse and process.
import express from "express";
import {
verifyWebhookSignature,
parseC2BValidation,
buildValidationResponse,
processC2BConfirmation,
} from "@gigchain/pay-mpesa-ramp";
const app = express();
// Keep raw body for HMAC verification
app.use("/mpesa", express.raw({ type: "application/json" }));
app.post("/mpesa/c2b/validation", async (req, res) => {
const signature = String(req.headers["x-gigchain-signature"] || "");
const secret = process.env.WEBHOOK_SECRET || "";
if (!verifyWebhookSignature(req.body, signature, secret)) {
return res.status(401).json(buildValidationResponse(false, "Invalid signature"));
}
const payload = parseC2BValidation(JSON.parse(req.body.toString("utf8")));
const accepted = Number(payload.TransAmount) > 0;
return res.json(buildValidationResponse(accepted, "Rejected by validation rule"));
});
app.post("/mpesa/c2b/confirmation", async (req, res) => {
const signature = String(req.headers["x-gigchain-signature"] || "");
const secret = process.env.WEBHOOK_SECRET || "";
if (!verifyWebhookSignature(req.body, signature, secret)) {
return res.status(401).json({ ok: false, message: "Invalid signature" });
}
const payload = JSON.parse(req.body.toString("utf8"));
const result = await processC2BConfirmation(payload);
return res.json({ ok: true, result });
});Public API overview
Main exports include:
- Configuration:
getConfig,resetConfig - Daraja:
DarajaClient - Webhooks:
verifyWebhookSignature,parseC2BValidation,parseC2BConfirmation,parseStkCallback,buildValidationResponse - Validation helpers:
normaliseKenyanPhone,validateAmount,validateAccountReference,validateHederaAccountId,validateHederaTokenId - Hedera token operations:
- GigKES:
associateGigKes,mintGigKes,transferGigKes - USDC:
associateUsdc,transferUsdc
- GigKES:
- High-level flow:
processC2BConfirmation
Local development
npm run build
npm test
npm run lint