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

@portous/payment-sdk

v2.1.1

Published

Official Node.js SDK for Portous Payment Platform - Accept payments, manage tokens, and handle transactions

Downloads

14

Readme

🚀 Portous Payment SDK v2.1.0

Official Node.js SDK for Portous Payment Platform - Complete payment processing, tokenization, and webhook handling in one package!

NEW in v2.1.0: Full TypeScript Support!


✨ Features

  • 💳 Payment Processing: Create payments, payment links, and process transactions
  • 🔐 Card Tokenization: Save cards securely for future payments
  • 🔔 Automatic Webhooks: Built-in webhook server with signature verification
  • 📡 Event-Driven: Listen to payment events with simple event listeners
  • ⚡ Zero Configuration: Just API key + Profile ID = ready to go!
  • 🧪 Built-in Testing: Test webhooks and connections easily
  • 🛡️ Secure by Default: Automatic HMAC-SHA256 signature verification
  • ✨ Full TypeScript Support: Complete type definitions and autocomplete
  • 📦 Dual Package: Works with both ES Modules (import) and CommonJS (require)

📦 Installation

npm install @portous/payment-sdk

🚀 Quick Start

JavaScript (CommonJS)

const { PaymentClient } = require('@portous/payment-sdk');

// Initialize SDK
const client = new PaymentClient({
  apiKey: 'your-api-key',
  profileId: 'your-profile-id',
  baseUrl: 'https://payment-platform-production.up.railway.app/api/v1'
});

// Listen to payment events
client.on('webhook:payment.completed', (data) => {
  console.log('💰 Payment completed!', data.amount, data.currency);
  // Update order, send email, fulfill purchase
});

// Start webhook server (automatic!)
await client.startWebhook('https://your-backend.com');

// Create payment link
const payment = await client.payments.createLink({
  amount: 100.00,
  currency: 'ILS',
  transactionType: 'sale',
  billingInfo: {
    firstName: 'John',
    lastName: 'Doe',
    email: '[email protected]',
    addressLine1: '123 Main St',
    city: 'Ramallah',
    postalCode: '12345'
  },
  metadata: {
    orderId: '12345'
  }
});

console.log('Payment URL:', payment.paymentUrl);
console.log('HTML Form:', payment.htmlForm); // For WebView
// Send this URL to customer!

TypeScript (ES Modules) ✨ NEW!

import { PaymentClient, PaymentData, Transaction, ClientConfig } from '@portous/payment-sdk';

// ✅ Full type checking and autocomplete
const config: ClientConfig = {
  apiKey: 'your-api-key',
  profileId: 'your-profile-id',
  baseUrl: 'https://payment-platform-production.up.railway.app/api/v1',
  debug: true
};

const client = new PaymentClient(config);

// ✅ Typed event listeners
client.on('webhook:payment.completed', (data: Transaction) => {
  console.log('💰 Payment completed!', data.amount, data.currency);
  console.log('Transaction ID:', data.transactionId);
  console.log('Metadata:', data.metadata);
});

// Start webhook server
await client.startWebhook('https://your-backend.com');

// ✅ Typed payment data with autocomplete
const paymentData: PaymentData = {
  amount: 100.00,
  currency: 'ILS',
  transactionType: 'sale',
  billingInfo: {
    firstName: 'John',
    lastName: 'Doe',
    email: '[email protected]',
    addressLine1: '123 Main St',
    city: 'Ramallah',
    postalCode: '12345'
  },
  customerId: 'user123',
  metadata: {
    orderId: '12345',
    description: 'Product purchase'
  }
};

const payment = await client.payments.createLink(paymentData);
console.log('Payment URL:', payment.paymentUrl);

📚 Complete API Reference

🔧 Client Configuration

Constructor Options

JavaScript:

const client = new PaymentClient({
  apiKey: 'your-api-key',              // Required: Your API key
  profileId: 'your-profile-id',        // Required: Your profile UUID
  baseUrl: 'https://platform.com/api/v1', // Optional: Platform URL
  timeout: 30000,                       // Optional: Request timeout (ms)
  debug: false                          // Optional: Enable debug logging
});

TypeScript:

import { PaymentClient, ClientConfig } from '@portous/payment-sdk';

const config: ClientConfig = {
  apiKey: 'your-api-key',
  profileId: 'your-profile-id',
  baseUrl: 'https://platform.com/api/v1',
  timeout: 30000,
  debug: true
};

