Skip to main content
The Transaction API allows you to accept payments from customers using various payment channels. This guide covers common transaction scenarios and best practices.

Overview

Transactions represent payment attempts from your customers. The typical flow involves:
  1. Initializing a transaction to get a payment URL
  2. Redirecting customers to complete payment
  3. Verifying the transaction after payment
  4. Handling the response

Initialize a Transaction

Create a payment session and get an authorization URL for your customer.
1

Import and initialize the SDK

import { Paystack } from '@efobi/paystack';

const paystack = new Paystack(process.env.PAYSTACK_SECRET_KEY!);
2

Initialize the transaction

const { data, error } = await paystack.transaction.initialize({
  email: 'customer@email.com',
  amount: '50000', // Amount in kobo (₦500.00)
  currency: 'NGN',
  reference: `TXN-${Date.now()}`, // Optional: unique reference
  callback_url: 'https://yoursite.com/verify'
});

if (error) {
  console.error('Initialization failed:', error);
  return;
}

// Redirect customer to authorization_url
console.log('Payment URL:', data.data.authorization_url);
console.log('Access Code:', data.data.access_code);
console.log('Reference:', data.data.reference);
3

Redirect the customer

// In a web application
window.location.href = data.data.authorization_url;

// Or return the URL to your frontend
return Response.json({ 
  paymentUrl: data.data.authorization_url,
  reference: data.data.reference 
});

Initialize Parameters

email
string
required
Customer’s email address
amount
string
required
Amount to charge in kobo (multiply by 100 for naira)
currency
string
default:"NGN"
Currency code: NGN, USD, GHS, ZAR, KES, or XOF
reference
string
Unique transaction reference (auto-generated if not provided)
callback_url
string
URL to redirect customer after payment
channels
array
Payment channels to enable: card, bank, ussd, qr, mobile_money, bank_transfer, eft, apple_pay
metadata
object
Custom data to store with the transaction
split_code
string
Split payment configuration code

Verify a Transaction

After payment, verify the transaction status using the reference.
const { data, error } = await paystack.transaction.verify(reference);

if (error) {
  console.error('Verification failed:', error);
  return;
}

if (data.data.status === 'success') {
  console.log('Payment successful!');
  console.log('Amount paid:', data.data.amount / 100);
  console.log('Customer:', data.data.customer.email);
  console.log('Reference:', data.data.reference);
  
  // Grant access to product/service
  await fulfillOrder(data.data);
} else {
  console.log('Payment status:', data.data.status);
}
Always verify transactions on your server before fulfilling orders. Never rely solely on frontend confirmations or webhooks without verification.

List Transactions

Retrieve a paginated list of transactions on your integration.
const { data, error } = await paystack.transaction.list({
  perPage: 50,
  page: 1,
  status: 'success',
  from: '2024-01-01',
  to: '2024-12-31'
});

if (data) {
  console.log('Total transactions:', data.meta.total);
  
  data.data.forEach(transaction => {
    console.log(`${transaction.reference}: ₦${transaction.amount / 100}`);
  });
}

List Parameters

perPage
number
default:"50"
Number of transactions per page (max: 100)
page
number
default:"1"
Page number to retrieve
status
string
Filter by status: success, failed, abandoned
customer
string
Filter by customer ID
from
string
Start date (ISO 8601 format)
to
string
End date (ISO 8601 format)

Get Single Transaction

Fetch details of a specific transaction by ID.
const transactionId = 12345678;

const { data, error } = await paystack.transaction.getTransactionById(
  transactionId
);

if (data) {
  console.log('Transaction details:', {
    reference: data.data.reference,
    amount: data.data.amount / 100,
    status: data.data.status,
    channel: data.data.channel,
    paidAt: data.data.paidAt
  });
}

Charge Authorization

Charge a customer using a previously authorized payment method.
Only use this for authorized recurring charges. Ensure you have the customer’s permission.
const { data, error } = await paystack.transaction.chargeAuthorization({
  authorization_code: 'AUTH_xxxxxxxxx',
  email: 'customer@email.com',
  amount: 50000, // Amount in kobo
  currency: 'NGN',
  reference: `REC-${Date.now()}`,
  metadata: {
    subscription_id: 'sub_123',
    billing_period: 'monthly'
  }
});

if (data && data.data.status === 'success') {
  console.log('Charge successful!');
  console.log('Transaction ID:', data.data.id);
  console.log('Amount charged:', data.data.amount / 100);
} else {
  console.log('Charge failed:', data?.data.gateway_response);
}
You can get the authorization_code from successful transactions where authorization.reusable is true.

