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



- ✅ 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
- ✅ Error handling - Custom error classes with detailed context
- ✅ Node.js 18+ - Uses native fetch API
- ✅ Zero dependencies - Minimal runtime footprint
``bash`
npm install saligpay-nodeor
yarn add saligpay-nodeor
pnpm add saligpay-nodeor
bun add saligpay-node
`typescript
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: "john@example.com",
contact: "+639123456789",
},
});
console.log("Checkout URL:", checkout.checkoutUrl);
// Redirect user to checkout.checkoutUrl to complete payment
`
- Configuration
- Authentication
- Checkout Sessions
- Webhooks
- Error Handling
- TypeScript Usage
- CommonJS Usage
- Testing
- Server-Side Usage
- Singleton Pattern
- React Router 7 (Remix)
- Next.js (App Router)
- Next.js (Pages Router)
- Hono
- NestJS
- Elysia (Bun)
- Troubleshooting
- Security Best Practices
- Support
Create a .env file (recommended):
`bash`
SALIGPAY_CLIENT_ID=your_client_id
SALIGPAY_CLIENT_SECRET=your_client_secret
SALIGPAY_ENV=sandbox
`typescript
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";
}
`
`typescript
// 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",
});
`
The SDK uses OAuth 2.0 client credentials for authentication:
`typescript
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();
`
You can also authenticate on-the-fly:
`typescript`
// Authenticate with provided credentials
const tokens = await saligpay.auth.authenticate("client-id", "client-secret");
`typescript
// Refresh an existing token
const newTokens = await saligpay.auth.refreshToken(refreshToken);
// Automatically refreshes when needed
await saligpay.ensureAuthenticated();
`
`typescript`
// Validate an access token
const isValid = await saligpay.auth.validateToken(accessToken);
console.log("Token valid:", isValid);
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.
`typescript
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(
"merchant@example.com",
"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
`typescript`
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:
`typescript
// 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:
`typescript
// 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);
}
`
`typescript
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: "john@example.com",
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);
`
`typescript
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
/* Is third party integration (optional, default: true) /
isThirdParty?: boolean;
}
`
`typescript`
const contact = {
name: "Juan Dela Cruz",
email: "juan@example.com",
contact: "+639123456789", // Philippine format
};
The SDK automatically uses stored tokens, but you can provide a custom token:
`typescript`
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: "jane@example.com",
},
},
"custom-access-token", // optional custom token
);
`typescript
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");
});
`
`typescript
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 });
`
`typescript
// 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);
`
`typescript`
interface SaligPayWebhookPayload {
id?: string;
externalId: string;
amount: number; // in centavos
status: "COMPLETED" | "FAILED" | "PENDING" | "CANCELLED";
paymentMethod: {
id: string;
type: string;
};
contact?: {
name: string;
email: string;
contact?: string;
};
metadata?: Record
createdAt?: string;
updatedAt?: string;
}
The SDK provides custom error classes for better error handling:
`typescript`
import {
SaligPayError,
AuthenticationError,
ValidationError,
NotFoundError,
} from "saligpay-node";
`typescript`
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: "john@example.com" },
});
} 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);
}
}
`typescript`
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
}
}
| 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 |
The SDK is written in TypeScript and exports all types:
`typescript
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: "john@example.com",
},
});
// Typed webhook payload
const handleWebhook = (payload: SaligPayWebhookPayload) => {
if (payload.status === "COMPLETED") {
// TypeScript knows payload has all required properties
console.log(Payment ${payload.externalId} completed);`
}
};
The SDK supports both ESM and CommonJS:
`javascript
// 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: "john@example.com",
},
});
console.log(checkout.checkoutUrl);
} catch (error) {
console.error(error);
}
}
createCheckout();
`
`typescript
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: "test@example.com",
},
});
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");
});
`
The SDK is designed for server-side use only. Here are patterns for popular frameworks.
Create a shared SDK instance to avoid re-initializing on every request:
`typescript
// 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;
}
`
---
#### Action: Create Checkout
`typescript
// 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()},${planId} Plan Subscription
amount: planId === "premium" ? 149900 : 49900, // ₱1,499 or ₱499
description: ,${process.env.APP_URL}/api/webhooks/saligpay
webhookUrl: ,${process.env.APP_URL}/checkout/success
returnUrl: ,
contact: {
name: formData.get("name") as string,
email,
},
metadata: { planId, userId: formData.get("userId") },
});
return redirect(checkout.checkoutUrl);
}
export default function CheckoutPage() {
return (
#### Loader: Check Payment Status
`typescript
// 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.order.findUnique({
where: { id: params.orderId },
});
if (!order) {
throw new Response("Order not found", { status: 404 });
}
return json({
orderId: order.id,
status: order.status,
amount: order.amount,
paidAt: order.paidAt,
});
}
`#### Resource Route: Webhook Handler
`typescript
// app/routes/api.webhooks.saligpay.ts
import type { ActionFunctionArgs } from "react-router";
import { json } from "react-router";
import { getSaligPay } from "~/lib/saligpay.server";
import { db } from "~/lib/db.server";export async function action({ request }: ActionFunctionArgs) {
const saligpay = getSaligPay();
const body = await request.json();
try {
const payload = saligpay.webhooks.constructEvent(body);
switch (payload.status) {
case "COMPLETED":
await db.order.update({
where: { externalId: payload.externalId },
data: {
status: "PAID",
paidAt: new Date(),
paymentMethod: payload.paymentMethod.type,
},
});
break;
case "FAILED":
await db.order.update({
where: { externalId: payload.externalId },
data: { status: "FAILED" },
});
break;
}
return json({ received: true });
} catch (error) {
console.error("Webhook error:", error);
return json({ error: "Invalid webhook" }, { status: 400 });
}
}
`---
$3
#### Server Action: Create Checkout
`typescript
// app/actions/checkout.ts
"use server";import { redirect } from "next/navigation";
import { getSaligPay } from "@/lib/saligpay";
export async function createCheckout(formData: FormData) {
const saligpay = getSaligPay();
await saligpay.ensureAuthenticated();
const checkout = await saligpay.checkout.create({
externalId:
order-${Date.now()},
amount: Number(formData.get("amount")),
description: formData.get("description") as string,
webhookUrl: ${process.env.NEXT_PUBLIC_APP_URL}/api/webhooks/saligpay,
returnUrl: ${process.env.NEXT_PUBLIC_APP_URL}/checkout/success,
contact: {
name: formData.get("name") as string,
email: formData.get("email") as string,
},
}); redirect(checkout.checkoutUrl);
}
`#### Route Handler: Webhook
`typescript
// app/api/webhooks/saligpay/route.ts
import { NextRequest, NextResponse } from "next/server";
import { getSaligPay } from "@/lib/saligpay";
import { prisma } from "@/lib/prisma";export async function POST(request: NextRequest) {
const saligpay = getSaligPay();
const body = await request.json();
try {
const payload = saligpay.webhooks.constructEvent(body);
if (payload.status === "COMPLETED") {
await prisma.order.update({
where: { externalId: payload.externalId },
data: {
status: "PAID",
paidAt: new Date(),
},
});
}
return NextResponse.json({ received: true });
} catch (error) {
console.error("Webhook error:", error);
return NextResponse.json({ error: "Invalid webhook" }, { status: 400 });
}
}
`#### Server Component with Checkout Button
`typescript
// app/checkout/page.tsx
import { createCheckout } from "@/app/actions/checkout";export default function CheckoutPage() {
return (
);
}
`---
$3
#### API Route: Create Checkout
`typescript
// pages/api/checkout/create.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { getSaligPay } from "@/lib/saligpay";export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" });
}
const saligpay = getSaligPay();
await saligpay.ensureAuthenticated();
try {
const { amount, description, name, email } = req.body;
const checkout = await saligpay.checkout.create({
externalId:
order-${Date.now()},
amount,
description,
webhookUrl: ${process.env.NEXT_PUBLIC_APP_URL}/api/webhooks/saligpay,
returnUrl: ${process.env.NEXT_PUBLIC_APP_URL}/checkout/success,
contact: { name, email },
}); return res.json({ checkoutUrl: checkout.checkoutUrl });
} catch (error) {
console.error("Checkout error:", error);
return res.status(500).json({ error: "Failed to create checkout" });
}
}
`#### API Route: Webhook Handler
`typescript
// pages/api/webhooks/saligpay.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { getSaligPay } from "@/lib/saligpay";
import { prisma } from "@/lib/prisma";export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" });
}
const saligpay = getSaligPay();
try {
const payload = saligpay.webhooks.constructEvent(req.body);
if (payload.status === "COMPLETED") {
await prisma.order.update({
where: { externalId: payload.externalId },
data: { status: "PAID", paidAt: new Date() },
});
}
return res.json({ received: true });
} catch (error) {
console.error("Webhook error:", error);
return res.status(400).json({ error: "Invalid webhook" });
}
}
`---
$3
`typescript
// src/index.ts
import { Hono } from "hono";
import { SaligPay } from "saligpay-node";const app = new Hono();
const saligpay = new SaligPay({
clientId: process.env.SALIGPAY_CLIENT_ID!,
clientSecret: process.env.SALIGPAY_CLIENT_SECRET!,
env: "sandbox",
});
// Create checkout
app.post("/checkout", async (c) => {
await saligpay.ensureAuthenticated();
const { amount, description, name, email } = await c.req.json();
const checkout = await saligpay.checkout.create({
externalId:
order-${Date.now()},
amount,
description,
webhookUrl: ${process.env.APP_URL}/webhooks/saligpay,
returnUrl: ${process.env.APP_URL}/success,
contact: { name, email },
}); return c.json({ checkoutUrl: checkout.checkoutUrl });
});
// Webhook handler
app.post("/webhooks/saligpay", async (c) => {
const body = await c.req.json();
try {
const payload = saligpay.webhooks.constructEvent(body);
if (payload.status === "COMPLETED") {
// Update your database
console.log(
Payment ${payload.externalId} completed!);
} return c.json({ received: true });
} catch (error) {
return c.json({ error: "Invalid webhook" }, 400);
}
});
export default app;
`---
$3
#### Service
`typescript
// src/saligpay/saligpay.service.ts
import { Injectable, OnModuleInit } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { SaligPay, CreateCheckoutOptions } from "saligpay-node";@Injectable()
export class SaligPayService implements OnModuleInit {
private client: SaligPay;
constructor(private configService: ConfigService) {
this.client = new SaligPay({
clientId: this.configService.get("SALIGPAY_CLIENT_ID"),
clientSecret: this.configService.get("SALIGPAY_CLIENT_SECRET"),
env: this.configService.get("NODE_ENV") === "production"
? "production"
: "sandbox",
});
}
async onModuleInit() {
await this.client.authenticate();
}
async createCheckout(options: CreateCheckoutOptions) {
await this.client.ensureAuthenticated();
return this.client.checkout.create(options);
}
parseWebhook(body: unknown) {
return this.client.webhooks.constructEvent(body);
}
}
`#### Controller
`typescript
// src/saligpay/saligpay.controller.ts
import { Controller, Post, Body, Res, HttpStatus } from "@nestjs/common";
import { Response } from "express";
import { SaligPayService } from "./saligpay.service";
import { OrdersService } from "../orders/orders.service";@Controller("webhooks")
export class WebhooksController {
constructor(
private saligpayService: SaligPayService,
private ordersService: OrdersService,
) {}
@Post("saligpay")
async handleWebhook(@Body() body: unknown, @Res() res: Response) {
try {
const payload = this.saligpayService.parseWebhook(body);
if (payload.status === "COMPLETED") {
await this.ordersService.markAsPaid(payload.externalId);
}
return res.status(HttpStatus.OK).json({ received: true });
} catch (error) {
return res.status(HttpStatus.BAD_REQUEST).json({
error: "Invalid webhook",
});
}
}
}
`---
$3
`typescript
// src/index.ts
import { Elysia } from "elysia";
import { SaligPay } from "saligpay-node";const saligpay = new SaligPay({
clientId: process.env.SALIGPAY_CLIENT_ID!,
clientSecret: process.env.SALIGPAY_CLIENT_SECRET!,
env: "sandbox",
});
const app = new Elysia()
.post("/checkout", async ({ body }) => {
await saligpay.ensureAuthenticated();
const checkout = await saligpay.checkout.create({
externalId:
order-${Date.now()},
amount: body.amount,
description: body.description,
webhookUrl: ${process.env.APP_URL}/webhooks/saligpay,
returnUrl: ${process.env.APP_URL}/success,
contact: { name: body.name, email: body.email },
}); return { checkoutUrl: checkout.checkoutUrl };
})
.post("/webhooks/saligpay", async ({ body, set }) => {
try {
const payload = saligpay.webhooks.constructEvent(body);
if (payload.status === "COMPLETED") {
console.log(
Payment ${payload.externalId} completed!);
} return { received: true };
} catch {
set.status = 400;
return { error: "Invalid webhook" };
}
})
.listen(3000);
console.log(
Server running at ${app.server?.hostname}:${app.server?.port});
`Troubleshooting
$3
Problem: Getting
AuthenticationError`typescript
// Solution: Verify credentials are correct
const saligpay = new SaligPay({
clientId: process.env.SALIGPAY_CLIENT_ID, // Double-check
clientSecret: process.env.SALIGPAY_CLIENT_SECRET, // Double-check
env: "sandbox", // Ensure correct environment
});// Test authentication
try {
await saligpay.authenticate();
console.log("Authentication successful!");
} catch (error) {
console.error("Auth failed:", error);
}
`$3
Problem:
AuthenticationError: Invalid access token`typescript
// Solution: Use ensureAuthenticated()
await saligPay.ensureAuthenticated();// This automatically refreshes expired tokens
const checkout = await saligpay.checkout.create(options);
`$3
Problem:
ValidationError: Amount must be greater than 0`typescript
// Solution: Validate input before API call
const createCheckout = async (options: CreateCheckoutOptions) => {
// Client-side validation
if (options.amount <= 0) {
throw new Error("Amount must be greater than 0");
} if (!options.externalId) {
throw new Error("External ID is required");
}
return saligpay.checkout.create(options);
};
`$3
Problem: Invalid JSON payload
`typescript
// Solution: Ensure raw body is passed to webhook handler
app.use(express.raw({ type: "application/json" }));app.post("/webhooks/saligpay", async (req, res) => {
await saligpay.webhooks.listen(req, res, handler);
});
`$3
Problem: Module not found or import errors
`typescript
// Solution: Ensure Node.js 18+ is installed
// Check Node version
console.log(process.version); // Should be v18.x.x or higher// If using TypeScript, ensure tsconfig.json has correct module settings
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext",
"target": "ES2022"
}
}
`Security Best Practices
$3
`bash
.gitignore
.env
.env.local
.env.*.local
`$3
`typescript
// ✅ Good - Use environment variables
const saligpay = new SaligPay({
clientId: process.env.SALIGPAY_CLIENT_ID,
clientSecret: process.env.SALIGPAY_CLIENT_SECRET,
});// ❌ Bad - Hardcoded credentials
const saligpay = new SaligPay({
clientId: "hardcoded-client-id",
clientSecret: "hardcoded-secret",
});
`$3
`typescript
// Store and verify webhook secret (future feature)
const WEBHOOK_SECRET = process.env.SALIGPAY_WEBHOOK_SECRET;// Always validate payload structure
app.post("/webhooks/saligpay", async (req, res) => {
try {
const payload = saligpay.webhooks.constructEvent(req.body);
// Additional validation
if (!payload.externalId || !payload.status) {
return res.status(400).send({ error: "Invalid payload" });
}
// Process webhook
await handlePayment(payload);
return res.send({ received: true });
} catch (error) {
console.error("Webhook error:", error);
return res.status(400).send({ error: "Invalid webhook" });
}
});
`$3
`typescript
// Always use sandbox for development
const saligpay = new SaligPay({
clientId: process.env.SALIGPAY_CLIENT_ID,
clientSecret: process.env.SALIGPAY_CLIENT_SECRET,
env: process.env.NODE_ENV === "production" ? "production" : "sandbox",
});
`$3
`typescript
import rateLimit from "express-rate-limit";const webhookLimiter = rateLimit({
windowMs: 15 60 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
});
app.post("/webhooks/saligpay", webhookLimiter, async (req, res) => {
await saligpay.webhooks.listen(req, res, handler);
});
`$3
`typescript
// Use HTTPS in production
// Add authentication headers to webhooks (if needed)
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;app.post("/webhooks/saligpay", async (req, res) => {
// Verify webhook secret (future enhancement)
const signature = req.headers["x-webhook-signature"];
if (signature !== WEBHOOK_SECRET) {
return res.status(401).send({ error: "Unauthorized" });
}
// Process webhook
await saligpay.webhooks.listen(req, res, handler);
});
`API Reference
$3
| Method | Returns | Description |
| ----------------------- | ----------------------------- | ---------------------------------------- |
|
authenticate() | Promise | Authenticate and store tokens |
| isAuthenticated() | boolean | Check if currently authenticated |
| getAccessToken() | string \| undefined | Get current access token |
| ensureAuthenticated() | Promise | Ensure authentication, refresh if needed |$3
| Method | Returns | Description |
| --------------------------------------------------------- | ----------------------------- | ----------------------- |
|
authenticate(clientId?, clientSecret?) | Promise | Get access tokens |
| refreshToken(refreshToken) | Promise | Refresh access token |
| loginAndRetrieveCredentials(email, password, adminKey?) | Promise | Full login flow |
| validateToken(accessToken) | Promise | Validate token validity |$3
| Method | Returns | Description |
| ------------------------------- | ------------------------------------ | ------------------------ |
|
create(options, accessToken?) | Promise | Create checkout session |
| setAccessToken(token) | void | Set default access token |$3
| Method | Returns | Description |
| --------------------------- | ------------------------ | -------------------------- |
|
constructEvent(payload) | SaligPayWebhookPayload | Parse webhook body |
| listen(req, res, handler) | Promise | Express middleware handler |
| process(payload, handler) | PromiseMIT © SaligPay
- 📧 Email: support@saligpay.com
- 📚 Documentation: https://docs.saligpay.com
- 🐛 Report Issues: https://github.com/saligpay/node-sdk/issues
- 💬 Discord: https://discord.gg/saligpay
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.
---
Built with ❤️ by SaligPay