const client = new PaymentClient(config);

Client Methods

| Method | Description | Returns | |--------|-------------|---------| | healthCheck() | Test connection to platform | Promise<boolean> | | startWebhook(publicUrl, options?) | Start webhook server | Promise<Express> | | registerWebhookUrl(url) | Register webhook URL | Promise<{success, message}> | | testWebhook() | Test webhook functionality | Promise<object> | | fetchWebhookSecret() | Get webhook secret | Promise<string> | | verifyWebhookSignature(payload, sig, secret) | Verify webhook signature | boolean |


💳 Payments API (client.payments)

1. Create Payment Link

Create a payment link for customer to complete payment.

JavaScript:

const payment = await client.payments.createLink({
  amount: 250.50,
  currency: 'ILS',
  transactionType: 'sale', // 'sale', 'authorization', 'sale,create_payment_token'
  billingInfo: {
    firstName: 'John',
    lastName: 'Doe',
    email: '[email protected]',
    addressLine1: '123 Main St',
    city: 'Ramallah',
    postalCode: '12345',
    state: 'PS-RBH',
    country: 'PS'
  },
  customerId: 'CUST-999',  // Optional: Link to your customer
  referenceNumber: 'ORD-12345',
  metadata: {
    orderId: '12345',
    description: 'Product purchase'
  }
});

console.log('Payment URL:', payment.paymentUrl);
console.log('HTML Form:', payment.htmlForm); // For WebView integration

TypeScript:

import { PaymentData, PaymentLinkResponse } from '@portous/payment-sdk';

const paymentData: PaymentData = {
  amount: 250.50,
  currency: 'ILS',
  transactionType: 'sale',
  billingInfo: {
    firstName: 'John',
    lastName: 'Doe',
    email: '[email protected]',
    addressLine1: '123 Main St',
    city: 'Ramallah',
    postalCode: '12345'
  },
  customerId: 'CUST-999',
  referenceNumber: 'ORD-12345',
  metadata: {
    orderId: '12345'
  }
};

const payment: PaymentLinkResponse = await client.payments.createLink(paymentData);
console.log('Payment URL:', payment.paymentUrl);

1a. Payment + Save Card (One Transaction)

Charge customer AND save their card for future use in a single transaction:

JavaScript:

const payment = await client.payments.createLink({
  amount: 100.00,
  currency: 'ILS',
  transactionType: 'sale,create_payment_token',  // ← Pay + Save Card!
  billingInfo: {
    firstName: 'John',
    lastName: 'Doe',
    email: '[email protected]',
    addressLine1: '123 Main St',
    city: 'Ramallah',
    postalCode: '12345'
  },
  customerId: 'CUST-999',
  referenceNumber: 'ORD-12345'
});

console.log('Payment URL:', payment.paymentUrl);
// Customer pays AND card is saved - both in one step!

TypeScript:

const paymentData: PaymentData = {
  amount: 100.00,
  currency: 'ILS',
  transactionType: 'sale,create_payment_token',
  billingInfo: { /* ... */ },
  customerId: 'CUST-999',
  metadata: {
    saveCard: true
  }
};

const payment = await client.payments.createLink(paymentData);

What happens:

  1. ✅ Customer pays 100 ILS immediately
  2. ✅ Card is saved as a payment token
  3. ✅ You can charge this card later without customer entering details
  4. ✅ Perfect for first-time purchases with subscription option

Response:

{
  transactionUuid: "uuid-here",
  referenceNumber: "ORD-12345",
  hostedCheckoutUrl: "https://cybersource.com/checkout/...",
  amount: "250.50",
  currency: "ILS",
  status: "pending"
}

2. Create Payment Link

Create a shareable payment link that can be sent to customers.

const link = await client.payments.createLink({
  amount: 100.00,
  currency: 'ILS',
  description: 'Invoice #INV-2025-001',
  customerEmail: '[email protected]',
  customerName: 'Jane Smith',
  referenceNumber: 'INV-2025-001',
  
  // Optional: Expiration (in seconds)
  expiresIn: 86400, // 24 hours (default: 24 hours)
  
  // Optional: Usage limits
  maxUses: 1, // Default: 1 (one-time use)
  
  // Optional: Metadata
  metadata: {
    invoiceId: 'INV-2025-001',
    customerId: 'CUST-456'
  }
});

console.log('Payment Link:', link.paymentLink);
console.log('Link ID:', link.linkId);
console.log('Expires At:', link.expiresAt);

