npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@rusoft/paycenter

v1.0.8

Published

Express router and helpers for Paycenter proxy and payment confirmation.

Readme

@rusoft/paycenter-proxy

Express router + helpers for Paycenter proxying and payment confirmation.

## Requirements

  • Node.js >= 18 (uses built-in fetch and crypto.randomUUID)

Install

npm i @rusoft/paycenter-proxy

## .env Requirements

# Required
PAYCENTER_TOKEN=your_authtoken_here
HMAC_SECRET=your_hmac_secret_here
PAYCENTER_CLIENTID=00000000
PAYCENTER_URL=https://paycorp-xxx.prod.aws.paycorp.lk/rest/service/proxy


## paycenter and Return API setup

# api/paycenter/route.ts

import { NextRequest } from "next/server";
import { proxyToPaycenter } from "@rusoft/paycenter";

export const runtime = "nodejs";

const mustGet = (k: string) => {
  const v = process.env[k];
  if (!v) throw new Error(`Missing env ${k}`);
  return v;
};

export async function POST(req: NextRequest) {
  try {
    const raw = await req.text(); // <-- raw JSON string

    const { status, body } = await proxyToPaycenter(raw, {
      paycenterUrl: mustGet("PAYCENTER_URL"),
      token: mustGet("PAYCENTER_TOKEN"),
      hmacSecret: mustGet("HMAC_SECRET"),
    });

    return new Response(body, {
      status,
      headers: { "content-type": "application/json" },
    });
  } catch (err: any) {
    console.error("Proxy error:", err);
    return new Response(
      JSON.stringify({ error: "Proxy error", message: err?.message }),
      { status: 500, headers: { "content-type": "application/json" } }
    );
  }
}

# api/paycenter/return/route.ts

import { NextRequest, NextResponse } from "next/server";
import { confirmPayment } from "@rusoft/paycenter";

export const runtime = "nodejs";

const must = (k: string) => {
  const v = process.env[k];
  if (!v) throw new Error(`Missing env ${k}`);
  return v;
};

export async function GET(req: NextRequest) {
  const url = new URL(req.url);
  const bridge = url.searchParams.get("bridge") === "1";
  const reqid = url.searchParams.get("reqid") || "";
  const clientRef = url.searchParams.get("clientRef") || "";

  const { status, body } = await confirmPayment(reqid, {
    paycenterUrl: must("PAYCENTER_URL"),
    clientId: must("PAYCENTER_CLIENTID"),
    token: must("PAYCENTER_TOKEN"),
    hmacSecret: must("HMAC_SECRET"),
  });

  if (!bridge) {
    return new Response(body, {
      status,
      headers: { "content-type": "application/json" },
    });
  }

  const jsonStr = typeof body === "string" ? body : JSON.stringify(body);
  const html = `<!doctype html>
<html><head><meta charset="utf-8"><title>Return</title></head>
<body>
<script>
  (function(){
    try{
      var payload = ${JSON.stringify(jsonStr)};
      var data; try{ data = JSON.parse(payload); }catch(e){ data = { raw: payload }; }
      window.parent.postMessage(
        { source: "paycenter", status: ${status}, clientRef: ${JSON.stringify(clientRef)}, reqid: ${JSON.stringify(reqid)}, data },
        location.origin
      );
    }catch(e){}
  })();
</script>
</body></html>`;
  return new Response(html, { status: 200, headers: { "content-type": "text/html; charset=utf-8" } });
}

## Page Implement

'Use Client'

# type paycenterMassage

type PaycenterMessage = {
  source: 'paycenter';
  status: number;
  clientRef: string;
  reqid: string;
  data: any;
};


# States

const [iframeUrl, setIframeUrl] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [paymentResult, setPaymentResult] = useState<PaycenterMessage | null>(null);
const iframeRef = useRef<HTMLIFrameElement>(null);

# Setup Bridge return

const bridgeReturnUrl = useMemo(() => {
    if (typeof window === 'undefined') return '';
    return `${window.location.origin}/api/paycenter/return?bridge=1`;
  }, []);

# Listen for return bridge → save in state

useEffect(() => {
    function onMessage(event: MessageEvent) {
      if (typeof window === 'undefined') return;
      if (event.origin !== window.location.origin) return;
      if (event.data?.source !== 'paycenter') return;
      setPaymentResult(event.data as PaycenterMessage); // <-- save first
      setIframeUrl(null); // close overlay
    }
    window.addEventListener('message', onMessage);
    return () => window.removeEventListener('message', onMessage);
  }, []);

  // After state updated, alert it
  useEffect(() => {
    if (!paymentResult) return;
    alert(
      `Payment result:
status=${paymentResult.status}
clientRef=${paymentResult.clientRef}
reqid=${paymentResult.reqid}
payload=${JSON.stringify(paymentResult.data, null, 2)}`
    );
  }, [paymentResult]);

