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

saligpay-node

v0.3.4

Published

SaligPay Node.js SDK - Type-safe client for SaligPay payment integration

Downloads

2,416

Readme

saligpay-node

Official Node.js SDK for SaligPay payment integration. A type-safe, production-ready client for processing payments, managing checkout sessions, and handling webhooks.

npm version TypeScript Node.js

⚠️ Breaking Changes in v0.2.10

IMPORTANT: Version 0.2.10 contains breaking changes to fix authentication flow issues:

Changed: getUserWithMerchant method signature

  • Before: getUserWithMerchant(userId: string, adminKey: string)
  • After: getUserWithMerchant(userId: string, sessionToken: string)
  • Migration: Pass the session token from signIn() response instead of admin key

Changed: OAuthRegisterApiResponse structure

  • Before: Flat structure { clientId, clientSecret, merchantId }
  • After: Nested structure { credentials: { clientId, clientSecret?, merchantId }, isExisting: boolean }
  • Migration: Access credentials via response.credentials.clientId instead of response.clientId

New: getUserWithMerchantByAdminKey method

  • For internal/platform operations that need admin key authentication
  • Use this when you don't have a session token

See Migration Guide below for detailed instructions.

Features

  • Type-safe - Full TypeScript support with exported types
  • Dual build - Works with both ESM and CommonJS
  • Authentication - Client credentials OAuth flow
  • Checkout - Create and manage payment sessions
  • Webhooks - Parse and verify webhook events
  • Webhook Management - Register, monitor, and manage webhook endpoints
  • Payouts - Retrieve payout list, details, transactions, and schedules
  • Wallets - Manage wallet accounts, balances, and transfers
  • Transfers - Create batch transfers between accounts
  • Banks - List receiving institutions for Instapay/Pesonet
  • Error handling - Custom error classes with detailed context
  • Node.js 18+ - Uses native fetch API
  • Zero dependencies - Minimal runtime footprint

Installation

npm install saligpay-node
# or
yarn add saligpay-node
# or
pnpm add saligpay-node
# or
bun add saligpay-node

Quick Start

import { SaligPay } from "saligpay-node";

// Initialize the SDK
const saligpay = new SaligPay({
    clientId: process.env.SALIGPAY_CLIENT_ID!,
    clientSecret: process.env.SALIGPAY_CLIENT_SECRET!,
    env: "sandbox", // or 'production'
});

// Authenticate
await saligpay.authenticate();

// Create a checkout session
const checkout = await saligpay.checkout.create({
    externalId: "order-123",
    amount: 10000, // in centavos (₱100.00)
    description: "Premium Plan Subscription",
    webhookUrl: "https://yourapp.com/webhooks/saligpay",
    returnUrl: "https://yourapp.com/payment/success",
    contact: {
        name: "John Doe",
        email: "[email protected]",
        contact: "+639123456789",
    },
});

console.log("Checkout URL:", checkout.checkoutUrl);
// Redirect user to checkout.checkoutUrl to complete payment

Table of Contents

Migration Guide

Migrating from v0.2.9 to v0.2.10

1. Update getUserWithMerchant calls

Before:

const merchantData = await client.getUserWithMerchant(userId, adminKey);

After:

// First sign in to get session token
const { sessionToken } = await client.signIn({ email, password });
const merchantData = await client.getUserWithMerchant(userId, sessionToken);

2. Update OAuthRegisterApiResponse access

Before:

const result = await client.registerMerchant(payload, adminKey);
const clientId = result.clientId;
const clientSecret = result.clientSecret;

After:

const result = await client.registerMerchant(payload, adminKey);
const clientId = result.credentials.clientId;
const clientSecret = result.credentials.clientSecret;

3. Update internalAuthentication (if using)

The internalAuthentication method now uses a new internal method getUserWithMerchantByAdminKey. No changes needed if using the high-level internalAuthentication method, but if you were calling getUserWithMerchant directly with admin key, use the new method:

// Use this for admin-key based merchant lookup
const merchantData = await client.getUserWithMerchantByAdminKey(userId, adminKey);

Configuration

Environment Variables

Create a .env file (recommended):

SALIGPAY_CLIENT_ID=your_client_id
SALIGPAY_CLIENT_SECRET=your_client_secret
SALIGPAY_ENV=sandbox

SDK Configuration Options

interface SaligPayConfig {
    /** OAuth Client ID */
    clientId?: string;

    /** OAuth Client Secret */
    clientSecret?: string;

    /** Admin API Key for platform operations */
    adminKey?: string;

    /** Custom base URL (overrides env setting) */
    baseUrl?: string;

    /** Environment: 'production' | 'sandbox' */
    env?: "production" | "sandbox";
}

Example Configuration

// Using environment variables (recommended)
const saligpay = new SaligPay({
    clientId: process.env.SALIGPAY_CLIENT_ID,
    clientSecret: process.env.SALIGPAY_CLIENT_SECRET,
    env: process.env.SALIGPAY_ENV as "production" | "sandbox",
});

// Custom base URL for staging/testing
const saligpayStaging = new SaligPay({
    clientId: "your-id",
    clientSecret: "your-secret",
    baseUrl: "https://staging-api.saligpay.com",
});

Authentication

Client Credentials Flow

The SDK uses OAuth 2.0 client credentials for authentication:

import { SaligPay } from "saligpay-node";

const saligpay = new SaligPay({
    clientId: "your-client-id",
    clientSecret: "your-client-secret",
});

// Authenticate and store tokens
const tokens = await saligpay.authenticate();
console.log("Access Token:", tokens.accessToken);
console.log("Expires At:", tokens.expiresAt);
console.log("Refresh Token:", tokens.refreshToken);

// Check if authenticated
if (saligpay.isAuthenticated()) {
    console.log("Still authenticated!");
}

// Refresh token if expired
await saligpay.ensureAuthenticated();

Manual Authentication

You can also authenticate on-the-fly:

// Authenticate with provided credentials
const tokens = await saligpay.auth.authenticate("client-id", "client-secret");

Refresh Tokens

// Refresh an existing token
const newTokens = await saligpay.auth.refreshToken(refreshToken);

// Automatically refreshes when needed
await saligpay.ensureAuthenticated();

Token Validation

// Validate an access token
const isValid = await saligpay.auth.validateToken(accessToken);
console.log("Token valid:", isValid);

OAuthAuthenticateApiResponse

interface OAuthAuthenticateApiResponse {
    access_token: string;
    refresh_token: string;
    expires_in: number;
    token_type: string;
}

RefreshTokenApiResponse

interface RefreshTokenApiResponse {
    access_token: string;
    refresh_token: string;
    expires_in: number;
    token_type: string;
    status: string;
    metadata?: {
        clientId: string;
        tokenIssueTime: number;
    };
}

Full Login Flow (Platform/Admin)

For platform integrations that need to onboard merchants programmatically, use loginAndRetrieveCredentials. This method performs the complete authentication flow:

  1. Sign in — Authenticate user with email/password
  2. Get merchant — Retrieve associated merchant details
  3. Register OAuth — Create OAuth credentials for the merchant (idempotent)
  4. Authenticate — Get access tokens using the new credentials