Response:

{
  linkId: "abc123xyz",
  transactionUuid: "uuid-here",
  paymentLink: "https://platform.com/p/abc123xyz",
  shortLink: "https://platform.com/p/abc123xyz",
  amount: "100.00",
  currency: "ILS",
  expiresAt: "2025-10-13T12:00:00.000Z",
  maxUses: 1,
  usedCount: 0,
  status: "active"
}

3. Get Payment Status

Get the status of a specific transaction.

const transaction = await client.payments.getStatus('transaction-uuid');

console.log('Status:', transaction.status);
console.log('Decision:', transaction.decision);
console.log('Amount:', transaction.amount);

Response:

{
  id: "uuid",
  referenceNumber: "ORD-12345",
  amount: "100.00",
  currency: "ILS",
  status: "completed", // 'pending', 'completed', 'declined', 'cancelled', 'error'
  decision: "ACCEPT",  // 'ACCEPT', 'DECLINE', 'ERROR', 'REVIEW', 'CANCEL'
  reasonCode: "100",
  message: "Transaction approved",
  customerEmail: "[email protected]",
  createdAt: "2025-10-12T10:00:00.000Z",
  updatedAt: "2025-10-12T10:05:00.000Z"
}

4. List Transactions

List all transactions with optional filtering.

const transactions = await client.payments.list({
  page: 1,
  limit: 20,
  
  // Optional filters
  status: 'completed', // 'pending', 'completed', 'declined', 'cancelled'
  startDate: '2025-10-01',
  endDate: '2025-10-31'
});

console.log('Total:', transactions.pagination.total);
console.log('Page:', transactions.pagination.page);
console.log('Transactions:', transactions.data.length);

transactions.data.forEach(tx => {
  console.log(`${tx.referenceNumber}: ${tx.amount} ${tx.currency} - ${tx.status}`);
});

Response:

{
  data: [
    {
      id: "uuid",
      referenceNumber: "ORD-001",
      amount: "100.00",
      currency: "ILS",
      status: "completed",
      decision: "ACCEPT",
      createdAt: "2025-10-12T10:00:00.000Z"
    }
    // ... more transactions
  ],
  pagination: {
    page: 1,
    limit: 20,
    total: 150,
    pages: 8
  }
}

5. Get Payment Link

Get details of a specific payment link.

const link = await client.payments.getLink('link-id');

console.log('Link URL:', link.paymentLink);
console.log('Status:', link.status);
console.log('Used:', link.usedCount, '/', link.maxUses);

6. List Payment Links

List all payment links.

const links = await client.payments.listLinks({
  page: 1,
  limit: 20,
  status: 'active' // 'active', 'completed', 'expired', 'cancelled'
});

links.data.forEach(link => {
  console.log(`${link.linkId}: ${link.amount} ${link.currency} - ${link.status}`);
});

7. Cancel Payment Link

Cancel an active payment link.

await client.payments.cancelLink('link-id');
console.log('Payment link cancelled');

8. Refund Payment

Request a refund for a completed payment.

const refund = await client.payments.refund({
  transactionId: 'original-transaction-uuid',
  amount: 50.00, // Partial refund (optional, full refund if omitted)
  reason: 'Customer requested refund'
});

console.log('Refund ID:', refund.refundId);
console.log('Status:', refund.status);

🔐 Tokenization API (client.tokens)

1. Create Tokenization Request

Create a request for customer to save their card.

JavaScript:

const tokenization = await client.tokens.create({
  billingInfo: {
    firstName: 'Jane',
    lastName: 'Smith',
    email: '[email protected]',
    addressLine1: '123 Main St',
    city: 'Ramallah',
    postalCode: '12345'
  },
  customerId: 'CUST-999',  // Your internal customer ID - links card to customer
  metadata: {
    source: 'mobile_app'
  }
});

console.log('Tokenization URL:', tokenization.tokenizationUrl);
console.log('HTML Form:', tokenization.htmlForm); // For WebView
// Send this URL to customer to enter card details

TypeScript:

import { TokenData, TokenCreationResponse } from '@portous/payment-sdk';

const tokenData: TokenData = {
  billingInfo: {
    firstName: 'Jane',
    lastName: 'Smith',
    email: '[email protected]',
    addressLine1: '123 Main St',
    city: 'Ramallah',
    postalCode: '12345'
  },
  customerId: 'CUST-999',
  metadata: {
    source: 'mobile_app'
  }
};

