npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mamdouh-aboammar/pixolink-pixopay

v1.0.0

Published

Payment processing module for PixoLink SDK with Egyptian gateways (Instapay, VF Cash) and Stripe

Readme

PixoPay Module

Payment processing for Egyptian and international markets

PixoPay provides a unified payment processing interface supporting three payment providers:

  • Instapay - Egyptian card/wallet/bank gateway (Visa, Mastercard, Meeza)
  • VF Cash - Vodafone Cash mobile wallet (20M+ users in Egypt)
  • Stripe - Global payment processing with 3D Secure

Features

Egyptian Market Support - Native integration with local payment methods
Mobile Wallet - USSD code support for feature phones
OAuth Management - Automatic token refresh for VF Cash
Webhook Verification - HMAC-SHA256 signature validation
Transaction Tracking - In-memory state management (extensible to database)
Currency Conversion - Automatic EGP piastre and zero-decimal currency handling
Provider Abstraction - Unified API across all payment providers
Type Safety - Full TypeScript support with Zod validation

Installation

npm install @pixora/pixolink-pixopay

Quick Start

import { PixoPayPlugin } from '@pixora/pixolink-pixopay';
import { initPixoLink } from '@pixora/pixolink';

// Initialize PixoLink with PixoPay
const pixo = await initPixoLink({
  plugins: [
    {
      plugin: new PixoPayPlugin(),
      config: {
        defaultProvider: 'instapay',
        providers: {
          instapay: {
            merchantId: 'MERCHANT_123',
            apiKey: process.env.INSTAPAY_API_KEY!,
            secretKey: process.env.INSTAPAY_SECRET_KEY!,
            webhookSecret: process.env.INSTAPAY_WEBHOOK_SECRET!,
            sandboxMode: true,
          },
          'vf-cash': {
            merchantCode: 'VFC_MERCHANT',
            apiKey: process.env.VFCASH_API_KEY!,
            secretKey: process.env.VFCASH_SECRET_KEY!,
            webhookSecret: process.env.VFCASH_WEBHOOK_SECRET!,
            sandboxMode: true,
          },
          stripe: {
            apiKey: process.env.STRIPE_SECRET_KEY!,
            webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
            apiVersion: '2024-12-18.acacia',
          },
        },
      },
    },
  ],
});

// Get PixoPay API
const pixopay = pixo.getPlugin<PixoPayAPI>('pixopay');

// Create payment
const result = await pixopay.createPayment({
  amount: 100, // EGP 100
  currency: 'EGP',
  customer: {
    name: 'Ahmed Hassan',
    email: '[email protected]',
    phone: '01012345678', // Egyptian format
  },
  description: 'Product purchase',
  returnUrl: 'https://mysite.com/success',
  webhookUrl: 'https://mysite.com/webhook',
});

if (result.success) {
  console.log('Payment created:', result.transactionId);
  console.log('Payment URL:', result.paymentUrl);
}

Provider Configuration

Instapay (Egyptian Gateway)

instapay: {
  merchantId: 'MERCHANT_123',
  apiKey: 'your_api_key',
  secretKey: 'your_secret_key',
  webhookSecret: 'your_webhook_secret',
  sandboxMode: true, // Use sandbox for testing
  baseUrl: 'https://sandbox.instapay.com.eg', // Optional, auto-set based on sandboxMode
}

Supported Payment Methods:

  • Cards: Visa, Mastercard, Meeza
  • Mobile Wallets: Vodafone Cash, Etisalat Cash, Orange Cash, etc.
  • Bank Transfers

Features:

  • HMAC-SHA256 signature verification
  • Automatic piastre conversion (1 EGP = 100 piastres)
  • Credential validation on init
  • Health check endpoint

VF Cash (Mobile Wallet)

'vf-cash': {
  merchantCode: 'VFC_MERCHANT',
  apiKey: 'your_api_key',
  secretKey: 'your_secret_key',
  webhookSecret: 'your_webhook_secret',
  sandboxMode: true,
  baseUrl: 'https://sandbox-api.vodafone.com.eg/vfcash', // Optional
}

Features:

  • OAuth 2.0 authentication with automatic token refresh
  • USSD code generation for feature phones
  • Egyptian phone number normalization (01XXXXXXXXX)
  • Token expiry handling (1-hour tokens, 5-min refresh buffer)

Phone Number Formats Supported:

'01012345678'     // 11 digits (standard)
'+201012345678'   // 13 digits (international)
'00201012345678'  // 14 digits (international)

Stripe (Global Payments)