[!IMPORTANT] This method requires an Admin Key and is intended for platform-level operations, not end-user authentication.

import { SaligPay } from "saligpay-node";

const saligpay = new SaligPay({
    adminKey: process.env.SALIGPAY_ADMIN_KEY!,
    env: "sandbox",
});

// Full login flow for a merchant
const result = await saligpay.auth.loginAndRetrieveCredentials(
    "[email protected]",
    "merchant-password",
);

// Result contains everything needed for subsequent API calls
console.log("User:", result.user);
console.log("Merchant:", result.merchant);
console.log("OAuth Credentials:", result.credentials);
console.log("Access Token:", result.tokens.accessToken);

LoginResult Structure

interface LoginResult {
    user: {
        id: string;
        email: string;
        name: string;
    };
    merchant: {
        id: string;
        email: string;
        tradeName: string;
        // ... other merchant fields
    };
    credentials: {
        clientId: string;
        clientSecret: string;
    };
    tokens: SaligPayAuthTokens;
}

Use Cases

Merchant Onboarding Flow:

// Platform backend onboarding a new merchant
async function onboardMerchant(email: string, password: string) {
    const saligpay = new SaligPay({
        adminKey: process.env.SALIGPAY_ADMIN_KEY!,
        env: "production",
    });

    const result = await saligpay.auth.loginAndRetrieveCredentials(
        email,
        password,
    );

    // Store credentials securely for future API calls
    await db.merchants.update({
        where: { email },
        data: {
            saligpayClientId: result.credentials.clientId,
            saligpayClientSecret: result.credentials.clientSecret,
            saligpayMerchantId: result.merchant.id,
        },
    });

    return result;
}

Multi-Tenant Platform:

// Create checkout on behalf of a merchant
async function createCheckoutForMerchant(
    merchantId: string,
    checkoutData: CreateCheckoutOptions,
) {
    const merchant = await db.merchants.findUnique({
        where: { id: merchantId },
    });

    const saligpay = new SaligPay({
        clientId: merchant.saligpayClientId,
        clientSecret: merchant.saligpayClientSecret,
        env: "production",
    });

    await saligpay.ensureAuthenticated();
    return saligpay.checkout.create(checkoutData);
}

Internal Authentication (Platform/Admin)

For platform integrations that need to authenticate merchants using only a user ID, use internalAuthentication. This method is similar to loginAndRetrieveCredentials but skips the email/password sign-in step and uses the admin key for authentication:

  1. Get merchant — Retrieve associated merchant details by user ID using admin key
  2. Register OAuth — Create OAuth credentials for the merchant (idempotent)
  3. Authenticate — Get access tokens using the new credentials

[!IMPORTANT] This method requires an Admin Key and is intended for platform-level operations where you already have the user ID.

import { SaligPay } from "saligpay-node";

const saligpay = new SaligPay({
    adminKey: process.env.SALIGPAY_ADMIN_KEY!,
    env: "sandbox",
});

// Authenticate using user ID
const result = await saligpay.auth.internalAuthentication(
    "user-id-from-your-system",
    "admin-key-or-leave-empty-to-use-config",
);

// Result contains everything needed for subsequent API calls
console.log("User:", result.user);
console.log("Merchant:", result.merchant);
console.log("OAuth Credentials:", result.credentials);
console.log("Access Token:", result.tokens.accessToken);

internalAuthentication Parameters

| Parameter | Type | Required | Description | | ---------- | ------ | -------- | --------------------------------------- | | userId | string | Yes | The user's ID from your system | | adminKey | string | No* | Admin key (uses config if not provided) |

*The adminKey parameter is optional if adminKey is set in the SDK configuration.

InternalAuthResult Structure

interface InternalAuthResult {
    user: {
        id: string;
        email: string;
        name: string;
    };
    merchant: {
        id: string;
        email: string;
        tradeName: string;
        // ... other merchant fields
    };
    credentials: {
        clientId: string;
        clientSecret: string;
    };
    tokens: SaligPayAuthTokens;
}

Use Cases

Platform Backend Operations:

// Internal admin operations without password
async function authenticateMerchantByUserId(userId: string, adminKey?: string) {
    const saligpay = new SaligPay({
        adminKey: adminKey || process.env.SALIGPAY_ADMIN_KEY!,
        env: "production",
    });

    const result = await saligpay.auth.internalAuthentication(userId, adminKey);

    // Store credentials securely
    await db.merchants.update({
        where: { userId },
        data: {
            saligpayClientId: result.credentials.clientId,
            saligpayClientSecret: result.credentials.clientSecret,
            saligpayMerchantId: result.merchant.id,
            lastAuthenticatedAt: new Date(),
        },
    });

    return result;
}

Automated Service Authentication:

// Background job authenticating multiple merchants
async function refreshMerchantTokens(merchantIds: string[]) {
    const saligpay = new SaligPay({
        adminKey: process.env.SALIGPAY_ADMIN_KEY!,
        env: "production",
    });

    for (const userId of merchantIds) {
        try {
            const result = await saligpay.auth.internalAuthentication(userId);

            // Update stored tokens
            await db.tokens.update({
                where: { userId },
                data: {
                    accessToken: result.tokens.accessToken,
                    refreshToken: result.tokens.refreshToken,
                    expiresAt: result.tokens.expiresAt,
                },
            });
        } catch (error) {
            console.error(`Failed to authenticate user ${userId}:`, error);
        }
    }
}

Checkout Sessions

Creating a Checkout Session

const checkout = await saligpay.checkout.create({
    externalId: "order-123",
    amount: 10000, // ₱100.00 in centavos
    description: "Premium Plan Subscription",
    webhookUrl: "https://yourapp.com/webhooks/saligpay",
    returnUrl: "https://yourapp.com/payment/success",
    contact: {
        name: "John Doe",
        email: "[email protected]",
        contact: "+639123456789",
    },
    metadata: {
        orderId: "12345",
        userId: "user-abc",
    },
    isThirdParty: true,
});

console.log("Checkout ID:", checkout.id);
console.log("Session Token:", checkout.sessionToken);
console.log("Checkout URL:", checkout.checkoutUrl);
console.log("Expires At:", checkout.expiresAt);
console.log("Status:", checkout.status);

CreateCheckoutApiResponse

interface CreateCheckoutApiResponse {
    id: string;
    sessionToken: string;
    amount: number;
    description: string;
    checkoutUrl: string;
    expiresAt: string;
    status: "pending" | "processing" | "completed" | "failed" | "cancelled" | "expired";
    createdAt: string;
}

Checkout Options

interface CreateCheckoutOptions {
    /** Unique external reference ID (required) */
    externalId: string;

    /** Amount in centavos (required) */
    amount: number;

    /** Description of the payment (required) */
    description: string;

    /** URL to receive webhook notifications (required) */
    webhookUrl: string;

    /** URL to redirect after payment (required) */
    returnUrl: string;

    /** Customer contact information (required) */
    contact: {
        name: string;
        email: string;
        contact?: string;
    };

    /** Additional metadata (optional) */
    metadata?: Record<string, unknown>;

    /** Is third party integration (optional, default: true) */
    isThirdParty?: boolean;
}

Contact Information