const tokenization: TokenCreationResponse = await client.tokens.create(tokenData);
console.log('Tokenization URL:', tokenization.tokenizationUrl);

Response:

{
  success: true,
  tokenRequestId: "uuid-here",
  status: "pending",
  tokenizationUrl: "https://cybersource.com/tokenize/...",
  htmlForm: "<form>...</form>" // For WebView integration
}

After customer saves card:

  • You receive webhook: webhook:token.created
  • Token is saved and can be used for future charges

2. List Saved Tokens

Get all saved payment tokens.

JavaScript:

const result = await client.tokens.list({
  page: 1,
  limit: 20,
  status: 'active', // 'active' or 'inactive'
  customerId: 'CUST-999' // Optional: filter by customer
});

console.log('Total tokens:', result.pagination.total);

result.tokens.forEach(token => {
  console.log(`Token: ${token.id}`);
  console.log(`  Customer: ${token.customerEmail}`);
  console.log(`  Card: ${token.cardType} ending in ${token.cardLastFour}`);
  console.log(`  Status: ${token.status}`);
  console.log(`  Created: ${new Date(token.createdAt).toLocaleDateString()}`);
});

TypeScript:

import { TokenListOptions, PaymentToken } from '@portous/payment-sdk';

const options: TokenListOptions = {
  page: 1,
  limit: 20,
  status: 'active',
  customerId: 'CUST-999'
};

const result = await client.tokens.list(options);

result.tokens.forEach((token: PaymentToken) => {
  console.log(`${token.cardType} **** ${token.cardLastFour}`);
});

2a. Get Cards for Specific Customer

Get all saved cards for a specific customer using their customer ID.

const customerCards = await client.tokens.list({
  customerId: 'CUST-999',  // Your internal customer ID
  isActive: true,
  page: 1,
  limit: 10
});

console.log(`Customer CUST-999 has ${customerCards.data.length} saved card(s)`);

customerCards.data.forEach(card => {
  console.log(`Card: ${card.card_type} ending in ${card.card_last_four}`);
  console.log(`  Expiry: ${card.card_expiry}`);
  console.log(`  Token ID: ${card.id}`);
});

Use case: Show customer their saved cards, allow them to manage or delete cards.

Response:

{
  data: [
    {
      id: "token-uuid",
      customer_email: "[email protected]",
      card_type: "001", // Visa
      masked_card_number: "****1234",
      expiry_date: "12/2025",
      status: "active",
      created_at: "2025-10-01T10:00:00.000Z",
      last_used: "2025-10-12T08:30:00.000Z"
    }
    // ... more tokens
  ],
  pagination: {
    page: 1,
    limit: 20,
    total: 45,
    pages: 3
  }
}

3. Get Token Details

Get details of a specific token.

const token = await client.tokens.get('token-uuid');

console.log('Card Type:', token.card_type);
console.log('Last 4 Digits:', token.masked_card_number);
console.log('Expiry:', token.expiry_date);
console.log('Status:', token.status);

4. Charge Saved Card

Charge a customer using their saved token (no card entry required!).

JavaScript:

const payment = await client.tokens.pay({
  paymentToken: 'saved-token-uuid',
  amount: 99.99,
  currency: 'ILS',
  referenceNumber: 'SUB-RENEWAL-2025-10',
  metadata: {
    subscriptionId: 'SUB-123',
    billingCycle: 'monthly'
  }
});

console.log('Payment created:', payment.transactionUuid);
console.log('Status:', payment.status);
console.log('HTML Form:', payment.htmlForm); // For 3DS verification if needed
// Customer is charged automatically!

TypeScript:

import { TokenPaymentData, PaymentLinkResponse } from '@portous/payment-sdk';

const tokenPaymentData: TokenPaymentData = {
  paymentToken: 'saved-token-uuid',
  amount: 99.99,
  currency: 'ILS',
  referenceNumber: 'SUB-RENEWAL-2025-10',
  metadata: {
    subscriptionId: 'SUB-123',
    billingCycle: 'monthly'
  }
};

const payment: PaymentLinkResponse = await client.tokens.pay(tokenPaymentData);
console.log('Payment status:', payment.status);

Response:

{
  transactionUuid: "uuid-here",
  referenceNumber: "SUB-RENEWAL-2025-10",
  amount: "99.99",
  currency: "ILS",
  status: "completed", // Usually instant!
  paymentToken: "saved-token-uuid"
}

