payplus-ipg
v1.0.6
Published
PayPlus Payment Gateway SDK for JavaScript/TypeScript — works with React, Next.js, Vue, Angular, and plain JS/Node
Maintainers
Readme
payplus-ipg
Official PayPlus Internet Payment Gateway (IPG) SDK for JavaScript & TypeScript
Integrate PayPlus payments into any JavaScript project with a single async function call. Zero dependencies, fully typed, works everywhere.
✅ Framework Support
| Framework | Support | |---|---| | ⚛️ React / Vite | ✅ | | ▲ Next.js (App Router, Pages Router, Edge Runtime) | ✅ | | 🟢 Vue 3 / Nuxt | ✅ | | 🔴 Angular | ✅ | | 🟠 Svelte / SvelteKit | ✅ | | 🟦 Plain JavaScript / TypeScript | ✅ | | 🖥️ Node.js ≥ 16 | ✅ |
How It Works
- Your server calls
createPaymentSession()with the order and customer details - PayPlus returns a hosted payment URL
- You redirect the customer to that URL
- The customer completes payment on the secure PayPlus page
- PayPlus notifies your server via webhook (
notifyUrl) and redirects the customer back to your site (redirectUrl)
Your Server ──► createPaymentSession() ──► PayPlus API
│
paymentUrl returned
│
Customer ◄─── redirect to paymentUrl ◄─────────┘
│
└──► Pays on PayPlus hosted page
│
├──► POST webhook ──► your notifyUrl (server-to-server)
└──► redirect ──► your redirectUrl (customer browser)Installation
npm install payplus-ipg
# or
yarn add payplus-ipg
# or
pnpm add payplus-ipgQuick Start
import { createPaymentSession } from 'payplus-ipg';
const result = await createPaymentSession({
merchantId: 'YOUR_MERCHANT_ID',
applicationKey: 'YOUR_APPLICATION_KEY',
secret: 'YOUR_SECRET',
amount: 1000,
currency: 'LKR',
orderId: 'ORD-00123',
redirectUrl: 'https://myshop.com/thank-you',
notifyUrl: 'https://myshop.com/api/payment-notify',
description: 'Order #ORD-00123',
customer: {
firstname: 'John',
lastname: 'Anderson',
email: '[email protected]',
phoneNumber: '771234567', // without dial code
dialCode: '+94', // optional, defaults to '+94'
},
sandbox: false, // true = sandbox (test payments) | false = live (real payments)
});
if (result.success && result.paymentUrl) {
// Redirect the customer to the PayPlus hosted payment page
window.location.href = result.paymentUrl;
} else {
console.error('Payment session error:', result.error);
}Integration Guide
Step 1 — Get Your Credentials
Log in to the PayPlus Merchant Dashboard and obtain:
- Merchant ID
- Application Key
- Secret (used for HMAC-SHA256 request signing — keep this private)
Store them in environment variables — never hardcode them in your source code.
PAYPLUS_MERCHANT_ID=your_merchant_id
PAYPLUS_APP_KEY=your_application_key
PAYPLUS_SECRET=your_secretStep 2 — Create a Payment Session (Server-Side)
⚠️ Always call
createPaymentSessionfrom your server (Next.js Server Action, API route, Express endpoint, etc.) so yoursecretis never exposed in the browser.
// Example: Next.js API route (pages/api/pay.ts)
import type { NextApiRequest, NextApiResponse } from 'next';
import { createPaymentSession } from 'payplus-ipg';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') return res.status(405).end();
const { orderId, amount, customer } = req.body;
const result = await createPaymentSession({
merchantId: process.env.PAYPLUS_MERCHANT_ID!,
applicationKey: process.env.PAYPLUS_APP_KEY!,
secret: process.env.PAYPLUS_SECRET!,
amount,
currency: 'LKR',
orderId,
redirectUrl: `${process.env.NEXT_PUBLIC_APP_URL}/thank-you?order=${orderId}`,
notifyUrl: `${process.env.NEXT_PUBLIC_APP_URL}/api/payment-notify`,
description: `Order ${orderId}`,
customer,
sandbox: false, // switch to true for test payments
});
if (result.success) {
res.status(200).json({ paymentUrl: result.paymentUrl });
} else {
res.status(500).json({ error: result.error });
}
}Step 3 — Redirect the Customer
Once you receive the paymentUrl, redirect the customer to it:
// Client-side
const res = await fetch('/api/pay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orderId, amount, customer }),
});
const { paymentUrl } = await res.json();
if (paymentUrl) {
window.location.href = paymentUrl;
}Step 4 — Handle the Webhook (notifyUrl)
PayPlus will POST a notification to your notifyUrl once payment is complete. Use this to fulfill the order in your database.
// Example: pages/api/payment-notify.ts
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') return res.status(405).end();
const notification = req.body;
// TODO: Validate the notification signature from PayPlus
// TODO: Check notification.status === 'SUCCESS' before fulfilling
if (notification.status === 'SUCCESS') {
const orderId = notification.orderId;
// Mark order as paid in your database
await markOrderPaid(orderId);
}
// Always respond 200 so PayPlus knows you received it
res.status(200).json({ received: true });
}Step 5 — Handle the Redirect (redirectUrl)
After payment, PayPlus redirects the customer back to your redirectUrl. Show them a confirmation page:
// Example: pages/thank-you.tsx
import { useRouter } from 'next/router';
export default function ThankYouPage() {
const { query } = useRouter();
return (
<div>
<h1>Thank you for your order!</h1>
<p>Order ID: {query.order}</p>
</div>
);
}Testing with Sandbox
Set sandbox: true to use the PayPlus sandbox gateway. Sandbox payments are test transactions — no real money is charged.
const result = await createPaymentSession({
// ... your options
sandbox: true, // test payments — no real money charged
});Switch to sandbox: false when you go live.
API Reference
createPaymentSession(options)
Returns Promise<PaymentSessionResult>
Options
| Parameter | Type | Required | Default | Description |
|------------------|-----------|----------|--------------|-------------|
| merchantId | string | ✅ | — | Your PayPlus Merchant ID |
| applicationKey | string | ✅ | — | Your PayPlus Application Key |
| secret | string | ✅ | — | Your secret key — used for HMAC-SHA256 signing. Never expose in the browser. |
| amount | number | ✅ | — | Payment amount (e.g. 1000 for LKR 1,000) |
| currency | string | ✅ | — | ISO 4217 currency code — e.g. 'LKR', 'USD' |
| orderId | string | ✅ | — | Unique order ID from your system |
| redirectUrl | string | ✅ | — | URL to redirect the customer after payment |
| notifyUrl | string | ✅ | — | Server webhook URL — PayPlus POSTs payment result here |
| customer | object | ✅ | — | Customer details — see CustomerInfo below |
| domain | string | — | '' | Your store domain |
| description | string | — | '' | Human-readable order description |
| paymentType | string | — | 'ONE_TIME' | 'ONE_TIME' or 'RECURRING' |
| source | string | — | 'CUSTOM' | Integration source identifier |
| pluginVersion | string | — | '1.0.0' | Your integration version string |
| sandbox | boolean | — | false | true = sandbox gateway (test payments), false = live gateway (real payments) |
CustomerInfo
| Parameter | Type | Required | Default | Description |
|---------------|----------|----------|---------|-------------|
| firstname | string | ✅ | — | Customer first name |
| lastname | string | ✅ | — | Customer last name |
| email | string | ✅ | — | Customer email address |
| phoneNumber | string | ✅ | — | Phone number without dial code |
| dialCode | string | — | '+94' | International dial code |
PaymentSessionResult
{
success: boolean; // true if the session was created successfully
paymentUrl?: string; // redirect the customer to this URL
sessionId?: string; // PayPlus session ID
statusCode?: number; // HTTP status code from the gateway
raw?: unknown; // full raw response body from the gateway
error?: string; // error message (present when success is false)
}Framework Examples
⚛️ React
import { useState } from 'react';
import { createPaymentSession } from 'payplus-ipg';
function CheckoutButton({ order }) {
const [loading, setLoading] = useState(false);
const handlePay = async () => {
setLoading(true);
// ⚠️ Call your backend API — do NOT call createPaymentSession directly
// from the browser with your real secret
const res = await fetch('/api/pay', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orderId: order.id, amount: order.total, customer: order.customer }),
});
const { paymentUrl, error } = await res.json();
if (paymentUrl) {
window.location.href = paymentUrl;
} else {
alert(`Payment error: ${error}`);
setLoading(false);
}
};
return (
<button onClick={handlePay} disabled={loading}>
{loading ? 'Redirecting...' : 'Pay Now'}
</button>
);
}▲ Next.js — App Router (Server Action)
// app/actions/payment.ts
'use server';
import { createPaymentSession } from 'payplus-ipg';
export async function initiatePayment(orderId: string, amount: number, customer: object) {
return createPaymentSession({
merchantId: process.env.PAYPLUS_MERCHANT_ID!,
applicationKey: process.env.PAYPLUS_APP_KEY!,
secret: process.env.PAYPLUS_SECRET!,
amount,
currency: 'LKR',
orderId,
redirectUrl: `${process.env.NEXT_PUBLIC_APP_URL}/thank-you`,
notifyUrl: `${process.env.NEXT_PUBLIC_APP_URL}/api/payment-notify`,
customer,
sandbox: false,
});
}// app/checkout/page.tsx
'use client';
import { initiatePayment } from '../actions/payment';
export default function CheckoutPage() {
const handlePay = async () => {
const result = await initiatePayment('ORD-001', 1500, {
firstname: 'John', lastname: 'Anderson',
email: '[email protected]', phoneNumber: '771234567',
});
if (result.success && result.paymentUrl) {
window.location.href = result.paymentUrl;
}
};
return <button onClick={handlePay}>Pay Now</button>;
}▲ Next.js — Pages Router (API Route)
// pages/api/create-payment.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { createPaymentSession } from 'payplus-ipg';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') return res.status(405).end();
const result = await createPaymentSession({
merchantId: process.env.PAYPLUS_MERCHANT_ID!,
applicationKey: process.env.PAYPLUS_APP_KEY!,
secret: process.env.PAYPLUS_SECRET!,
...req.body,
});
res.status(result.success ? 200 : 500).json(result);
}🟢 Vue 3 / Nuxt
<script setup lang="ts">
import { ref } from 'vue';
import { createPaymentSession } from 'payplus-ipg';
const loading = ref(false);
// ⚠️ In production use a Nuxt server route — not direct client-side calls with your secret
async function pay() {
loading.value = true;
const result = await createPaymentSession({
merchantId: import.meta.env.VITE_PAYPLUS_MERCHANT_ID,
applicationKey: import.meta.env.VITE_PAYPLUS_APP_KEY,
secret: import.meta.env.VITE_PAYPLUS_SECRET,
amount: 2500,
currency: 'LKR',
orderId: 'ORD-456',
redirectUrl: `${window.location.origin}/thank-you`,
notifyUrl: 'https://myshop.com/api/payment-notify',
customer: {
firstname: 'Nimal', lastname: 'Silva',
email: '[email protected]', phoneNumber: '779876543',
},
sandbox: false,
});
if (result.success && result.paymentUrl) {
window.location.href = result.paymentUrl;
} else {
alert(result.error);
loading.value = false;
}
}
</script>
<template>
<button @click="pay" :disabled="loading">
{{ loading ? 'Redirecting…' : 'Pay Now' }}
</button>
</template>🔴 Angular
// payment.service.ts
import { Injectable } from '@angular/core';
import { createPaymentSession, CreatePaymentSessionOptions } from 'payplus-ipg';
import { environment } from '../environments/environment';
@Injectable({ providedIn: 'root' })
export class PaymentService {
initiatePayment(options: Omit<CreatePaymentSessionOptions, 'merchantId' | 'applicationKey' | 'secret'>) {
return createPaymentSession({
...options,
merchantId: environment.payplusMerchantId,
applicationKey: environment.payplusAppKey,
secret: environment.payplusSecret,
});
}
}// checkout.component.ts
import { Component } from '@angular/core';
import { PaymentService } from './payment.service';
@Component({
selector: 'app-checkout',
template: `<button (click)="pay()" [disabled]="loading">
{{ loading ? 'Redirecting...' : 'Pay Now' }}
</button>`
})
export class CheckoutComponent {
loading = false;
constructor(private paymentService: PaymentService) {}
async pay() {
this.loading = true;
const result = await this.paymentService.initiatePayment({
amount: 1000,
currency: 'LKR',
orderId: 'ORD-789',
redirectUrl: 'https://myshop.com/thank-you',
notifyUrl: 'https://myshop.com/api/payment-notify',
customer: {
firstname: 'Sunil', lastname: 'Fernando',
email: '[email protected]', phoneNumber: '712345678',
},
sandbox: false,
});
if (result.success && result.paymentUrl) {
window.location.href = result.paymentUrl;
} else {
alert(result.error);
this.loading = false;
}
}
}🖥️ Node.js
import { createPaymentSession } from 'payplus-ipg';
const result = await createPaymentSession({
merchantId: process.env.PAYPLUS_MERCHANT_ID!,
applicationKey: process.env.PAYPLUS_APP_KEY!,
secret: process.env.PAYPLUS_SECRET!,
amount: 500,
currency: 'LKR',
orderId: 'ORD-001',
redirectUrl: 'https://myshop.com/thank-you',
notifyUrl: 'https://myshop.com/api/payment-notify',
customer: {
firstname: 'John', lastname: 'Doe',
email: '[email protected]', phoneNumber: '771234567',
},
sandbox: true, // switch to false for live payments
});
if (result.success) {
console.log('Payment URL:', result.paymentUrl);
} else {
console.error('Error:', result.error);
}Security Best Practices
- Never expose your
secretin client-side code. Always callcreatePaymentSessionfrom a server (API route, Server Action, Express endpoint). - Store all credentials in environment variables — never commit them to source control.
- Validate webhook payloads from PayPlus before fulfilling orders.
- Use HTTPS for both your
redirectUrlandnotifyUrlin production.
TypeScript Support
Full TypeScript support is built in — no @types package needed.
import {
createPaymentSession,
CreatePaymentSessionOptions,
CustomerInfo,
PaymentSessionResult,
} from 'payplus-ipg';Requirements
- Node.js ≥ 16 (uses native Web Crypto API or built-in
cryptomodule — no extra dependencies) - Browser — any modern browser (Chrome 37+, Firefox 34+, Safari 11+, Edge 79+)
License
MIT © PayPlus