const contact = {
    name: "Juan Dela Cruz",
    email: "[email protected]",
    contact: "+639123456789", // Philippine format
};

Using Access Tokens

The SDK automatically uses stored tokens, but you can provide a custom token:

const checkout = await saligpay.checkout.create(
    {
        externalId: "order-456",
        amount: 25000,
        description: "One-time purchase",
        webhookUrl: "https://yourapp.com/webhooks/saligpay",
        returnUrl: "https://yourapp.com/payment/success",
        contact: {
            name: "Jane Smith",
            email: "[email protected]",
        },
    },
    "custom-access-token", // optional custom token
);

Payment Intents

Payment intents are used for mobile SDK integrations where you handle the payment flow directly in your app.

Create Payment Intent

const paymentIntent = await saligpay.paymentIntents.create({
    amount: 10000, // in centavos
    description: "Premium Plan Subscription",
    externalId: "order-123",
    webhookUrl: "https://yourapp.com/webhooks/saligpay",
    returnUrl: "https://yourapp.com/payment/success",
    contact: {
        name: "John Doe",
        email: "[email protected]",
        contact: "+639123456789",
    },
    metadata: {
        orderId: "12345",
    },
});

console.log("Payment Intent ID:", paymentIntent.id);
console.log("Client Key:", paymentIntent.clientKey);
console.log("Status:", paymentIntent.status);
console.log("Session Token:", paymentIntent.sessionToken);

PaymentIntentApiResponse

interface PaymentIntentApiResponse {
    id: string;
    transactionId: string;
    sessionToken: string;
    amount: number;
    status: "requires_payment_method" | "requires_confirmation" | "requires_action" | "processing" | "succeeded" | "canceled";
    description: string;
    clientKey: string;
    paymentMethods: string[];
    fee: number;
    createdAt: string;
    updatedAt: string;
}

Create Payment Intent Options

interface CreatePaymentIntentOptions {
    /** Amount in centavos (required) */
    amount: number;

    /** Description of the payment (required) */
    description: string;

    /** Unique external reference ID (optional) */
    externalId?: string;

    /** Customer contact information (optional) */
    contact?: {
        name: string;
        email: string;
        contact?: string;
    };

    /** URL to receive webhook notifications (optional) */
    webhookUrl?: string;

    /** URL to redirect after payment (optional) */
    returnUrl?: string;

    /** Additional metadata (optional) */
    metadata?: Record<string, unknown>;
}

Confirm Payment Intent

const confirmation = await saligpay.paymentIntents.confirm("pi_abc123", {
    paymentMethod: "card",
    creditCardDetails: {
        cardNumber: "4242424242424242",
        expMonth: "12",
        expYear: "2025",
        cvc: "123",
    },
    contact: {
        name: "John Doe",
        email: "[email protected]",
    },
});

console.log("Payment Status:", confirmation.status);
console.log("Requires Action:", confirmation.requiresAction);
if (confirmation.nextAction) {
    console.log("Next Action:", confirmation.nextAction.type);
}

PaymentConfirmationApiResponse

interface PaymentConfirmationApiResponse {
    intentId: string;
    status: "requires_payment_method" | "requires_confirmation" | "requires_action" | "processing" | "succeeded" | "canceled";
    redirectUrl?: string;
    requiresAction: boolean;
    nextAction?: {
        type: "redirect" | "display_details";
        redirectUrl?: string;
    };
}

Confirm Payment Intent Options

interface ConfirmPaymentIntentOptions {
    /** Payment method type (required) */
    paymentMethod: string;

    /** Credit card details (required for card payments) */
    creditCardDetails?: {
        cardNumber: string;
        expMonth: string;
        expYear: string;
        cvc: string;
    };

    /** Customer contact information (optional) */
    contact?: {
        name: string;
        email: string;
        contact?: string;
    };
}

Get Payment Intent Status

const status = await saligpay.paymentIntents.getStatus("pi_abc123");

console.log("Status:", status.status);
console.log("Amount:", status.amount);
console.log("Client Key:", status.clientKey);

Subscriptions

The SDK provides a complete subscription management system. Subscriptions are initiated via SDK, and payment is handled by SaligPay's hosted UI - no manual card entry through your app is needed.

Subscription Flow

1. Create/get a plan → 2. Initiate subscription session → 3. Redirect to subscriptionUrl → 4. Handle webhook for status updates

Create a Plan (if needed)

const plan = await saligpay.subscriptions.createPlan({
    name: "Premium Monthly",
    description: "Premium plan billed monthly",
    amount: 999, // ₱999.00
    currency: "PHP",
    interval: "month",
    interval_count: 1,
    billing_scheme: "stairstep",
});

console.log("Plan ID:", plan.id);

Initiate Subscription

const subscription = await saligpay.subscriptions.initiate({
    plan_id: "plan_abc123",
    customer_email: "[email protected]",
    customer_name: "John Doe",
    customer_phone: "+639123456789",
    externalId: "order-123",
});

console.log("Session Token:", subscription.sessionToken);
console.log("Customer ID:", subscription.customerId);
console.log("Subscription ID:", subscription.subscriptionId);

// Redirect user to SaligPay's hosted payment UI
console.log("Subscription URL:", subscription.subscriptionUrl);

InitiateSubscriptionResponseDto

interface InitiateSubscriptionResponseDto {
    success: boolean;
    sessionToken: string;
    clientSecret: string;
    customerId: string;
    subscriptionId?: string;
    plan: PlanDto;
    subscriptionUrl: string;
    publicKey: string;
    subscriptionUrl: string;
}

Subscription Options

interface InitiateSubscriptionRequestDto {
    /** Plan ID (required) */
    plan_id: string;

    /** Existing PayMongo customer ID (if provided, email/name are skipped) */
    customer_id?: string;

    /** Customer email (required if customer_id not provided) */
    customer_email?: string;

    /** Customer full name (required if customer_id not provided; split into first_name/last_name) */
    customer_name?: string;

    /** Customer phone (auto-formatted to +63 prefix, max 13 digits) */
    customer_phone?: string;

    /** Billing address (optional) */
    billing_address?: {
        line1: string;
        line2?: string;
        city: string;
        state: string;
        postal_code: string;
        country: string;
    };

    /** Secondary webhook URL for subscription events */
    webhookUrl?: string;

    /** Unique external reference ID for the subscription (e.g., order number) */
    externalId?: string;

    /** Additional metadata (optional) */
    metadata?: Record<string, unknown>;
}

Get Subscription Session

const session = await saligpay.subscriptions.getSession("session_token_abc");

console.log("Session Status:", session.status);
console.log("Plan:", session.plan);
console.log("Customer:", session.customer);

GetSessionResponseDto

interface GetSessionResponseDto {
    sessionToken: string;
    plan: {
        id: string;
        name: string;
        amount: number;
        currency: string;
        interval: string;
        intervalCount: number;
        description?: string;
    };
    customer: {
        email: string;
        name: string;
        phone?: string;
    };
    subscriptionId?: string;
    status: "pending" | "processing" | "completed" | "failed";
}

Process Subscription Payment

const payment = await saligpay.subscriptions.processPayment({
    sessionToken: "session_token_abc",
    cardDetails: {
        card_number: "4242424242424242",
        exp_month: "12",
        exp_year: "2025",
        cvc: "123",
    },
});