5. Deactivate Token

Deactivate a saved card token.

await client.tokens.deactivate('token-uuid');
console.log('Token deactivated');

6. Get Token Statistics

Get statistics about your tokens.

const stats = await client.tokens.getStats();

console.log('Total tokens:', stats.total);
console.log('Active tokens:', stats.active);
console.log('Used tokens:', stats.used);
console.log('Expired tokens:', stats.expired);

7. Get Token Transactions

Get all transactions made with a specific saved card.

const history = await client.tokens.getTransactions('token-uuid', {
  page: 1,
  limit: 20
});

console.log(`Total charges: ${history.pagination.total}`);

history.transactions.forEach(tx => {
  console.log(`${new Date(tx.created_at).toLocaleDateString()}: ${tx.amount} ${tx.currency} - ${tx.status}`);
  console.log(`  Reference: ${tx.reference_number}`);
});

Use case: Show customer their card usage history or track subscription payments.


🔔 Webhook Events

The SDK automatically handles webhooks and emits events for your application.

Available Events

| Event | Description | When It Fires | |-------|-------------|---------------| | payment.completed | Payment successful | Customer completes payment | | payment.declined | Payment declined | Bank/Cybersource declines payment | | payment.cancelled | Payment cancelled | Customer cancels payment | | payment.error | Payment error | Technical error occurs | | payment.pending | Payment pending | Payment needs review | | webhook:started | Webhook server started | SDK starts webhook server | | webhook:stopped | Webhook server stopped | SDK stops webhook server | | webhook:received | Any webhook received | Any webhook arrives |

Event Data Structure

client.on('payment.completed', (data) => {
  console.log(data);
  /*
  {
    transactionUuid: "uuid-here",
    referenceNumber: "ORD-12345",
    amount: "100.00",
    currency: "ILS",
    status: "completed",
    decision: "ACCEPT",
    reasonCode: "100",
    message: "Transaction approved",
    customerEmail: "[email protected]",
    customerName: "John Doe",
    cardType: "001", // Visa
    maskedCardNumber: "****1234",
    processedAt: "2025-10-12T10:05:00.000Z",
    metadata: { ... } // Your custom metadata
  }
  */
});

Complete Event Handling Example

JavaScript:

const { PaymentClient } = require('@portous/payment-sdk');

const client = new PaymentClient({
  apiKey: process.env.API_KEY,
  profileId: process.env.PROFILE_ID,
  baseUrl: 'https://payment-platform-production.up.railway.app/api/v1'
});

// Payment completed
client.on('webhook:payment.completed', async (data) => {
  console.log(`✅ Payment completed: ${data.amount} ${data.currency}`);
  
  // YOUR BUSINESS LOGIC
  await updateOrderStatus(data.referenceNumber, 'paid');
  await sendConfirmationEmail(data.customerEmail);
  await fulfillOrder(data.referenceNumber);
  await updateInventory(data.metadata.items);
});

// Payment failed
client.on('webhook:payment.failed', async (data) => {
  console.log(`❌ Payment failed: ${data.reasonCode}`);
  
  await updateOrderStatus(data.referenceNumber, 'payment_failed');
  await sendPaymentFailedEmail(data.customerEmail);
});

// Token created (card saved)
client.on('webhook:token.created', async (data) => {
  console.log(`💳 Card saved: **** ${data.cardLastFour}`);
  
  await linkCardToCustomer(data.metadata.customerId, data.id);
});

// Webhook errors
client.on('webhook:error', (error) => {
  console.error(`💥 Webhook error: ${error.message}`);
  await alertAdminTeam(error);
});

// Webhook lifecycle
client.on('webhook:started', (info) => {
  console.log(`🔔 Webhook server started on port ${info.port}`);
  console.log(`   Webhook URL: ${info.webhookUrl}`);
});

// Start webhook server (auto-registers with platform)
await client.startWebhook('https://your-backend.com');

console.log('🚀 Ready to receive payments!');

TypeScript:

import { PaymentClient, Transaction, ClientConfig } from '@portous/payment-sdk';

const config: ClientConfig = {
  apiKey: process.env.API_KEY!,
  profileId: process.env.PROFILE_ID!,
  baseUrl: 'https://payment-platform-production.up.railway.app/api/v1'
};

const client = new PaymentClient(config);

