npm package: @gigchain/pay-mpesa-ramp
Description: In-house M-Pesa → GigKES → USDC ramp for GigChain Pay on Hedera
Latest version: 0.1.0

# @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

```bash
npm install @gigchain/pay-mpesa-ramp
```

## Required environment variables

The package validates configuration at runtime through `getConfig()`. Set these variables before use:

### Daraja

- `DARAJA_CONSUMER_KEY`
- `DARAJA_CONSUMER_SECRET`
- `DARAJA_PAYBILL_NUMBER`
- `DARAJA_PASS_KEY`
- `DARAJA_CALLBACK_URL`
- `DARAJA_VALIDATION_URL`
- `DARAJA_CONFIRMATION_URL`
- `DARAJA_ENV` (optional, default: `sandbox`, allowed: `sandbox|production`)

### Hedera

- `HEDERA_NETWORK` (optional, default: `testnet`, allowed: `mainnet|testnet|previewnet`)
- `HEDERA_OPERATOR_ID`
- `HEDERA_OPERATOR_PRIVATE_KEY`
- `HEDERA_GIGKES_TOKEN_ID`
- `HEDERA_USDC_TOKEN_ID`
- `HEDERA_TREASURY_ACCOUNT_ID`

### App

- `WEBHOOK_SECRET`
- `LOG_LEVEL` (optional, default: `info`, allowed: `debug|info|warn|error`)
- `KES_PER_USDC` (optional, default: `130`)

Example `.env`:

```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=130
```

## Quick start

### 1) Validate configuration

```ts
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)

```ts
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

```ts
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

```ts
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.

```ts
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`
- High-level flow: `processC2BConfirmation`

## Local development

```bash
npm run build
npm test
npm run lint
```