Transaction Timeline

View the event history of a transaction.
const { data, error } = await paystack.transaction.viewTxnTimeline(
  'transaction_reference'
);

if (data) {
  console.log('Timeline:', data.data.history);
  data.data.history.forEach(event => {
    console.log(`${event.type}: ${event.message}`);
  });
}

Transaction Totals

Get total volume of transactions on your integration.
const { data, error } = await paystack.transaction.getTxnTotals({
  perPage: 1,
  page: 1
});

if (data) {
  console.log('Total transactions:', data.data.total_transactions);
  console.log('Total volume:', data.data.total_volume);
  
  data.data.total_volume_by_currency.forEach(volume => {
    console.log(`${volume.currency}: ${volume.amount / 100}`);
  });
}

Export Transactions

Export transactions to a CSV file.
const { data, error } = await paystack.transaction.exportTxns({
  perPage: 50,
  page: 1,
  from: '2024-01-01',
  to: '2024-12-31'
});

if (data) {
  console.log('Export path:', data.data.path);
  console.log('Expires at:', data.data.expiresAt);
  
  // Download the CSV file
  const response = await fetch(data.data.path);
  const csv = await response.text();
}

Partial Debit

Retrieve part of a payment from a customer.
This feature is typically used for split payments where the full amount isn’t needed upfront.
const { data, error } = await paystack.transaction.partialDebit({
  authorization_code: 'AUTH_xxxxxxxxx',
  email: 'customer@email.com',
  amount: 25000, // Partial amount in kobo
  currency: 'NGN',
  reference: `PART-${Date.now()}`
});

if (data && data.data.status === 'success') {
  console.log('Partial debit successful!');
  console.log('Amount debited:', data.data.amount / 100);
  console.log('Requested amount:', data.data.requested_amount / 100);
}

Error Handling

Handle transaction errors gracefully:
try {
  const { data, error } = await paystack.transaction.initialize({
    email: 'customer@email.com',
    amount: '50000'
  });

  if (error) {
    // Validation error from Zod
    console.error('Validation error:', error.flatten());
    return { success: false, message: 'Invalid transaction data' };
  }

  if (!data.status) {
    // API error response
    console.error('API error:', data.message);
    return { success: false, message: data.message };
  }

  return { success: true, data: data.data };

} catch (err) {
  // Network or unexpected error
  console.error('Unexpected error:', err);
  return { success: false, message: 'Something went wrong' };
}

Best Practices

  1. Always verify transactions on your server after payment
  2. Store transaction references in your database for reconciliation
  3. Use unique references for each transaction to prevent duplicates
  4. Handle webhooks for real-time payment notifications
  5. Log all transactions for audit trails and debugging
  6. Test with test keys before going live
  7. Implement proper error handling at every step

Common Scenarios

One-time Payment

async function processPayment(customerEmail: string, amount: number) {
  // Step 1: Initialize transaction
  const { data, error } = await paystack.transaction.initialize({
    email: customerEmail,
    amount: (amount * 100).toString(), // Convert to kobo
    callback_url: `${process.env.APP_URL}/verify`
  });

  if (error || !data.status) {
    throw new Error('Failed to initialize payment');
  }

  // Step 2: Store reference in database
  await db.transactions.create({
    reference: data.data.reference,
    email: customerEmail,
    amount: amount,
    status: 'pending'
  });

  // Step 3: Return payment URL
  return data.data.authorization_url;
}

// After customer pays and returns
async function verifyPayment(reference: string) {
  const { data, error } = await paystack.transaction.verify(reference);

  if (error) {
    throw new Error('Verification failed');
  }

  // Update database
  await db.transactions.update(
    { reference },
    { 
      status: data.data.status,
      paidAt: data.data.paidAt
    }
  );

  if (data.data.status === 'success') {
    // Fulfill order
    await fulfillOrder(reference);
  }

  return data.data;
}

Recurring Charges

async function chargeSubscription(subscription: Subscription) {
  const { data, error } = await paystack.transaction.chargeAuthorization({
    authorization_code: subscription.authorizationCode,
    email: subscription.customerEmail,
    amount: subscription.amount * 100,
    reference: `SUB-${subscription.id}-${Date.now()}`,
    metadata: {
      subscription_id: subscription.id,
      billing_period: subscription.currentPeriod
    }
  });

  if (error) {
    // Handle payment failure
    await notifyCustomer(subscription, 'payment_failed');
    return false;
  }

  if (data.data.status === 'success') {
    await recordPayment(subscription, data.data);
    await extendSubscription(subscription);
    return true;
  }

  return false;
}