console.log("Payment Status:", payment.status);
console.log("Success:", payment.success);
if (payment.subscriptionId) {
    console.log("Subscription ID:", payment.subscriptionId);
}

// If 3D Secure is required
if (payment.status === "requires_action") {
    window.location.href = payment.redirectUrl;
}

ProcessSubscriptionPaymentResponseDto

interface ProcessSubscriptionPaymentResponseDto {
    success: boolean;
    status: "succeeded" | "requires_action" | "failed";
    subscriptionId?: string;
    redirectUrl?: string;
    nextActionUrl?: string;
    error?: string;
}

Get Subscription

const subscription = await saligpay.subscriptions.get("sub_abc123");

console.log("Status:", subscription.subscription.status);
console.log("Plan ID:", subscription.subscription.plan_id);
console.log("Next Billing:", subscription.subscription.next_billing_schedule);

SubscriptionResponseDto

interface SubscriptionResponseDto {
    success: boolean;
    subscription: SubscriptionDto;
}

interface SubscriptionDto {
    id: string;
    customer_id: string;
    plan_id: string;
    status: "active" | "inactive" | "canceled" | "trialing" | "past_due" | "unpaid" | "incomplete";
    anchor_date: string;
    next_billing_schedule: string;
    cancelled_at?: number;
    created_at: number;
    updated_at: number;
}

List Subscriptions

const subscriptions = await saligpay.subscriptions.list({
    limit: 20,
    customer_id: "cus_abc123",
    plan_id: "plan_abc123",
    sort_by: "created_at",
    order: "desc",
});

subscriptions.subscriptions.forEach((sub) => {
    console.log(`${sub.id} - ${sub.status}`);
});

SubscriptionsListResponseDto

interface SubscriptionsListResponseDto {
    subscriptions: SubscriptionDto[];
    hasMore: boolean;
}

List Options

interface SubscriptionsQueryDto {
    /** Number of items to return */
    limit?: number;
    /** Pagination cursor (after resource ID) */
    after?: string;
    /** Pagination cursor (before resource ID) */
    before?: string;
    /** Filter by customer ID */
    customer_id?: string;
    /** Filter by plan ID */
    plan_id?: string;
    /** Sort field */
    sort_by?: string;
    /** Sort direction */
    order?: "asc" | "desc";
}

Update Subscription Plan

const updated = await saligpay.subscriptions.updatePlan("sub_abc123", {
    plan_id: "plan_new_123",
});

console.log("New Plan ID:", updated.subscription.plan_id);
console.log("Success:", updated.success);

Update Subscription Payment Method

const updated = await saligpay.subscriptions.updatePaymentMethod("sub_abc123", {
    payment_method_id: "pm_new_123",
});

console.log("Success:", updated.success);
console.log("Payment Method ID:", updated.subscription.payment_method_id);

Cancel Subscription

const cancelled = await saligpay.subscriptions.cancel("sub_abc123", "No longer needed");

console.log("Success:", cancelled.success);
console.log("Status:", cancelled.subscription.status);

Create Subscription Plan

const plan = await saligpay.subscriptions.createPlan({
    name: "Premium Monthly",
    description: "Premium plan billed monthly",
    amount: 999, // ₱999.00
    currency: "PHP",
    interval: "monthly",
    interval_count: 1,
});

console.log("Plan ID:", plan.id);
console.log("Amount:", plan.amount); // Note: Returns centavos (e.g., 99900)
console.log("Interval:", plan.interval);

PlanDto

interface PlanDto {
    id: string;
    name: string;
    amount: number; // Note: Returns amount in centavos (as stored by PayMongo)
    currency: string;
    interval: "weekly" | "monthly" | "yearly";
    interval_count: number;
    description?: string;
    livemode: boolean;
    cycle_count?: number;
    metadata?: Record<string, unknown>;
    created_at: number;
    updated_at: number;
}

Create Plan Options

interface CreatePlanRequestDto {
    /** Plan name (required) */
    name: string;

    /** Plan description (optional) */
    description?: string;

    /** Amount in Philippine Peso (required, minimum 20) */
    amount: number;

    /** Currency code (required, default: PHP) */
    currency?: string;

    /** Billing interval (required): weekly, monthly, yearly */
    interval: "weekly" | "monthly" | "yearly";

    /** Number of intervals (optional, default: 1, max: 10) */
    interval_count?: number;

    /** Total billing cycles before auto-cancel (optional, minimum 2) */
    cycle_count?: number;

    /** Additional metadata (optional) */
    metadata?: Record<string, unknown>;
}

List Subscription Plans

const plans = await saligpay.subscriptions.listPlans({
    limit: 10,
});

plans.plans.forEach((plan) => {
    console.log(`${plan.id}: ${plan.name} - ₱${plan.amount / 100}`);
});
console.log("Has More:", plans.hasMore);

PlansListResponseDto

interface PlansListResponseDto {
    plans: PlanDto[];
    hasMore: boolean;
}

Get Subscription Plan

const plan = await saligpay.subscriptions.getPlan("plan_abc123");

console.log("Plan Name:", plan.name);
console.log("Amount:", plan.amount);
console.log("Interval:", plan.interval);
console.log("Currency:", plan.currency);

Payouts

List Payouts

const payouts = await saligpay.payouts.list({
    payout_status: "deposited",
    sort_by: "created_at",
    order: "desc",
    limit: 20,
});

console.log("Total records:", payouts.total_records);
payouts.data.forEach((payout) => {
    console.log(`${payout.id}: ₱${payout.attributes.amount / 100} - ${payout.attributes.status}`);
});

PayoutListResult

interface PayoutListResult {
    total_amount: number;
    total_per_currency: Record<string, { total_amount: number }>;
    total_records: number;
    pagination: {
        next_cursor: string | null;
        prev_cursor: string | null;
    };
    data: Array<{
        id: string;
        type: string;
        attributes: PayoutAttributes;
    }>;
}

interface PayoutAttributes {
    amount: number;
    adjustment_amount: number;
    bank_account_name: string;
    bank_account_number: string;
    bank_name: string;
    converted_net_amount: number;
    currency: string;
    fee: number;
    net_amount: number;
    status: string;
    provider: string;
    created_at: number;
}

List Options

interface PayoutListOptions {
    search?: string;
    payout_status?: "pending" | "on_hold" | "in_transit" | "deposited" | "returned" | "cancelled";
    sort_by?: "created_at" | "net_amount";
    order?: "asc" | "desc";
    provider?: "paymongo_central_hub" | "unionbank";
    "created_at.between"?: string;
    limit?: number;
    after?: string;
    before?: string;
}

Get Payout Details

const payout = await saligpay.payouts.get("po_abc123");

console.log("Amount:", payout.attributes.amount);
console.log("Net Amount:", payout.attributes.net_amount);
console.log("Status:", payout.attributes.status);
console.log("Bank:", payout.attributes.bank_name);

PayoutDetailResult

interface PayoutDetailResult {
    id: string;
    type: string;
    attributes: PayoutAttributes;
}

Get Payout Transactions

const transactions = await saligpay.payouts.getTransactions("po_abc123", {
    limit: 10,
});