# Send Init Req with payload

async function sendInitRequest() {
    setLoading(true);
    try {
      const payload = {
        version: '1.5',
        msgId: crypto.randomUUID(),
        operation: 'PAYMENT_INIT',
        requestDate: new Date().toISOString(),
        validateOnly: false,
        requestData: {
          clientId: 'xxxxxxxx',
          transactionType: 'PURCHASE',
          transactionAmount: {
            totalAmount: 0,
            paymentAmount: 200,
            serviceFeeAmount: 0,
            currency: 'LKR',
          },
          redirect: {
            returnUrl: bridgeReturnUrl,   // bridge posts JSON to parent
            cancelUrl: bridgeReturnUrl,   // optional
            returnMethod: 'GET',
          },
          clientRef: `ORDER-${crypto.randomUUID()}`,
          comment: `Test ${new Date().toISOString()}`,
          tokenize: false,
          useReliability: true,
          extraData: { st_id: '123456', batch_id: '102348748', group: '1231458' },
        },
      };

      const res = await fetch('/api/paycenter', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify(payload),
      });

      const text = await res.text();
      let data: any;
      try { data = JSON.parse(text); } catch { data = { raw: text }; }

      const url =
        data?.responseData?.paymentPageUrl ||
        data?.paymentPageUrl ||
        data?.redirectUrl;

      if (!url) {
        alert('No payment page URL received.');
        return;
      }
      setIframeUrl(url);
    } catch (e: any) {
      console.error(e);
      alert(`Init failed: ${e?.message || 'Unknown error'}`);
    } finally {
      setLoading(false);
    }
  }

## retun UI Sample

<main style={{ minHeight: '100vh', padding: 20, display: 'flex', flexDirection: 'column', gap: 16, background: '#f9f9f9', color: '#333', fontFamily: 'Arial, sans-serif' }}>
      <h1>Paycenter API Test (Save then Alert)</h1>

      <button onClick={sendInitRequest} disabled={loading} style={{ alignSelf: 'flex-start', padding: '10px 20px', fontSize: 16 }}>
        {loading ? 'Sending…' : 'Send Init Request'}
      </button>

      {paymentResult && (
        <pre style={{ background: '#fff', padding: 12, borderRadius: 8, maxWidth: 900, overflow: 'auto' }}>
          {JSON.stringify(paymentResult, null, 2)}
        </pre>
      )}

      {iframeUrl && (
        <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.6)', display: 'flex', justifyContent: 'center', alignItems: 'center', zIndex: 9999 }}>
          <div style={{ position: 'relative', width: '90%', height: '90%' }}>
            <button onClick={() => setIframeUrl(null)} style={{ position: 'absolute', top: 10, right: 10, background: '#fff', border: 'none', fontSize: 24, borderRadius: '50%', padding: '4px 10px', zIndex: 10000 }}>
              &times;
            </button>
            <iframe ref={iframeRef} src={iframeUrl} style={{ width: '100%', height: '100%', border: 'none', background: '#fff' }} />
          </div>
        </div>
      )}
    </main>


## Authors

- [@rusoft](https://www.github.com/ruma-lk)


## Used By

This project is for Paycorp Iframe:

-bancstac


## Tech Advice

Above content for NextJS Example use you can use as Normal in Express:

// server.js
import 'dotenv/config';
import express from 'express';
import cors from 'cors';


// If using as a dependency in another app, use:
import { createPaycenterRouter } from '@rusoft/paycenter';

const app = express();

// CORS (allow your frontend origin; comma-separate multiple origins in ALLOWED_ORIGINS)
const allowList = (process.env.ALLOWED_ORIGINS || '').split(',').map(s => s.trim()).filter(Boolean);
app.use(cors({
  origin: allowList.length ? allowList : true,
  credentials: true,
}));

// (Optional) health check
app.get('/', (_req, res) => res.json({ ok: true }));

// Mount Paycenter router
app.use(
  createPaycenterRouter({
    cors: {
      origin: allowList.length ? allowList : true,
      credentials: true,
    },
    basePath: process.env.BASE_PATH || '/api', // gives /api/paycenter and /api/paycenter/return
  })
);

const port = Number(process.env.PORT || 3001);
app.listen(port, () => {
  console.log(`➜ Paycenter proxy up:  http://localhost:${port}`);
  console.log(`   POST   ${process.env.BASE_PATH || '/api'}/paycenter`);
  console.log(`   GET    ${process.env.BASE_PATH || '/api'}/paycenter/return`);
  console.log(`   GET    /confirm?reqid=...`);
});