Skip to main content

Overview

This guide walks you through accepting payments using FossaPay’s Fiat Wallet API. You’ll learn how to create fiat wallets, receive payments, handle webhooks, and reconcile transactions.

Quick Start

1

Create a Fiat Wallet

Generate a unique account number for your customer
2

Share Account Details

Provide the account number to your customer
3

Receive Payment

Customer transfers money to the wallet account
4

Get Notified

Receive instant webhook notification
5

Credit Customer

Update your customer’s balance or fulfill their order

Creating Fiat Wallets

For Wallet Top-ups

curl -X POST https://api-staging.fossapay.com/api/v1/wallets/fiat/create \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "cus_123",
    "walletName": "Main Wallet",
    "walletReference": "wallet_ref_001"
  }'
Response:
{
  "success": true,
  "message": "Fiat wallet created successfully",
  "data": {
    "walletId": "wal_abc123xyz",
    "bankName": "Wema Bank",
    "bankCode": "035",
    "accountNumber": "1234567890"
  }
}

For Invoice Payments

# Create fiat wallet for customer if not exists
curl -X POST https://api-staging.fossapay.com/api/v1/wallets/fiat/create \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "cus_456",
    "walletName": "Invoice Wallet",
    "walletReference": "invoice_INV-001"
  }'
Response:
{
  "success": true,
  "message": "Fiat wallet created successfully",
  "data": {
    "walletId": "wal_xyz789",
    "bankName": "Wema Bank",
    "bankCode": "035",
    "accountNumber": "0987654321"
  }
}
Share the account details with the customer for payment.

Displaying Account Details

Email Template

<div style="font-family: Arial, sans-serif;">
  <h2>Payment Instructions</h2>
  <p>Please transfer <strong>₦50,000</strong> to the account below:</p>

  <div style="background: #f5f5f5; padding: 20px; border-radius: 8px;">
    <p><strong>Bank:</strong> Wema Bank</p>
    <p><strong>Account Number:</strong> 1234567890</p>
    <p><strong>Account Name:</strong> ACME Corp - Jane Smith</p>
    <p><strong>Amount:</strong> ₦50,000</p>
  </div>

  <p><small>This account is unique to your transaction and expires on Jan 31, 2024</small></p>
</div>

In-App Display (React)

function PaymentInstructions({ account, amount }) {
  const [copied, setCopied] = useState(false);

  const copyAccountNumber = () => {
    navigator.clipboard.writeText(account.account_number);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  };

  return (
    <div className="payment-card">
      <h3>Transfer {formatCurrency(amount)} to:</h3>

      <div className="account-details">
        <div className="detail-row">
          <span>Bank:</span>
          <strong>{account.bank_name}</strong>
        </div>

        <div className="detail-row">
          <span>Account Number:</span>
          <div>
            <strong>{account.account_number}</strong>
            <button onClick={copyAccountNumber}>
              {copied ? 'Copied!' : 'Copy'}
            </button>
          </div>
        </div>

        <div className="detail-row">
          <span>Account Name:</span>
          <strong>{account.account_name}</strong>
        </div>
      </div>

      <p className="note">
        Payments are confirmed instantly
      </p>
    </div>
  );
}

Handling Webhook Notifications

Set Up Webhook Endpoint

const express = require('express');
const crypto = require('crypto');
const app = express();

app.use(express.json());

app.post('/webhooks/fossapay', async (req, res) => {
  // 1. Verify signature
  if (!verifySignature(req.body, req.headers['x-fossapay-signature'])) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // 2. Respond immediately
  res.status(200).json({ received: true });

  // 3. Process asynchronously
  const { event, data } = req.body;

  if (event === 'deposit.completed') {
    await handlePaymentReceived(data);
  }
});

async function handlePaymentReceived(payment) {
  console.log('Payment received:', payment);

  // Get customer from metadata
const customerId = payment.metadata.customer_id;

  // Create transaction record
  await db.transactions.create({
    id: payment.transaction_id,
    customer_id: customerId,
    amount: payment.amount,
    type: 'credit',
    status: 'completed',
    reference: payment.reference,
    created_at: payment.created_at
  });

  // Credit customer wallet
  await db.wallets.increment({
    customer_id: customerId,
    amount: payment.amount
  });

  // Send notification
  await sendNotification(customerId, {
    title: 'Payment Received!',
    message: `Your wallet has been credited with ₦${payment.amount.toLocaleString()}`
  });

  // Send confirmation email
  await sendEmail(payment.metadata.customer_email, {
    subject: 'Payment Confirmation',
    template: 'payment-received',
    data: {
      amount: payment.amount,
      reference: payment.reference,
      date: payment.created_at
    }
  });
}

