saligpay-node
v0.3.4
Published
SaligPay Node.js SDK - Type-safe client for SaligPay payment integration
Downloads
2,416
Maintainers
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.
⚠️ 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.clientIdinstead ofresponse.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-nodeQuick 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 paymentTable of Contents
- Breaking Changes
- Migration Guide
- Configuration
- Authentication
- Checkout Sessions
- Payment Intents
- Subscriptions
- Payouts
- Wallets
- Transfers
- Banks
- Webhook Management
- Webhooks
- Error Handling
- TypeScript Usage
- CommonJS Usage
- Testing
- Server-Side Usage
- Troubleshooting
- Security Best Practices
- Support
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=sandboxSDK 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:
- Sign in — Authenticate user with email/password
- Get merchant — Retrieve associated merchant details
- Register OAuth — Create OAuth credentials for the merchant (idempotent)
- 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:
- Get merchant — Retrieve associated merchant details by user ID using admin key
- Register OAuth — Create OAuth credentials for the merchant (idempotent)
- 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 updatesCreate 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 thewallet_id. Save this wallet ID securely for subsequent wallet operations (get balance, transfers, etc.).Recommended Flow:
- Authenticate with
saligpay.authenticate()- Retrieve merchant details via
GET /client/{clientId}(or store the wallet_id during registration/onboarding)- Save the
wallet_idin your database or session- Use the saved
wallet_idfor 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-walletspesonet- 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