// ✅ Full type checking for webhook data
client.on('webhook:payment.completed', async (data: Transaction) => {
  console.log(`✅ Payment completed: ${data.amount} ${data.currency}`);
  console.log(`Transaction ID: ${data.transactionId}`);
  console.log(`Metadata:`, data.metadata);
  
  // YOUR BUSINESS LOGIC with type safety
  await updateOrderStatus(data.referenceNumber, 'paid');
  await sendConfirmationEmail(data.customerEmail);
});

client.on('webhook:payment.failed', async (data: Transaction) => {
  console.log(`❌ Payment failed: ${data.reasonCode}`);
  await handleFailedPayment(data);
});

client.on('webhook:token.created', async (data: Transaction) => {
  console.log(`💳 Card saved: ${data.cardType} **** ${data.cardLastFour}`);
  await linkCardToCustomer(data.metadata?.customerId, data.id);
});

await client.startWebhook('https://your-backend.com');

🛠️ Webhook Management

Start Webhook Server

// Option 1: Production with auto-registration
await client.startWebhook('https://your-backend.com');
// SDK automatically registers: https://your-backend.com/webhooks/{profileId}

// Option 2: Development with ngrok
await client.startWebhook('https://abc123.ngrok.io');

// Option 3: Local only (manual dashboard setup)
await client.startWebhook();

What it does:

  1. ✅ Fetches webhook secret from platform (via API key)
  2. ✅ Creates Express server on port 3003
  3. ✅ Registers webhook URL with platform (if public URL provided)
  4. ✅ Verifies all incoming webhook signatures
  5. ✅ Emits events for your application

Stop Webhook Server

client.stopWebhook();

Manual Webhook Registration

// Usually not needed - startWebhook() does this automatically
await client.registerWebhookUrl('https://your-backend.com/webhooks');

🎯 Complete Usage Examples

Example 1: E-commerce Store

const PaymentClient = require('@portous/payment-sdk');

const client = new PaymentClient({
  apiKey: process.env.API_KEY,
  profileId: process.env.PROFILE_ID,
  baseUrl: 'https://payment-platform-production.up.railway.app/api/v1'
});

// Create payment link for order
async function createOrderPayment(order) {
  const link = await client.payments.createLink({
    amount: order.totalAmount,
    currency: 'ILS',
    description: `Order #${order.id}`,
    customerEmail: order.customerEmail,
    customerName: order.customerName,
    referenceNumber: order.id,
    expiresIn: 86400, // 24 hours
    metadata: {
      orderId: order.id,
      items: order.items
    }
  });
  
  return link.paymentLink; // Send to customer via email/SMS
}

// Handle payment completion
client.on('payment.completed', async (data) => {
  const order = await getOrder(data.referenceNumber);
  
  // Mark order as paid
  order.status = 'paid';
  order.paymentId = data.transactionUuid;
  await order.save();
  
  // Send confirmation
  await sendOrderConfirmation(order);
  
  // Fulfill order
  await fulfillOrder(order);
});

await client.startWebhook('https://store.example.com');

Example 2: Subscription Service

const PaymentClient = require('@portous/payment-sdk');

const client = new PaymentClient({
  apiKey: process.env.API_KEY,
  profileId: process.env.PROFILE_ID,
  baseUrl: 'https://payment-platform-production.up.railway.app/api/v1'
});

// Step 1: Customer signs up - save their card
async function signUpCustomer(email, name) {
  const tokenization = await client.tokens.create({
    customerEmail: email,
    customerName: name,
    referenceNumber: `SIGNUP-${Date.now()}`,
    description: 'Save card for subscription'
  });
  
  return tokenization.tokenizationUrl; // Send to customer
}

// Step 2: Charge customer monthly (automatic!)
async function chargeMonthlySubscription(customer) {
  const payment = await client.tokens.pay({
    tokenId: customer.savedTokenId,
    amount: 29.99,
    currency: 'USD',
    referenceNumber: `SUB-${customer.id}-${Date.now()}`,
    description: 'Monthly subscription'
  });
  
  return payment.transactionUuid;
}

// Step 3: Handle successful charges
client.on('payment.completed', async (data) => {
  if (data.referenceNumber.startsWith('SUB-')) {
    const customerId = data.referenceNumber.split('-')[1];
    
    // Extend subscription
    await extendSubscription(customerId, 30); // 30 days
    
    // Send receipt
    await sendReceipt(data.customerEmail, data);
  }
});