stripe: {
  apiKey: 'sk_test_...',
  webhookSecret: 'whsec_...',
  apiVersion: '2024-12-18.acacia',
}

Features:

  • Payment Intents API with 3D Secure/SCA
  • Automatic payment methods
  • Zero-decimal currency support (JPY, KRW, VND, CLP)
  • Card saving with setup_future_usage
  • Manual capture and cancellation
  • Customer creation for recurring payments

API Reference

Creating Payments

const result = await pixopay.createPayment({
  amount: 250.50, // Major units (EGP, USD, EUR, etc.)
  currency: 'EGP',
  customer: {
    id: 'cust_123', // Optional
    name: 'Ahmed Hassan',
    email: '[email protected]',
    phone: '01012345678', // Required for VF Cash
  },
  description: 'Product purchase',
  metadata: {
    orderId: 'ORD_12345',
    userId: 'user_789',
  },
  returnUrl: 'https://mysite.com/success',
  cancelUrl: 'https://mysite.com/cancel',
  webhookUrl: 'https://mysite.com/webhook',
});

if (result.success) {
  console.log('Transaction ID:', result.transactionId);
  console.log('Payment URL:', result.paymentUrl); // Redirect customer here
  console.log('Status:', result.status); // 'pending'
  
  // Provider-specific metadata
  if (result.provider === 'vf-cash') {
    console.log('USSD Code:', result.metadata?.ussdCode); // For feature phones
  }
}

Checking Payment Status

const status = await pixopay.getPaymentStatus('TXN_12345');

console.log('Status:', status.status); // pending, processing, completed, failed
console.log('Amount:', status.amount);
console.log('Currency:', status.currency);

Processing Refunds

// Full refund
const refund = await pixopay.refundPayment({
  transactionId: 'TXN_12345',
  reason: 'Customer request',
  metadata: {
    requestedBy: 'admin_user',
  },
});

// Partial refund
const partialRefund = await pixopay.refundPayment({
  transactionId: 'TXN_12345',
  amount: 50.00, // Refund 50 EGP out of 100 EGP
  reason: 'Partial refund for returned item',
});

if (refund.success) {
  console.log('Refund ID:', refund.refundId);
  console.log('Refunded amount:', refund.amount);
}

Webhook Handling

// Express.js example
app.post('/webhook/instapay', async (req, res) => {
  const payload = JSON.stringify(req.body);
  const signature = req.headers['x-signature'] as string;
  
  try {
    const event = await pixopay.verifyWebhook('instapay', payload, signature);
    
    console.log('Event type:', event.type); // payment.success, payment.failed, etc.
    console.log('Transaction ID:', event.transactionId);
    console.log('Event data:', event.data);
    
    // Handle event
    switch (event.type) {
      case 'payment.success':
        await fulfillOrder(event.transactionId);
        break;
      case 'payment.failed':
        await notifyCustomer(event.transactionId);
        break;
      case 'payment.refunded':
        await processRefund(event.transactionId);
        break;
    }
    
    res.status(200).send('OK');
  } catch (error) {
    console.error('Webhook verification failed:', error);
    res.status(400).send('Invalid signature');
  }
});

Transaction Management

// Get transaction by ID
const transaction = pixopay.getTransaction('TXN_12345');
console.log('Transaction:', transaction);

// Get all transactions
const allTransactions = pixopay.getAllTransactions();

// Get transactions by status
const pending = pixopay.getTransactionsByStatus('pending');
const completed = pixopay.getTransactionsByStatus('completed');

// Get statistics
const stats = pixopay.getStats();
console.log('Total transactions:', stats.transactions.total);
console.log('By status:', stats.transactions.byStatus);
console.log('By provider:', stats.transactions.byProvider);
console.log('Total revenue:', stats.transactions.totalAmount);
// Example: { EGP: 15000, USD: 500 }

Provider Management

// Get available providers
const providers = pixopay.getProviders();
console.log('Providers:', providers); // ['instapay', 'vf-cash', 'stripe']

// Check if provider is available
const hasInstapay = pixopay.isProviderAvailable('instapay');

// Create payment with specific provider
const result = await pixopay.createPayment(
  paymentRequest,
  'stripe' // Override default provider
);

Advanced Usage

Stripe-Specific Features

import { StripeAdapter } from '@pixora/pixolink-pixopay';

const stripe = new StripeAdapter();
await stripe.init(stripeConfig);

// Manual capture (authorization flow)
const authResult = await stripe.createPayment({
  ...paymentRequest,
  captureMethod: 'manual',
});

// Later, capture the payment
await stripe.capturePayment(authResult.transactionId, 100.00);

