@portous/payment-sdk
v2.1.1
Published
Official Node.js SDK for Portous Payment Platform - Accept payments, manage tokens, and handle transactions
Downloads
14
Maintainers
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 integrationTypeScript: ✨
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:
- ✅ Customer pays 100 ILS immediately
- ✅ Card is saved as a payment token
- ✅ You can charge this card later without customer entering details
- ✅ 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 detailsTypeScript: ✨
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:
- ✅ Fetches webhook secret from platform (via API key)
- ✅ Creates Express server on port 3003
- ✅ Registers webhook URL with platform (if public URL provided)
- ✅ Verifies all incoming webhook signatures
- ✅ 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=productionHeroku
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.comRailway
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.appDocker
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.tsdefinitions - ✅ ES Modules support - Use modern
importsyntax - ✅ 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
- Documentation: Complete Integration Guide
- Issues: GitHub Issues
- Email: [email protected]
Made with ❤️ by the Portous Team