// Handle failed charges
client.on('payment.declined', async (data) => {
  if (data.referenceNumber.startsWith('SUB-')) {
    const customerId = data.referenceNumber.split('-')[1];
    
    // Notify customer
    await sendPaymentFailedNotification(customerId);
    
    // Retry in 3 days
    await scheduleRetry(customerId, 3);
  }
});

await client.startWebhook('https://subscription-service.com');

// Charge all subscriptions (run daily via cron)
async function chargeAllSubscriptions() {
  const customers = await getActiveSubscribers();
  
  for (const customer of customers) {
    try {
      await chargeMonthlySubscription(customer);
    } catch (error) {
      console.error(`Failed to charge ${customer.email}:`, error.message);
    }
  }
}

Example 3: Invoice Payment System

const PaymentClient = require('@portous/payment-sdk');

const client = new PaymentClient({
  apiKey: process.env.API_KEY,
  profileId: process.env.PROFILE_ID,
  baseUrl: 'https://payment-platform-production.up.railway.app/api/v1'
});

// Create invoice with payment link
async function createInvoice(invoice) {
  const link = await client.payments.createLink({
    amount: invoice.totalAmount,
    currency: invoice.currency,
    description: `Invoice ${invoice.invoiceNumber}`,
    customerEmail: invoice.customerEmail,
    customerName: invoice.customerName,
    referenceNumber: invoice.invoiceNumber,
    expiresIn: 30 * 24 * 60 * 60, // 30 days
    metadata: {
      invoiceId: invoice.id,
      dueDate: invoice.dueDate
    }
  });
  
  // Save payment link to invoice
  invoice.paymentLink = link.paymentLink;
  invoice.paymentLinkId = link.linkId;
  await invoice.save();
  
  // Send invoice email with payment link
  await sendInvoiceEmail(invoice);
  
  return link;
}

// Handle payment
client.on('payment.completed', async (data) => {
  const invoice = await findInvoiceByNumber(data.referenceNumber);
  
  // Mark as paid
  invoice.status = 'paid';
  invoice.paidAt = new Date();
  invoice.paymentId = data.transactionUuid;
  await invoice.save();
  
  // Send receipt
  await sendPaymentReceipt(invoice);
  
  // Update accounting system
  await updateAccounting(invoice);
});

await client.startWebhook('https://invoice-system.com');

🐛 Debug Mode

Enable detailed debug logging to troubleshoot issues:

const client = new PaymentClient({
  apiKey: 'your-api-key',
  profileId: 'your-profile-id',
  debug: true // Enable debug logging
});

What Debug Mode Shows:

  • 🐛 SDK initialization details (profile ID, API key prefix)
  • 🐛 Webhook secret retrieval process
  • 🐛 Webhook registration attempts
  • 🐛 Incoming webhook headers and body
  • 🐛 Signature verification details
  • 🐛 Event emission details
  • 🐛 API request/response details

Example Output:

🚀 Portous Payment SDK v2.0.1 initialized
   Base URL: https://platform.com/api/v1
   Webhook enabled: true
   🐛 Debug mode: ENABLED
   Profile ID: abc-123-def
   API Key: 709d0345...
🐛 [DEBUG] Fetching webhook secret from platform...
🐛 [DEBUG] Webhook secret retrieved: e362754a...
🐛 [DEBUG] Registering webhook URL with platform: https://my-app.com/webhooks
🐛 [DEBUG] Webhook URL registered successfully
🐛 [DEBUG] Webhook received: { headers: {...}, body: {...} }
🐛 [DEBUG] Signature verified successfully
🐛 [DEBUG] Emitting event: payment.completed {...}

⚠️ Warning: Debug mode logs sensitive information (API keys, secrets). Only use in development!


🚀 Production Deployment

Environment Variables

# Required
API_KEY=your-api-key
PROFILE_ID=your-profile-id

# Recommended
PUBLIC_URL=https://your-app.com  # For auto-registration
PLATFORM_URL=https://payment-platform-production.up.railway.app/api/v1

# Optional
WEBHOOK_PORT=3003
NODE_ENV=production

Heroku

heroku config:set API_KEY=your-api-key
heroku config:set PROFILE_ID=your-profile-id
heroku config:set PUBLIC_URL=https://your-app.herokuapp.com

Railway

railway variables set API_KEY=your-api-key
railway variables set PROFILE_ID=your-profile-id
railway variables set PUBLIC_URL=https://your-app.up.railway.app

Docker

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3003
ENV PUBLIC_URL=https://your-app.com
CMD ["node", "server.js"]

