AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL unreviewed MIT Self-run

Paystack Integration

skill-babatunde-fatai-ai-agent-skills-library-paystack-integration · by Babatunde-Fatai

>

No reviews yet
0 installs
8 views
0.0% view→install

Install

$ agentstack add skill-babatunde-fatai-ai-agent-skills-library-paystack-integration

Open-source listing, not yet scanned by AgentStack. Follow the source repository for install instructions.

Security review

⚠ Flagged

1 finding(s); flagged for manual review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures
  • high Reads credentials/environment and may exfiltrate them.

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Reliability & compatibility

Not yet reviewed
0 installs to date
no reviews yet
4mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Paystack Integration? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Paystack Integration

Paystack is Africa's leading payment gateway, primarily serving Nigeria, Ghana, Kenya, and South Africa. This skill covers complete payment integration patterns.

Table of Contents (all contained in .references/)

  1. [Agent Execution Spec](references/AGENTEXECUTIONSPEC.md) ⚠️ READ FIRST
  2. [Quick Reference](#quick-reference)
  3. [Payment Flow Overview](#payment-flow-overview)
  4. [Core Implementation](#core-implementation)
  5. [Webhook Essentials](#webhook-essentials)
  6. [Framework Guides](#framework-guides)
  7. [Deployment Checklist](#deployment-checklist)
  8. [Quick Troubleshooting](#quick-troubleshooting)

File Selection Decision Tree (quick)

  • If implementing frontend popup/redirect → read [references/one-time-payments.md](references/one-time-payments.md)
  • If implementing backend verification → read [references/express-implementation.md](references/express-implementation.md)
  • If implementing webhooks → read [references/webhooks.md](references/webhooks.md)
  • If implementing subscriptions or charge authorization → read [references/subscriptions.md](references/subscriptions.md)
  • If unsure about responsibilities or safety → read [references/AGENTEXECUTIONSPEC.md](references/AGENTEXECUTIONSPEC.md) first
  • Need framework-specific examples → read [references/nextjs-implementation.md](references/nextjs-implementation.md) or [references/express-implementation.md](references/express-implementation.md)

⚠️ For Coding Agents: Start Here

Before reading any examples or framework guides, you MUST read:

[Agent Execution Spec](references/AGENTEXECUTIONSPEC.md)

This file defines the payment safety contract and execution order that must be followed to prevent fraud and incorrect implementations.

Condensed Payment Safety Rules (summary — see execution spec for full invariants):

  • Call Paystack initializeTransaction FIRST, then store the returned reference in DB.
  • Always convert and store amounts in smallest currency unit (kobo/pesewa/cents) and compare those values.
  • Never trust client-side success callbacks; always verify on backend using the reference.
  • Webhook handlers must verify x-paystack-signature using the raw request body before parsing.
  • Verify that the verified amount exactly matches the expected DB amount before fulfilling.
  • Ensure idempotency: if order.status == 'paid', exit immediately.

Examples in this SKILL are references. The execution spec is the protocol.


Frontend and Backend Integration Bridge

In most implementations, frontend and backend are separate or separated by folders.

After a successful popup or redirect, the frontend MUST send the transaction reference to the backend for verification.

Recommended pattern:

Frontend → POST /api/verify-payment { reference }

The backend then performs Paystack verification and marks the order as paid.

Reference consistency is critical: The reference returned from initializeTransaction is stored in your database and sent to the frontend. This same reference appears in webhooks and must be used for verification.


Quick Reference

Environment Variables

# Backend (NEVER expose these)
PAYSTACK_SECRET_KEY=sk_test_xxx   # or sk_live_xxx for production
PAYSTACK_WEBHOOK_SECRET=whsec_xxx # Optional: separate webhook secret

# Frontend (safe to expose)
PAYSTACK_PUBLIC_KEY=pk_test_xxx   # or pk_live_xxx for production

API Configuration

const PAYSTACK_BASE_URL = 'https://api.paystack.co';

const headers = {
  Authorization: `Bearer ${process.env.PAYSTACK_SECRET_KEY}`,
  'Content-Type': 'application/json',
};

Reference Management (CRITICAL)

// ❌ WRONG: Generate reference locally, save to DB, then call Paystack
const reference = generateReference('PAY');
await db.orders.create({ reference, ... });  // Orphan if next line fails!
const transaction = await initializeTransaction({ ..., reference });

// ✅ CORRECT: Call Paystack first, then save with returned reference
const transaction = await initializeTransaction({
  email,
  amount: amountInKobo,
  // reference intentionally omitted — Paystack generates it
});
await db.orders.create({
  reference: transaction.reference,  // ← Use Paystack's reference
  ...
});

Flow:

  1. Call initializeTransaction() WITHOUT a reference parameter
  2. Paystack returns a unique reference in result.data.reference
  3. Store this reference in your database
  4. Return this reference to frontend
  5. Webhook events will contain this same reference

⚠️ WHY: If you save to DB before calling Paystack and the API fails, you create orphan records. By calling Paystack first, you only persist successful initializations.

Currency: Kobo Conversion (CRITICAL)

// Paystack amounts are in smallest currency unit
// NGN: kobo (1 Naira = 100 kobo)
// GHS: pesewas (1 Cedi = 100 pesewas)

const amountInKobo = amountInNaira * 100;  // 5000 NGN = 500000 kobo
const amountInNaira = amountInKobo / 100;  // 500000 kobo = 5000 NGN

// Helper function
function toKobo(amount: number): number {
  return Math.round(amount * 100);
}

Supported Payment Channels

| Currency | Smallest Unit | Multiplier | |----------|---------------|------------| | NGN | kobo | 100 | | GHS | pesewas | 100 | | ZAR | cents | 100 | | KES | cents | 100 | | USD | cents | 100 |

Note: multiplier is 100 for all listed currencies; store and compare amounts in the smallest unit.

type PaystackChannel = 'card' | 'bank' | 'ussd' | 'qr' | 'mobile_money' | 'bank_transfer' | 'eft';

Payment Flow Overview

One-Time Payment Flow

┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Client    │────▶│   Backend   │────▶│  Paystack   │────▶│   Backend   │
│  (initiate) │     │ (initialize)│     │  (payment)  │     │  (verify)   │
└─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘
      │                    │                   │                   │
      │  1. Request        │  2. POST          │  3. Pay via       │  4. GET
      │     payment        │     /initialize   │     popup/redirect│     /verify/:ref
      │                    │                   │                   │
      │                    │  Returns:         │  Returns:         │  Returns:
      │                    │  authorization_url│  reference        │  status: success
      │                    │  access_code      │                   │  amount verified
      │                    │  reference        │                   │

Decision: Redirect vs Popup

| Method | Use When | Pros | Cons | |--------|----------|------|------| | Redirect | Simple integration, server-rendered apps | No JS required, works everywhere | User leaves your site | | Popup | SPA, better UX | User stays on your site, smoother | Requires Inline.js SDK |

Subscription Flow

1. Create Plan (Dashboard or API) → plan_code
2. Create/Fetch Customer → customer_code
3. Initialize with plan parameter OR Create subscription directly
4. Handle webhook events for lifecycle management

See [references/one-time-payments.md](references/one-time-payments.md) for detailed one-time payment implementation. See [references/subscriptions.md](references/subscriptions.md) for subscription/recurring billing.


Core Implementation

TypeScript Interfaces

interface PaystackResponse {
  status: boolean;
  message: string;
  data: T;
}

interface InitializeTransactionData {
  authorization_url: string;
  access_code: string;
  reference: string;
}

interface VerifyTransactionData {
  id: number;
  status: 'success' | 'failed' | 'abandoned';
  reference: string;
  amount: number; // in kobo
  currency: string;
  channel: string;
  customer: {
    id: number;
    email: string;
    customer_code: string;
  };
  authorization?: {
    authorization_code: string;
    card_type: string;
    last4: string;
    exp_month: string;
    exp_year: string;
    reusable: boolean;
  };
  metadata?: Record;
}

interface WebhookEvent {
  event: string;
  data: Record;
}

Initialize Transaction

interface InitializeParams {
  email: string;
  amount: number; // in kobo
  /**
   * Transaction reference. RECOMMENDED: Omit this parameter.
   * When omitted, Paystack generates a unique reference.
   * Use the returned reference for database storage.
   * Only provide a reference for retry scenarios (e.g., retrying after network timeout).
   */
  reference?: string;
  callback_url?: string;
  metadata?: Record;
  channels?: PaystackChannel[];
  plan?: string; // plan_code for subscriptions
}

/**
 * Initialize a Paystack transaction.
 *
 * IMPORTANT: Omit the 'reference' parameter to let Paystack generate one.
 * Store the returned reference (result.data.reference) in your database.
 * This reference will appear in webhooks and verification responses.
 */
async function initializeTransaction(params: InitializeParams): Promise {
  const response = await fetch(`${PAYSTACK_BASE_URL}/transaction/initialize`, {
    method: 'POST',
    headers,
    body: JSON.stringify(params),
  });

  const result: PaystackResponse = await response.json();

  if (!result.status) {
    throw new Error(result.message);
  }

  return result.data;
}

Verify Transaction (CRITICAL: Always Verify Amount)

async function verifyTransaction(reference: string, expectedAmount: number): Promise {
  const response = await fetch(
    `${PAYSTACK_BASE_URL}/transaction/verify/${encodeURIComponent(reference)}`,
    { headers }
  );

  const result: PaystackResponse = await response.json();

  if (!result.status) {
    throw new Error(result.message);
  }

  // CRITICAL: Verify payment status
  if (result.data.status !== 'success') {
    throw new Error(`Payment not successful: ${result.data.status}`);
  }

  // CRITICAL: Verify amount matches expected (prevents underpayment fraud)
  if (result.data.amount !== expectedAmount) {
    throw new Error(`Amount mismatch: expected ${expectedAmount}, got ${result.data.amount}`);
  }

  return result.data;
}

Webhook Essentials

Webhooks notify your server of payment events. Signature verification is mandatory for security.

Signature Verification (SECURITY CRITICAL)

import crypto from 'crypto';

function verifyPaystackWebhook(
  payload: string | Buffer,
  signature: string,
  secretKey: string
): boolean {
  const payloadString = typeof payload === 'string' ? payload : payload.toString();

  const hash = crypto
    .createHmac('sha512', secretKey)
    .update(payloadString)
    .digest('hex');

  // Use timing-safe comparison to prevent timing attacks
  try {
    return crypto.timingSafeEqual(
      Buffer.from(hash, 'hex'),
      Buffer.from(signature.toLowerCase(), 'hex')
    );
  } catch {
    return false; // Lengths don't match
  }
}

// Usage in webhook handler
function handleWebhook(req: Request): Response {
  const signature = req.headers.get('x-paystack-signature');

  if (!signature) {
    return new Response('Missing signature', { status: 401 });
  }

  // IMPORTANT: Use raw body, not parsed JSON
  const rawBody = await req.text();

  const isValid = verifyPaystackWebhook(
    rawBody,
    signature,
    process.env.PAYSTACK_SECRET_KEY!
  );

  if (!isValid) {
    console.error('Invalid webhook signature');
    return new Response('Invalid signature', { status: 401 });
  }

  const event: WebhookEvent = JSON.parse(rawBody);

  // Process event (see below)
  await processWebhookEvent(event);

  // MUST return 200 within 30 seconds
  return new Response('OK', { status: 200 });
}

Key Webhook Events

| Event | When | Action | |-------|------|--------| | charge.success | Payment completed | Fulfill order, update database | | charge.failed | Payment failed | Notify user, log for analysis | | subscription.create | New subscription | Activate subscription | | subscription.disable | Subscription cancelled | Revoke access | | subscription.not_renew | Won't renew (user cancelled) | Send retention email | | invoice.create | 3 days before charge | Send reminder email | | invoice.payment_failed | Subscription charge failed | Notify user, retry logic | | invoice.update | Invoice status changed | Update subscription status |

Idempotency

async function processWebhookEvent(event: WebhookEvent): Promise {
  // Extract unique event identifier
  const eventId = event.data.id as number;

  // Check if already processed (use your database)
  const processed = await db.webhookEvents.findUnique({ where: { eventId } });
  if (processed) {
    console.log(`Event ${eventId} already processed, skipping`);
    return;
  }

  // Process based on event type
  switch (event.event) {
    case 'charge.success':
      await handleChargeSuccess(event.data);
      break;
    case 'subscription.create':
      await handleSubscriptionCreate(event.data);
      break;
    // ... other events
  }

  // Mark as processed
  await db.webhookEvents.create({ data: { eventId, event: event.event } });
}

See [references/webhooks.md](references/webhooks.md) for complete event handling patterns.


Framework Guides

Next.js (App Router)

Recommended file structure:

app/api/paystack/
├── initialize/route.ts    # POST - Initialize payment
├── verify/route.ts        # GET - Verify payment
└── webhook/route.ts       # POST - Handle webhooks
components/
└── PaystackButton.tsx     # Client component with Inline.js
lib/
└── paystack.ts            # Utility functions

See [references/nextjs-implementation.md](references/nextjs-implementation.md) for complete implementation.

Express.js

Recommended structure:

routes/
└── paystack.routes.ts
controllers/
└── paystack.controller.ts
middleware/
└── paystack.middleware.ts  # Signature verification
services/
└── paystack.service.ts     # API calls

See [references/express-implementation.md](references/express-implementation.md) for complete implementation.


Deployment Checklist

Before Going Live

  • [ ] Environment Variables
  • [ ] PAYSTACK_SECRET_KEY set (sklivexxx for production)
  • [ ] PAYSTACK_PUBLIC_KEY set (pklivexxx for production)
  • [ ] Keys not committed to version control
  • [ ] Webhook Configuration
  • [ ] Webhook URL configured in Paystack Dashboard (Settings → API Keys & Webhooks)
  • [ ] HTTPS endpoint (required for production)
  • [ ] Signature verification implemented and tested
  • [ ] Return 200 OK within 30 seconds
  • [ ] Payment Verification
  • [ ] Amount verification implemented (prevent underpayment)
  • [ ] Status check implemented (only process success)
  • [ ] Idempotency for webhook handling
  • [ ] Testing
  • [ ] Test with Paystack test cards
  • [ ] Test webhook with ngrok (local) or staging URL
  • [ ] Test subscription lifecycle (create → charge → cancel)
  • [ ] Test failed payment scenarios
  • [ ] Monitoring
  • [ ] Webhook delivery logs in Paystack Dashboard
  • [ ] Application logging for payment events
  • [ ] Alerting for webhook failures

Test Cards

| Card Number | Scenario | |-------------|----------| | 4084084084084081 | Successful payment | | 5078575078575078 | Insufficient funds | | 4084080000005408 | Declined |

CVV: Any 3 digits | Expiry: Any future date | PIN: 1234 | OTP: 123456


Quick Troubleshooting

Common Errors

| Error | Cause | Solution | |-------|-------|----------| | Invalid key | Wrong API key | Check env var, test vs live key | | Amount is required | Missing/zero amount | Ensure amount in kobo (multiply by 100) | | Invalid signature | Webhook verification failed | Use raw body, verify secret key | | Transaction reference exists | Duplicate reference | Generate unique reference per transaction | | `Customer not found

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.