transactions.data.forEach((tx) => {
    console.log(`${tx.id}: ₱${tx.attributes.amount / 100}`);
});

PayoutTransactionsResult

interface PayoutTransactionsResult {
    pagination: {
        next_cursor: string | null;
        prev_cursor: string | null;
    };
    data: Array<{
        id: string;
        type: string;
        attributes: TransactionAttributes;
    }>;
}

interface TransactionAttributes {
    amount: number;
    balance_transaction_id: string;
    created_at: number;
    currency: string;
    description: string | null;
    fee?: number;
    net_amount: number;
    payout_id: string;
    paid_at?: number;
}

Get Payout Schedule

const schedule = await saligpay.payouts.getSchedule("org_merchant123");

console.log("Schedule type:", schedule.attributes.type);
console.log("Available days:", schedule.attributes.options);
schedule.attributes.lineup.forEach((item) => {
    console.log(`₱${item.amount / 100} - ${new Date(item.receive_at * 1000).toDateString()}`);
});

PayoutScheduleResult

interface PayoutScheduleResult {
    id: string;
    type: string;
    attributes: {
        options: string[];
        type: string;
        days: string | null;
        lineup: Array<{
            amount: number;
            currency: string;
            transactions: {
                adjustments: { amount: number; count: number; currency: string };
                disputes: { amount: number; count: number; currency: string };
                payments: { amount: number; count: number; currency: string };
                refunds: { amount: number; count: number; currency: string };
            };
            generation_at: number;
            receive_at: number;
        }>;
    };
}

Wallets

[!IMPORTANT] Save Wallet Details After Authentication: The wallet ID is not returned during the OAuth authentication phase. After authenticating, you must call GET /client/{clientId} to retrieve your wallet details including the wallet_id. Save this wallet ID securely for subsequent wallet operations (get balance, transfers, etc.).

Recommended Flow:

  1. Authenticate with saligpay.authenticate()
  2. Retrieve merchant details via GET /client/{clientId} (or store the wallet_id during registration/onboarding)
  3. Save the wallet_id in your database or session
  4. Use the saved wallet_id for all wallet operations

Get Wallet Account

const wallet = await saligpay.wallets.get("wallet_abc123");

console.log("Account:", wallet.attributes.account_number);
console.log("Available Balance:", wallet.attributes.available_balance);
console.log("Status:", wallet.attributes.status);

WalletAccountResult

interface WalletAccountResult {
    id: string;
    type: string;
    attributes: {
        account_name: string;
        account_number: string;
        available_balance: number;
        pending_balance?: number;
        status: string;
        limits: {
            daily: { max_amount: number; remaining: number };
            monthly: { max_amount: number; remaining: number };
            per_transaction: { max_amount: number };
        };
        type: string;
        created_at: number;
        updated_at: number;
    };
}

Get Wallet Balance

const balance = await saligpay.wallets.getBalance("wallet_abc123");

console.log("Available:", balance.available);
console.log("Pending:", balance.pending);

WalletBalanceResult

interface WalletBalanceResult {
    available: number;
    pending: number;
}

Transfer from Wallet

const transfer = await saligpay.wallets.transfer("wallet_abc123", {
    destination_account: {
        number: "1234567890",
        name: "Juan Dela Cruz",
        bic: "BNORPHMM",
    },
    amount: 50000, // ₱500.00 in centavos
    provider: "instapay",
    description: "Wallet withdrawal",
    reference_number: "WITHDRAW-001",
});

console.log("Transfer ID:", transfer.id);
console.log("Status:", transfer.transfers[0].status);

CreateWalletTransferResponse

interface CreateWalletTransferResponse {
    id: string;
    transfers: WalletTransferResult[];
}

interface WalletTransferResult {
    id: string;
    source_account: Account;
    destination_account: Account;
    amount: number;
    fee: number;
    currency: string;
    provider: string;
    status: string;
    reference_number?: string;
    purpose?: string;
    description?: string;
    batch_transfer_id: string;
    created_at: string;
    updated_at: string;
}

interface Account {
    number: string;
    name: string;
    bic: string;
    bank_name?: string;
}

List Wallet Transfers

const transfers = await saligpay.wallets.listTransfers("wallet_abc123", {
    limit: 20,
});

console.log("Has more:", transfers.has_more);
transfers.data.forEach((t) => {
    console.log(`${t.id}: ₱${t.amount / 100} - ${t.status}`);
});

WalletTransferListResult

interface WalletTransferListResult {
    has_more: boolean;
    data: WalletTransferResult[];
}

Get Specific Transfer

const transfer = await saligpay.wallets.getTransfer(
    "wallet_abc123",
    "tf_transfer123",
);

console.log("Amount:", transfer.amount);
console.log("Fee:", transfer.fee);
console.log("Reference:", transfer.reference_number);

Transfers

Create Batch Transfer

const batchTransfer = await saligpay.transfers.create({
    transfers: [
        {
            source_account: {
                number: "1234567890",
                name: "Merchant Account",
                bic: "BNORPHMM",
            },
            destination_account: {
                number: "0987654321",
                name: "Partner Account",
                bic: "BNORPHMM",
            },
            amount: 100000, // ₱1,000.00
            currency: "PHP",
            provider: "instapay",
            reference_number: "BATCH-001",
            description: "Partner settlement",
        },
    ],
});

console.log("Batch Transfer ID:", batchTransfer.id);
console.log("Transfers created:", batchTransfer.transfers.length);

BatchTransfer

interface BatchTransfer {
    id: string;
    transfers: Transfer[];
}

interface Transfer {
    id: string;
    livemode: boolean;
    merchant_id: string;
    source_account: TransferAccount;
    destination_account: TransferAccount;
    amount: number;
    fee: number;
    currency: string;
    provider: "paymongo" | "instapay" | "pesonet";
    status: "pending" | "succeeded" | "failed";
    reference_number?: string;
    purpose?: string;
    description?: string;
    provider_reference_number?: string;
    batch_transfer_id: string;
    created_at: string;
    updated_at: string;
}

interface TransferAccount {
    number: string;
    name: string;
    bic: string;
    bank_name?: string;
}

List Transfers

const transfers = await saligpay.transfers.list({
    limit: 20,
    description: "settlement",
});

console.log("Has more:", transfers.has_more);
transfers.data.forEach((t) => {
    console.log(`${t.id}: ₱${t.amount / 100} - ${t.status}`);
});

TransferListResult

interface TransferListResult {
    has_more: boolean;
    data: Transfer[];
}

Get Transfer Details

const transfer = await saligpay.transfers.get("tf_abc123");

console.log("Status:", transfer.status);
console.log("Amount:", transfer.amount);
console.log("Fee:", transfer.fee);
console.log("Provider:", transfer.provider);

Banks

Get Receiving Institutions

const banks = await saligpay.banks.getReceivingInstitutions("instapay");

banks.forEach((bank) => {
    console.log(`${bank.attributes.name} (${bank.attributes.provider_code})`);
});

ReceivingInstitution

interface ReceivingInstitution {
    id: string;
    type: string;
    attributes: {
        name: string;
        provider: "instapay" | "pesonet";
        provider_code: string;
    };
}