// Or cancel it
await stripe.cancelPayment(authResult.transactionId);

// Create customer for recurring payments
const customer = await stripe.createCustomer({
  name: 'Ahmed Hassan',
  email: '[email protected]',
  metadata: { userId: 'user_123' },
});

Direct Adapter Usage

If you need to use adapters without the plugin wrapper:

import { InstapayAdapter } from '@pixora/pixolink-pixopay';

const instapay = new InstapayAdapter();
await instapay.init({
  merchantId: 'MERCHANT_123',
  apiKey: 'api_key',
  secretKey: 'secret_key',
  sandboxMode: true,
});

const result = await instapay.createPayment(paymentRequest);
const status = await instapay.getPaymentStatus(result.transactionId);
const refund = await instapay.refundPayment(refundRequest);
const healthy = await instapay.isHealthy();

Transaction Export/Import

import { TransactionManager } from '@pixora/pixolink-pixopay';

const manager = new TransactionManager();

// Export transactions
const json = manager.export();
localStorage.setItem('transactions', json);

// Import transactions
const json = localStorage.getItem('transactions');
if (json) {
  const count = manager.import(json);
  console.log(`Imported ${count} transactions`);
}

Stale Transaction Detection

// Plugin automatically checks for stale transactions
// Configure in plugin config:
{
  staleTransactionCheckInterval: 30, // Check every 30 minutes
}

// Listen for stale transaction events
pixo.eventBus.on('pixopay:stale:transactions', (data) => {
  console.log('Found stale transactions:', data.count);
  
  // Manually check status for each
  for (const tx of data.transactions) {
    pixopay.getPaymentStatus(tx.id).then((status) => {
      console.log(`Transaction ${tx.id} status: ${status.status}`);
    });
  }
});

Event System

PixoPay emits events for monitoring and debugging:

// Payment created
pixo.eventBus.on('pixopay:payment:created', (data) => {
  console.log('Payment created:', data.transactionId);
  console.log('Provider:', data.provider);
  console.log('Amount:', data.amount, data.currency);
  console.log('Latency:', data.latency, 'ms');
});

// Payment error
pixo.eventBus.on('pixopay:payment:error', (data) => {
  console.error('Payment error:', data.provider, data.error);
});

// Payment refunded
pixo.eventBus.on('pixopay:payment:refunded', (data) => {
  console.log('Refund processed:', data.refundId);
});

// Webhook received
pixo.eventBus.on('pixopay:webhook:received', (data) => {
  console.log('Webhook:', data.provider, data.eventType);
});

// Plugin lifecycle
pixo.eventBus.on('pixopay:started', (data) => {
  console.log('PixoPay started with providers:', data.providers);
});

Phone Number Normalization (VF Cash)

VF Cash requires Egyptian phone numbers in the format 01XXXXXXXXX. The adapter automatically normalizes various formats:

// All these formats are normalized to: 01012345678
'01012345678'      // Already normalized ✓
'+201012345678'    // International format ✓
'00201012345678'   // International format ✓
'1012345678'       // Missing leading 0 → INVALID ✗
'0101234567'       // Only 10 digits → INVALID ✗

Best Practice: Always collect phone numbers with country code (+20) and normalize before storing.

Currency Handling

EGP (Egyptian Pound)

Instapay and VF Cash use piastres as minor units:

  • 1 EGP = 100 piastres
  • API sends amounts in piastres
  • PixoPay automatically converts: 100.50 EGP → 10050 piastres

Zero-Decimal Currencies (Stripe)

Some currencies don't use decimal places. Stripe handles these automatically:

  • JPY (Japanese Yen): ¥1,000 = 1000 (no conversion)
  • KRW (South Korean Won): ₩50,000 = 50000
  • VND (Vietnamese Dong): ₫100,000 = 100000
  • CLP (Chilean Peso): $5,000 = 5000

PixoPay detects these currencies and handles conversion correctly.

Error Handling

try {
  const result = await pixopay.createPayment(request);
  if (!result.success) {
    console.error('Payment failed:', result.message);
    // Handle user-facing error
  }
} catch (error) {
  if (error.message.includes('provider') && error.message.includes('not configured')) {
    console.error('Payment provider not available');
  } else if (error.message.includes('webhook')) {
    console.error('Webhook verification failed');
  } else {
    console.error('Unknown error:', error);
  }
}

Testing

Sandbox Credentials

Instapay Sandbox:

  • Base URL: https://sandbox.instapay.com.eg
  • Test cards: Visa 4111111111111111, Mastercard 5555555555554444
  • Set sandboxMode: true in config

