One interface for payment providers.
@payments-sdk/payments is an ESM-only TypeScript monorepo for composing independently
published payment provider packages behind one typed runtime. The runtime does
not import or register providers: applications choose the providers they need.
Install the project skills for coding agents:
npx skills add neplextech/payments-sdkThe skills and agent-oriented documentation are indexed at https://payments.neplex.dev/llms.txt and https://payments.neplex.dev/llms-full.txt.
pnpm add @payments-sdk/payments @payments-sdk/fonepayimport { fonepay } from '@payments-sdk/fonepay';
import { createPayments } from '@payments-sdk/payments';
export const payments = createPayments({
providers: [
fonepay({
merchantCode: process.env.FONEPAY_MERCHANT_CODE!,
secretKey: process.env.FONEPAY_SECRET_KEY!,
fonepayBaseUrl: process.env.FONEPAY_BASE_URL!,
}),
],
});
const checkout = await payments.checkout({
provider: 'fonepay',
amount: 1500_00,
currency: 'NPR',
reference: 'order_123',
providerOptions: {
returnUrl: 'https://merchant.example/payments/fonepay/return',
remarks1: 'Payment for order 123',
},
});
// checkout.action is a provider-defined redirect, form, or QR action.Amounts are integer minor units. For example, 1500_00 represents NPR
1,500.00. The core currency type is intentionally extensible; providers may
apply their own currency and amount rules.
Providers implement capabilities such as checkout, verification, returns, and
webhooks. createPayments() derives the runtime API from the configured
providers, so unsupported capabilities do not appear in the inferred type.
Pass payment fields directly to payments.checkout() as shown above. The SDK
creates and validates a payment intent before invoking the selected provider.
Create an intent when the application needs to reuse it across operations:
const intent = payments.createIntent({
amount: 1500_00,
currency: 'NPR',
reference: 'order_123',
metadata: { orderId: 'order_123' },
});
const checkout = await payments.checkout({
provider: 'fonepay',
intent,
providerOptions: {
returnUrl: 'https://merchant.example/payments/fonepay/return',
remarks1: 'Payment for order 123',
},
});The SDK is stateless: persistence, order handling, idempotency, and fulfillment remain application responsibilities.
FonePay hosted checkout verifies its browser return through the signed returns namespace:
export async function handleFonepayReturn(request: Request) {
const returned = await payments.returns.fonepay(request);
// Reconcile returned.reference and providerData.UID before fulfillment.
return returned;
}
const qrCheckout = await payments.checkout({
provider: 'fonepay',
amount: 350_00,
currency: 'NPR',
reference: 'order_qr_123',
providerOptions: { mode: 'dynamic_qr', remarks1: 'Payment for order 123' },
});
const qrStatus = await payments.verify({
provider: 'fonepay',
reference: 'order_qr_123',
providerOptions: { amount: 350_00 },
});Provider responses are normalized to statuses such as pending, succeeded,
failed, cancelled, refunded, and unknown; gateway-specific data remains
in providerData.
Browser return handling is separate from server-to-server webhooks. HTTP route registration and raw request handling belong to the application:
For an instance configured with @payments-sdk/esewa:
export async function handleEsewaReturn(request: Request) {
const returned = await payments.returns.esewa(request);
// Validate or persist the return, then call payments.verify(...).
return returned;
}The initial eSewa, FonePay, and Khalti packages implement returns. The initial
provider set does not implement webhooks, refunds, or cancellations. A future
provider can add those capabilities without changes to @payments-sdk/payments;
its configured capabilities will be reflected in the corresponding inferred
namespace.
packages/core @payments-sdk/core provider protocol and shared types
packages/sdk @payments-sdk/payments configured runtime and orchestration
packages/esewa @payments-sdk/esewa eSewa ePay v2 provider
packages/fonepay @payments-sdk/fonepay FonePay hosted and Dynamic QR provider
packages/khalti @payments-sdk/khalti Khalti KPG-2 providerThe core package is a development contract for provider authors. Provider packages use type-only core imports and are independently publishable. The runtime package has no provider registry or Nepal-specific provider knowledge.
| Package | Checkout | Verify | Returns |
|---|---|---|---|
@payments-sdk/esewa |
ePay v2 form | Transaction status | Signed browser return |
@payments-sdk/fonepay |
Signed hosted redirect / Dynamic QR | PRN status lookup | Signed browser return |
@payments-sdk/khalti |
KPG-2 redirect | Payment lookup | Browser return |
Provider credentials, sandbox settings, and gateway-specific options are documented in each provider package README.
This repository uses Node.js 22 or newer and pnpm:
pnpm install
pnpm run format:check
pnpm run lint
pnpm run typecheck
pnpm run test
pnpm run buildThe documentation app lives in apps/docs. Cross-package examples and tests
live in apps/integration-tests.
Keep provider-specific behavior inside the provider package. Keep orchestration
and runtime validation inside @payments-sdk/payments, and keep shared contracts
provider-neutral inside @payments-sdk/core. Add or update type tests and
runtime tests for behavior that crosses a package boundary.
Do not commit credentials or real gateway secrets. Provider implementations
should use the runtime fetch and now context supplied by the SDK rather
than owning global infrastructure.
The five publishable packages are independently versioned. GitHub Actions runs the release workflow only for a created GitHub release or an explicit manual dispatch. It checks the exact npm version of each package, skips versions that already exist, and publishes new versions with npm Trusted Publishing.
Maintainer setup is documented in .github/RELEASING.md.
MIT. See LICENSE.