Bank Types

  • instapay - Instapay participating banks and e-wallets
  • pesonet - PESONet participating banks

Webhook Management

The SaligPay SDK provides a full suite of webhook management endpoints for registering, monitoring, and maintaining your webhook endpoints programmatically.

Supported Event Types

| Category | Event Type | Description | |----------|-----------|-------------| | payments | payment.paid | Payment successfully completed | | payments | payment.failed | Payment failed | | payments | payment.refunded | Payment refunded | | checkout | checkout_session.payment.paid | Checkout session payment completed | | checkout | checkout_session.expired | Checkout session expired | | subscriptions | subscription.created | Subscription created | | subscriptions | subscription.updated | Subscription updated | | subscriptions | subscription.cancelled | Subscription cancelled | | subscriptions | subscription.resumed | Subscription resumed | | subscriptions | subscription.activated | Subscription activated | | subscriptions | subscription.past_due | Subscription is past due | | subscriptions | subscription.unpaid | Subscription is unpaid | | subscriptions | subscription.renewed | Subscription renewed | | subscriptions | subscription.trial_ended | Subscription trial ended | | subscriptions | subscription.payment.success | Subscription payment succeeded | | subscriptions | subscription.payment.failed | Subscription payment failed | | invoices | invoice.paid | Invoice paid | | invoices | invoice.failed | Invoice payment failed | | refunds | refund.created | Refund created | | refunds | refund.updated | Refund updated | | payouts | payout.created | Payout created | | payouts | payout.updated | Payout updated | | system | webhook.test | Test webhook event for endpoint verification |

Create Webhook

const webhook = await saligpay.webhookManagement.create({
    url: "https://yourapp.com/webhooks/saligpay",
    eventTypes: ["payment.paid", "payment.failed", "refund.created"],
    description: "Production webhook endpoint",
});

console.log("Webhook ID:", webhook.id);
console.log("Secret:", webhook.secret); // returned on creation only
console.log("Events:", webhook.events);

WebhookResponseDto

interface WebhookResponseDto {
    id: string;
    merchantId: string;
    url: string;
    description?: string;
    isActive: boolean;
    secret?: string;
    events: Array<{ eventType: string; id: string }>;
    createdAt: string;
    updatedAt: string;
}

List Webhooks

const webhooks = await saligpay.webhookManagement.list({
    limit: 20,
    offset: 0,
    isActive: true,
});

console.log("Total:", webhooks.meta.total);
webhooks.data.forEach((w) => {
    console.log(`${w.id} - ${w.url} (${w.isActive ? "active" : "inactive"})`);
});

WebhookListResponseDto

interface WebhookListResponseDto {
    data: WebhookResponseDto[];
    meta: {
        page: number;
        limit: number;
        total: number;
        totalPages: number;
        hasNext: boolean;
        hasPrevious: boolean;
    };
}

Get Webhook

const webhook = await saligpay.webhookManagement.get("wh_abc123");

console.log("URL:", webhook.url);
console.log("Active:", webhook.isActive);
console.log("Subscribed events:", webhook.events.map((e) => e.eventType));

Update Webhook

const updated = await saligpay.webhookManagement.update("wh_abc123", {
    url: "https://new-domain.com/webhooks/saligpay",
    eventTypes: ["payment.paid", "subscription.created"],
    description: "Updated endpoint",
    isActive: true,
});

Delete Webhook

await saligpay.webhookManagement.delete("wh_abc123");

Regenerate Secret

const result = await saligpay.webhookManagement.regenerateSecret("wh_abc123");

console.log("New secret:", result.secret);

Test Webhook

const result = await saligpay.webhookManagement.test("wh_abc123");

console.log("Success:", result.success);
console.log("Status code:", result.statusCode);
console.log("Response time:", result.responseTimeMs);

Get Event Types

const categories = await saligpay.webhookManagement.getEventTypes();

categories.forEach((group) => {
    console.log(`Category: ${group.category}`);
    group.events.forEach((event) => {
        console.log(`  - ${event.name}: ${event.description}`);
    });
});

Get Delivery Logs

const logs = await saligpay.webhookManagement.getDeliveryLogs("wh_abc123", {
    limit: 20,
    offset: 0,
    status: "failed",
    eventType: "payment.paid",
    from: "2025-01-01",
    to: "2025-12-31",
});

logs.data.forEach((log) => {
    console.log(`${log.eventType} - ${log.status} - ${log.statusCode}`);
});

DeliveryLogDto

interface DeliveryLogDto {
    id: string;
    webhookId: string;
    eventId: string;
    eventType: string;
    payloadHash: string;
    statusCode?: number;
    responseTimeMs?: number;
    attempt: number;
    status: string;
    errorMessage?: string;
    deliveredAt?: string;
    createdAt: string;
}

Get Delivery Log Detail

const log = await saligpay.webhookManagement.getDeliveryLog(
    "wh_abc123",
    "log_xyz789",
);

console.log("Payload snapshot:", log.payloadSnapshot);
console.log("Response body:", log.responseBody);

DeliveryLogDetailDto

interface DeliveryLogDetailDto extends DeliveryLogDto {
    payloadSnapshot?: string;
    requestBody?: unknown;
    responseBody?: string;
}

Replay Failed Delivery

const result = await saligpay.webhookManagement.replayDelivery(
    "wh_abc123",
    "log_xyz789",
);

console.log("Replay success:", result.success);
console.log("Log ID:", result.logId);

TestWebhookResponseDto

interface TestWebhookResponseDto {
    success: boolean;
    statusCode?: number;
    responseTimeMs?: number;
    errorMessage?: string;
    logId: string;
}

Trigger Webhook

Manually trigger a mock event to a webhook endpoint with a custom payload. This is useful for testing your webhook handlers without waiting for real events.

const result = await saligpay.webhookManagement.trigger("wh_abc123", {
    eventId: "evt-test-123",
    eventType: "subscription.payment.success",
    payload: {
        subscriptionId: "sub_abc123",
        planId: "plan_xyz789",
        amount: 100000,
        status: "active",
        customer: {
            id: "cus_123",
            name: "Juan Dela Cruz",
            email: "[email protected]",
            phone: "+639123456789",
        },
    },
});

console.log("Triggered:", result.success);
console.log("Log ID:", result.logId);
console.log("Response time:", result.responseTimeMs);

TriggerWebhookRequestDto

interface TriggerWebhookRequestDto {
    /** Unique event identifier */
    eventId: string;

    /** Event type to trigger (must be in supported events catalog) */
    eventType: string;

    /** Event payload sent to the webhook endpoint. Include a customer object with name, email, and phone for realistic testing. */
    payload: Record<string, unknown>;
}

Webhook Payload DTOs by Event Type

The following DTOs describe the payload shape for each event category when received at your webhook endpoint.

Payment Events Payload (payment.paid, payment.failed, payment.refunded)

interface PaymentWebhookPayload {
    id: string;
    type: "payment.paid" | "payment.failed" | "payment.refunded";
    timestamp: string;
    data: {
        id: string;
        externalId: string;
        amount: number;
        status: string;
        paymentMethod: {
            id: string;
            type: string;
        };
        customer?: {
            id?: string;
            name?: string;
            email?: string;
            phone?: string;
        };
        contact?: {
            name: string;
            email: string;
            phone?: string;
        };
        metadata?: Record<string, unknown>;
        createdAt: string;
        updatedAt: string;
    };
}