Using ngrok (Development)

# Terminal 1: Start ngrok
ngrok http 3003

# Terminal 2: Start your app with ngrok URL
PUBLIC_URL=https://abc123.ngrok.io node server.js

🔒 Security Features

  • Automatic Signature Verification: All webhooks verified with HMAC-SHA256
  • Secure Secret Management: Secrets fetched securely via API key
  • Timing-Safe Comparison: Prevents timing attacks
  • HTTPS Enforcement: Production requires HTTPS
  • IP Validation: No localhost/private IPs in production
  • Rate Limiting: Built-in protection

🧪 Testing

Test Connection

const isHealthy = await client.healthCheck();
console.log('Platform healthy:', isHealthy);

Test Webhook

const result = await client.testWebhook();
console.log('Webhook test:', result);
// Emits test event

📘 TypeScript Usage

Full Type Definitions

The SDK includes complete TypeScript definitions. Import types as needed:

import {
  // Main Client
  PaymentClient,
  ClientConfig,
  
  // Payment Types
  PaymentData,
  PaymentLinkResponse,
  Transaction,
  TransactionType,
  TransactionStatus,
  
  // Token Types
  TokenData,
  TokenPaymentData,
  TokenCreationResponse,
  PaymentToken,
  TokenStatus,
  TokenListOptions,
  
  // Other Types
  BillingInfo,
  RefundData,
  ListOptions,
  HealthCheckResponse,
  
  // Error Types
  PaymentError
} from '@portous/payment-sdk';

TypeScript Configuration

Ensure your tsconfig.json includes:

{
  "compilerOptions": {
    "module": "commonjs",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "skipLibCheck": true
  }
}

Example: NestJS Integration

import { Injectable, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PaymentClient, Transaction, PaymentData } from '@portous/payment-sdk';

@Injectable()
export class PaymentService implements OnModuleInit {
  private client: PaymentClient;

  constructor(private configService: ConfigService) {
    this.client = new PaymentClient({
      apiKey: this.configService.get('PORTOUS_API_KEY')!,
      profileId: this.configService.get('PORTOUS_PROFILE_ID')!,
      baseUrl: this.configService.get('PORTOUS_BASE_URL')!,
      debug: true
    });
  }

  async onModuleInit() {
    // Setup webhooks
    this.client.on('webhook:payment.completed', (data: Transaction) => {
      this.handlePaymentCompleted(data);
    });

    await this.client.startWebhook(
      this.configService.get('PUBLIC_URL')!
    );
  }

  async createPayment(amount: number, customerId: string) {
    const paymentData: PaymentData = {
      amount,
      currency: 'ILS',
      transactionType: 'sale',
      billingInfo: {
        firstName: 'Customer',
        lastName: 'Name',
        email: '[email protected]',
        addressLine1: 'Address',
        city: 'Ramallah',
        postalCode: '12345'
      },
      customerId,
      metadata: {
        customerId,
        source: 'api'
      }
    };

    return await this.client.payments.createLink(paymentData);
  }

  private handlePaymentCompleted(data: Transaction) {
    // Full type safety
    console.log(`Payment ${data.transactionId} completed`);
    console.log(`Amount: ${data.amount} ${data.currency}`);
    console.log(`Customer: ${data.metadata?.customerId}`);
  }
}

🔧 Troubleshooting

Common Issues

1. Webhook not receiving events

  • Check if PUBLIC_URL is accessible from internet
  • Verify firewall allows port 3003
  • Check webhook registration in dashboard

2. Signature verification failed

  • Ensure webhook secret is correct
  • Check system time synchronization
  • Enable debug mode to see signature details

3. Connection timeout

  • Verify platform URL is correct
  • Check network connectivity
  • Increase timeout value

📝 Changelog

v2.1.0 (Latest) ✨

  • Full TypeScript support with complete .d.ts definitions
  • ES Modules support - Use modern import syntax
  • Dual package - Works with both ESM and CommonJS
  • Enhanced types - All functions fully typed
  • Better autocomplete - IntelliSense in VS Code
  • NestJS optimized - Perfect for TypeScript projects
  • No breaking changes - Fully backward compatible

v2.0.5

  • Payment creation and tokenization
  • Webhook handling and auto-registration
  • Event-driven architecture
  • Customer ID linking
  • Metadata support

📄 License

MIT License - see LICENSE file for details


🤝 Support


Made with ❤️ by the Portous Team