function verifySignature(payload, signature) {
  const secret = process.env.FOSSAPAY_WEBHOOK_SECRET;
  const hash = crypto
    .createHmac('sha256', secret)
    .update(JSON.stringify(payload))
    .digest('hex');

  return hash === signature;
}

Webhook Payload Example

{
  "event": "deposit.completed",
  "event_id": "evt_abc123",
  "timestamp": "2024-01-15T10:30:45Z",
  "data": {
    "transaction_id": "txn_001",
    "wallet_id": "wal_abc123",
    "account_number": "1234567890",
    "amount": 50000,
    "currency": "NGN",
    "sender_name": "John Doe",
    "sender_account": "0987654321",
    "sender_bank": "GTBank",
    "sender_bank_code": "058",
    "reference": "FP-20240115-001",
    "narration": "Payment from John Doe",
    "customer_id": "cus_123",
    "created_at": "2024-01-15T10:30:40Z"
  }
}

Querying Transaction Status

Get Transaction by ID

const transaction = await client.transactions.get('txn_001');

console.log(transaction);
// {
//   "status": "success",
//   "data": {
//     "transaction_id": "txn_001",
//     "amount": 50000,
//     "status": "successful",
//     "sender_name": "John Doe",
//     "created_at": "2024-01-15T10:30:40Z"
//   }
// }

List Virtual Account Transactions

const response = await fetch('https://api-staging.fossapay.com/api/v1/wallets/fiat/va_abc123/transactions?startDate=2024-01-01&endDate=2024-01-31&limit=50', {
  headers: {
    'x-api-key': 'YOUR_API_KEY'
  }
});

const data = await response.json();
const transactions = data.transactions;

Overpayment

async function handlePaymentReceived(payment) {
  const expectedAmount = await getExpectedAmount(payment.metadata.invoice_id);

  if (payment.amount > expectedAmount) {
    // Overpayment detected
    const excess = payment.amount - expectedAmount;

    // Credit invoice amount
    await creditInvoice(payment.metadata.invoice_id, expectedAmount);

    // Credit excess to wallet
    await creditWallet(payment.metadata.customer_id, excess);

    // Notify customer
    await sendNotification(payment.metadata.customer_id, {
      title: 'Overpayment Detected',
      message: `You paid ₦${excess.toLocaleString()} extra. We've added it to your wallet.`
    });
  } else {
    // Normal payment
    await creditInvoice(payment.metadata.invoice_id, payment.amount);
  }
}

Underpayment

if (payment.amount < expectedAmount) {
  const shortfall = expectedAmount - payment.amount;

  // Mark invoice as partially paid
  await db.invoices.update(payment.metadata.invoice_id, {
    status: 'partially_paid',
    amount_paid: payment.amount,
    amount_remaining: shortfall
  });

  // Notify customer
  await sendNotification(payment.metadata.customer_id, {
    title: 'Partial Payment Received',
    message: `You paid ₦${payment.amount.toLocaleString()}. ₦${shortfall.toLocaleString()} remaining.`
  });
}

Duplicate Payments

async function handlePaymentReceived(payment) {
  // Check if already processed
  const existing = await db.transactions.findOne({
    id: payment.transaction_id
  });

  if (existing) {
    console.log('Duplicate webhook, skipping');
    return;
  }

  // Process payment
  // ...
}

Best Practices

Store relevant information in metadata for easy tracking:
metadata: {
  customer_id: 'cus_123',
  order_id: 'ord_456',
  campaign: 'q1-promo',
  source: 'mobile_app'
}
Always set expiry dates for invoice payments:
expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) // 7 days
Never process webhooks without signature verification.
Always check for duplicates using transaction_id or event_id.
Always notify customers when payments are received.

Testing

Simulate Payment in Sandbox

curl -X POST https://api-staging.fossapay.com/api/v1/sandbox/simulate-payment \
  -H "x-api-key: fp_test_sk_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "virtual_account_number": "1234567890",
    "amount": 50000,
    "sender_name": "Test Customer",
    "sender_account": "0000000001",
    "sender_bank": "058"
  }'

Next Steps

Create Fiat Wallet API

API reference for creating fiat wallets

Webhooks Guide

Learn more about handling webhooks

Reconciliation Guide

Best practices for reconciliation

Wallets Concept

Understand wallets in depth