Checkout Events Payload (checkout_session.payment.paid, checkout_session.expired)

interface CheckoutWebhookPayload {
    id: string;
    type: "checkout_session.payment.paid" | "checkout_session.expired";
    timestamp: string;
    data: {
        id: string;
        sessionToken: string;
        status: string;
        amount: number;
        description?: string;
        customer?: {
            id?: string;
            name?: string;
            email?: string;
            phone?: string;
        };
        contact?: {
            name: string;
            email: string;
        };
        metadata?: Record<string, unknown>;
        createdAt: string;
        expiresAt: string;
    };
}

Subscription Events Payload

interface SubscriptionWebhookPayload {
    id: string;
    type:
        | "subscription.created"
        | "subscription.updated"
        | "subscription.cancelled"
        | "subscription.resumed"
        | "subscription.activated"
        | "subscription.past_due"
        | "subscription.unpaid"
        | "subscription.renewed"
        | "subscription.trial_ended"
        | "subscription.payment.success"
        | "subscription.payment.failed";
    timestamp: string;
    data: {
        subscriptionId: string;
        customerId: string;
        planId: string;
        status: string;
        amount: number;
        billingSchedule?: string;
        cancelledAt?: string;
        createdAt: string;
        updatedAt: string;
        metadata?: Record<string, unknown>;
        customer?: {
            id?: string;
            name?: string;
            email?: string;
            phone?: string;
        };
    };
}

Invoice Events Payload (invoice.paid, invoice.failed)

interface InvoiceWebhookPayload {
    id: string;
    type: "invoice.paid" | "invoice.failed";
    timestamp: string;
    data: {
        invoiceId: string;
        subscriptionId: string;
        customerId: string;
        amount: number;
        status: string;
        billingPeriodStart: string;
        billingPeriodEnd: string;
        dueDate: string;
        paidAt?: string;
        createdAt: string;
        customer?: {
            id?: string;
            name?: string;
            email?: string;
            phone?: string;
        };
    };
}

Refund Events Payload (refund.created, refund.updated)

interface RefundWebhookPayload {
    id: string;
    type: "refund.created" | "refund.updated";
    timestamp: string;
    data: {
        refundId: string;
        transactionId: string;
        amount: number;
        reason?: string;
        status: string;
        createdAt: string;
        updatedAt?: string;
        customer?: {
            id?: string;
            name?: string;
            email?: string;
            phone?: string;
        };
    };
}

Payout Events Payload (payout.created, payout.updated)

interface PayoutWebhookPayload {
    id: string;
    type: "payout.created" | "payout.updated";
    timestamp: string;
    data: {
        payoutId: string;
        merchantId: string;
        amount: number;
        currency: string;
        status: string;
        provider: string;
        bankAccountName: string;
        bankAccountNumber: string;
        bankName: string;
        createdAt: string;
        updatedAt?: string;
    };
}

Test Event Payload (webhook.test)

interface TestWebhookPayload {
    id: string;
    type: "webhook.test";
    timestamp: string;
    data: {
        message: string;
        webhookId: string;
    };
}
const stats = await saligpay.webhookManagement.getDeliveryStats("wh_abc123");

console.log("Total:", stats.total);
console.log("Success count:", stats.successCount);
console.log("Failed count:", stats.failedCount);
console.log("Success rate:", stats.successRate);
console.log("Avg response time:", stats.averageResponseTimeMs);

DeliveryStatsDto

interface DeliveryStatsDto {
    total: number;
    successCount: number;
    failedCount: number;
    successRate: number;
    averageResponseTimeMs?: number;
    failuresByEventType?: Record<string, number>;
}

Webhooks

Express.js Handler

import express from "express";
import { SaligPay } from "saligpay-node";

const app = express();
const saligpay = new SaligPay({
    clientId: process.env.SALIGPAY_CLIENT_ID,
    clientSecret: process.env.SALIGPAY_CLIENT_SECRET,
});

// Raw body parser for signature verification (if needed in future)
app.use("/webhooks/saligpay", express.raw({ type: "application/json" }));

app.post("/webhooks/saligpay", async (req, res) => {
    await saligpay.webhooks.listen(req, res, async (payload) => {
        console.log("Webhook received:", payload);

        switch (payload.status) {
            case "COMPLETED":
                // Update your database
                await db.orders.update(payload.externalId, {
                    status: "PAID",
                    paidAt: new Date(),
                });
                break;

            case "FAILED":
                await db.orders.update(payload.externalId, {
                    status: "FAILED",
                });
                break;

            case "PENDING":
                console.log("Payment pending for:", payload.externalId);
                break;

            default:
                console.log("Unknown status:", payload.status);
        }
    });
});

app.listen(3000, () => {
    console.log("Server running on port 3000");
});

Fastify Handler

import Fastify from "fastify";
import { SaligPay } from "saligpay-node";

const fastify = Fastify();
const saligpay = new SaligPay({
    clientId: process.env.SALIGPAY_CLIENT_ID,
    clientSecret: process.env.SALIGPAY_CLIENT_SECRET,
});

fastify.post("/webhooks/saligpay", async (request, reply) => {
    try {
        const payload = saligpay.webhooks.constructEvent(request.body);

        // Process webhook
        console.log("Payment:", payload.externalId, payload.status);

        return reply.send({ received: true });
    } catch (error) {
        return reply.code(400).send({ error: "Invalid webhook" });
    }
});

fastify.listen({ port: 3000 });

Manual Webhook Processing

// Process webhook manually
const payload = saligpay.webhooks.constructEvent(req.body);

// Access webhook data
console.log("External ID:", payload.externalId);
console.log("Amount:", payload.amount / 100, "PHP");
console.log("Status:", payload.status);
console.log("Payment Method:", payload.paymentMethod);
console.log("Contact:", payload.contact);
console.log("Metadata:", payload.metadata);

Webhook Payload Structure

interface SaligPayWebhookPayload {
    id?: string;
    externalId: string;
    amount: number; // in centavos
    status: "pending" | "processing" | "completed" | "failed" | "cancelled" | "expired" | "refunded";
    paymentMethod: {
        id: string;
        type: "gcash" | "maya" | "card" | "bank_transfer" | string;
    };
    contact?: {
        name: string;
        email: string;
        phone?: string;
    };
    metadata?: Record<string, unknown>;
    createdAt?: string;
    updatedAt?: string;
}

WebhookEventType

type WebhookEventType =
    | "checkout.completed"
    | "checkout.failed"
    | "checkout.expired"
    | "payment_intent.succeeded"
    | "payment_intent.failed"
    | "refund.created";

WebhookHandler

type WebhookHandler = (
    payload: SaligPayWebhookPayload,
) => Promise<void> | void;

Error Handling

Error Classes

The SDK provides custom error classes for better error handling:

import {
    SaligPayError,
    AuthenticationError,
    ValidationError,
    NotFoundError,
} from "saligpay-node";

Try-Catch Pattern