VF Cash Sandbox:

  • Base URL: https://sandbox-api.vodafone.com.eg/vfcash
  • Test phone: 01000000001 (always succeeds)
  • Test phone: 01000000002 (always fails)
  • Set sandboxMode: true in config

Stripe Test Mode:

  • Use test API key: sk_test_...
  • Test cards: https://stripe.com/docs/testing

Unit Tests

import { InstapayAdapter } from '@pixora/pixolink-pixopay';
import { describe, it, expect } from 'vitest';

describe('InstapayAdapter', () => {
  it('should create payment successfully', async () => {
    const adapter = new InstapayAdapter();
    await adapter.init({
      merchantId: 'TEST_MERCHANT',
      apiKey: 'test_key',
      secretKey: 'test_secret',
      sandboxMode: true,
    });
    
    const result = await adapter.createPayment({
      amount: 100,
      currency: 'EGP',
      customer: {
        name: 'Test User',
        email: '[email protected]',
      },
      description: 'Test payment',
    });
    
    expect(result.success).toBe(true);
    expect(result.transactionId).toBeDefined();
    expect(result.paymentUrl).toBeDefined();
  });
});

Security Best Practices

  1. Never expose API keys - Use environment variables
  2. Verify webhook signatures - Always use verifyWebhook() before processing
  3. Use HTTPS - All webhook endpoints must use HTTPS in production
  4. Validate amounts - Use Zod schemas to validate payment requests
  5. Set sandboxMode correctly - Use sandbox for testing, production for live payments
  6. Rotate webhook secrets - Regularly rotate secrets and update config

Production Checklist

  • [ ] Switch sandboxMode: false for all providers
  • [ ] Use production API keys (not test keys)
  • [ ] Configure webhook endpoints with HTTPS
  • [ ] Set up webhook signature verification
  • [ ] Implement proper error logging (Sentry, etc.)
  • [ ] Add transaction persistence (database instead of in-memory)
  • [ ] Set up monitoring for stale transactions
  • [ ] Test all payment flows end-to-end
  • [ ] Implement refund workflows
  • [ ] Add rate limiting for API calls

Database Integration

The TransactionManager currently uses in-memory storage. For production, extend it with database persistence:

import { TransactionManager, Transaction } from '@pixora/pixolink-pixopay';
import { db } from './database';

class DatabaseTransactionManager extends TransactionManager {
  async createTransaction(result: PaymentResult): Transaction {
    const transaction = super.createTransaction(result);
    
    // Save to database
    await db.transactions.create({
      data: {
        id: transaction.id,
        provider: transaction.provider,
        status: transaction.status,
        amount: transaction.amount,
        currency: transaction.currency,
        metadata: JSON.stringify(transaction.metadata),
        createdAt: transaction.createdAt,
      },
    });
    
    return transaction;
  }
  
  async getTransaction(id: string): Promise<Transaction | undefined> {
    const tx = await db.transactions.findUnique({ where: { id } });
    if (!tx) return super.getTransaction(id);
    
    return {
      id: tx.id,
      provider: tx.provider as PaymentProvider,
      status: tx.status as PaymentStatus,
      amount: tx.amount,
      currency: tx.currency as Currency,
      metadata: JSON.parse(tx.metadata || '{}'),
      createdAt: new Date(tx.createdAt),
      updatedAt: new Date(tx.updatedAt),
    };
  }
  
  // Override other methods...
}

Type Exports

import type {
  PaymentProvider,    // 'stripe' | 'instapay' | 'vf-cash'
  PaymentStatus,      // 'pending' | 'processing' | 'completed' | 'failed' | 'refunded' | 'cancelled'
  Currency,           // 'EGP' | 'USD' | 'EUR' | 'GBP'
  CustomerInfo,
  PaymentRequest,
  PaymentResult,
  Transaction,
  RefundRequest,
  RefundResult,
  WebhookEvent,
  WebhookEventType,   // 'payment.success' | 'payment.failed' | 'payment.refunded' | 'payment.cancelled'
  PaymentAdapter,
  StripeConfig,
  InstapayConfig,
  VFCashConfig,
} from '@pixora/pixolink-pixopay';

// Zod schemas for validation
import {
  CustomerInfoSchema,
  PaymentRequestSchema,
  RefundRequestSchema,
} from '@pixora/pixolink-pixopay';

License

MIT

Support

For issues and questions:

  • GitHub: https://github.com/pixora/pixolink
  • Email: [email protected]
  • Documentation: https://pixolink.pixora.com

Built with ❤️ for the Egyptian market and beyond