try {
    const checkout = await saligpay.checkout.create({
        externalId: "order-123",
        amount: 10000,
        description: "Test payment",
        webhookUrl: "https://yourapp.com/webhooks/saligpay",
        returnUrl: "https://yourapp.com/success",
        contact: { name: "John", email: "[email protected]" },
    });
} catch (error) {
    if (error instanceof AuthenticationError) {
        console.error("Authentication failed:", error.message);
        // Re-authenticate
        await saligpay.authenticate();
    } else if (error instanceof ValidationError) {
        console.error("Validation error:", error.message);
        console.error("Details:", error.details);
    } else if (error instanceof NotFoundError) {
        console.error("Resource not found:", error.message);
    } else if (error instanceof SaligPayError) {
        console.error("API error:", error.message);
        console.error("Status code:", error.statusCode);
        console.error("Error code:", error.code);
        console.error("Details:", error.details);
    } else {
        console.error("Unknown error:", error);
    }
}

Error Response Structure

try {
    await saligpay.checkout.create(options);
} catch (error) {
    if (error instanceof SaligPayError) {
        console.error(error.statusCode); // HTTP status code
        console.error(error.code); // API error code
        console.error(error.message); // Error message
        console.error(error.details); // Additional details
    }
}

Common Errors

| Error Class | Status Code | Description | | --------------------- | ----------- | -------------------------------- | | ValidationError | 400 | Invalid input data | | AuthenticationError | 401 | Invalid credentials | | NotFoundError | 404 | Resource not found | | SaligPayError | 500 | Server error or unexpected issue |

TypeScript Usage

The SDK is written in TypeScript and exports all types:

import {
    SaligPay,
    SaligPayConfig,
    SaligPayAuthTokens,
    CreateCheckoutOptions,
    CreateCheckoutApiResponse,
    SaligPayWebhookPayload,
    ContactInfo,
} from "saligpay-node";

// Strongly typed configuration
const config: SaligPayConfig = {
    clientId: "your-id",
    clientSecret: "your-secret",
    env: "sandbox",
};

// Typed response
const checkout: CreateCheckoutApiResponse = await saligpay.checkout.create({
    externalId: "order-123",
    amount: 10000,
    description: "Test",
    webhookUrl: "https://example.com/webhook",
    returnUrl: "https://example.com/success",
    contact: {
        name: "John Doe",
        email: "[email protected]",
    },
});

// Typed webhook payload
const handleWebhook = (payload: SaligPayWebhookPayload) => {
    if (payload.status === "COMPLETED") {
        // TypeScript knows payload has all required properties
        console.log(`Payment ${payload.externalId} completed`);
    }
};

CommonJS Usage

The SDK supports both ESM and CommonJS:

// Using require()
const { SaligPay } = require("saligpay-node");

const saligpay = new SaligPay({
    clientId: "your-client-id",
    clientSecret: "your-client-secret",
    env: "sandbox",
});

async function createCheckout() {
    try {
        const checkout = await saligpay.checkout.create({
            externalId: "order-123",
            amount: 10000,
            description: "Test payment",
            webhookUrl: "https://example.com/webhook",
            returnUrl: "https://example.com/success",
            contact: {
                name: "John Doe",
                email: "[email protected]",
            },
        });

        console.log(checkout.checkoutUrl);
    } catch (error) {
        console.error(error);
    }
}

createCheckout();

Testing

Example Test Suite

import { SaligPay } from "saligpay-node";

describe("SaligPay SDK", () => {
    let saligPay: SaligPay;

    beforeAll(() => {
        saligPay = new SaligPay({
            clientId: process.env.TEST_CLIENT_ID,
            clientSecret: process.env.TEST_CLIENT_SECRET,
            env: "sandbox",
        });
    });

    it("should authenticate successfully", async () => {
        const tokens = await saligPay.authenticate();

        expect(tokens).toBeDefined();
        expect(tokens.accessToken).toBeDefined();
        expect(tokens.refreshToken).toBeDefined();
        expect(tokens.expiresAt).toBeInstanceOf(Date);
    });

    it("should create a checkout session", async () => {
        await saligPay.authenticate();

        const checkout = await saligPay.checkout.create({
            externalId: "test-order",
            amount: 10000,
            description: "Test payment",
            webhookUrl: "https://example.com/webhook",
            returnUrl: "https://example.com/success",
            contact: {
                name: "Test User",
                email: "[email protected]",
            },
        });

        expect(checkout).toBeDefined();
        expect(checkout.id).toBeDefined();
        expect(checkout.checkoutUrl).toBeDefined();
    });

    it("should handle webhook payloads", () => {
        const payload = {
            externalId: "test-order",
            amount: 10000,
            status: "COMPLETED",
            paymentMethod: {
                id: "pm-test",
                type: "gcash",
            },
        };

        const event = saligPay.webhooks.constructEvent(payload);

        expect(event.externalId).toBe("test-order");
        expect(event.status).toBe("COMPLETED");
});

Server-Side Usage

The SDK is designed for server-side use only. Here are patterns for popular frameworks.

Singleton Pattern (Recommended)

Create a shared SDK instance to avoid re-initializing on every request:

// lib/saligpay.ts
import { SaligPay } from "saligpay-node";

let saligpayInstance: SaligPay | null = null;

export function getSaligPay(): SaligPay {
    if (!saligpayInstance) {
        saligpayInstance = new SaligPay({
            clientId: process.env.SALIGPAY_CLIENT_ID!,
            clientSecret: process.env.SALIGPAY_CLIENT_SECRET!,
            env:
                process.env.NODE_ENV === "production"
                    ? "production"
                    : "sandbox",
        });
    }
    return saligpayInstance;
}

React Router 7 (Remix)

Action: Create Checkout

// app/routes/checkout.tsx
import type { ActionFunctionArgs } from "react-router";
import { redirect } from "react-router";
import { getSaligPay } from "~/lib/saligpay.server";

export async function action({ request }: ActionFunctionArgs) {
    const formData = await request.formData();
    const planId = formData.get("planId") as string;
    const email = formData.get("email") as string;

    const saligpay = getSaligPay();
    await saligpay.ensureAuthenticated();

    const checkout = await saligpay.checkout.create({
        externalId: `order-${Date.now()}`,
        amount: planId === "premium" ? 149900 : 49900, // ₱1,499 or ₱499
        description: `${planId} Plan Subscription`,
        webhookUrl: `${process.env.APP_URL}/api/webhooks/saligpay`,
        returnUrl: `${process.env.APP_URL}/checkout/success`,
        contact: {
            name: formData.get("name") as string,
            email,
        },
        metadata: { planId, userId: formData.get("userId") },
    });

    return redirect(checkout.checkoutUrl);
}

export default function CheckoutPage() {
    return (
        <form method="post">
            <input type="hidden" name="planId" value="premium" />
            <input type="text" name="name" placeholder="Full Name" required />
            <input type="email" name="email" placeholder="Email" required />
            <button type="submit">Proceed to Payment</button>
        </form>
    );
}

Loader: Check Payment Status

// app/routes/payment.$orderId.tsx
import type { LoaderFunctionArgs } from "react-router";
import { json } from "react-router";
import { db } from "~/lib/db.server";

export async function loader({ params }: LoaderFunctionArgs) {
    const order = await db.orde