Server
Node.js SDK
Server-side Node.js implementation for integrating YardPay Pro. Uses native fetch with automatic retries and Express middleware — no npm packages required.
API Client
Create yardpaypro.mjs (or .js with "type": "module" in package.json)
// yardpaypro.mjs
export class YppError extends Error {
constructor(status, code, message) {
super(message);
this.name = 'YppError';
this.status = status;
this.code = code;
}
}
export class YardPayProClient {
#apiKey;
#baseUrl;
#timeout;
#retries;
#retryDelay;
constructor({ apiKey, baseUrl = process.env.API_BASE_URL || '', timeout = 30000, retries = 2, retryDelay = 1000 }) {
this.#apiKey = apiKey;
this.#baseUrl = baseUrl;
this.#timeout = timeout;
this.#retries = retries;
this.#retryDelay = retryDelay;
}
get baseUrl() { return this.#baseUrl; }
async #request(method, path, { body, params } = {}) {
let url = this.#baseUrl + path;
if (params) {
const qs = new URLSearchParams();
for (const [k, v] of Object.entries(params)) {
if (v !== undefined && v !== null) qs.append(k, String(v));
}
const str = qs.toString();
if (str) url += '?' + str;
}
let lastError;
for (let attempt = 0; attempt <= this.#retries; attempt++) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), this.#timeout);
try {
const res = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
'x-api-key': this.#apiKey,
},
body: body ? JSON.stringify(body) : undefined,
signal: controller.signal,
});
clearTimeout(timer);
if (res.status >= 500 && attempt < this.#retries) {
lastError = new YppError(res.status, 'server_error', res.statusText);
await new Promise(r => setTimeout(r, this.#retryDelay * (attempt + 1)));
continue;
}
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new YppError(res.status, err.code || 'api_error', err.message || res.statusText);
}
if (res.status === 204) return null;
const data = await res.json();
return data.value !== undefined ? data.value : data;
} catch (err) {
clearTimeout(timer);
if (err instanceof YppError) throw err;
if (err.name === 'AbortError') {
lastError = new YppError(0, 'timeout', 'Request timed out');
if (attempt < this.#retries) {
await new Promise(r => setTimeout(r, this.#retryDelay * (attempt + 1)));
continue;
}
}
throw lastError || err;
}
}
throw lastError;
}
get(path, params) { return this.#request('GET', path, { params }); }
post(path, body) { return this.#request('POST', path, { body }); }
put(path, body) { return this.#request('PUT', path, { body }); }
delete(path) { return this.#request('DELETE', path); }
}Services
Create yardpaypro-services.mjs
// yardpaypro-services.mjs
export class Payments {
#client;
constructor(client) { this.#client = client; }
payInvoice(data) {
return this.#client.post('/api/payment/payinvoice', data);
}
getPaymentMethods(invoiceId) {
return this.#client.get(`/v1/invoice/${invoiceId}/PaymentMethods`);
}
getFundingSources() {
return this.#client.get('/v1/FundingSource');
}
registerCard({ card, name }) {
return this.#client.post('/v1/payment/RegisterCard', { card, name });
}
verifyCard(fundingSourceId, code) {
return this.#client.post(`/v1/fundingSource/${fundingSourceId}/verify`, { VerificationCode: code });
}
}
export class Invoices {
#client;
constructor(client) { this.#client = client; }
getByReference(code) {
return this.#client.get(`/v1/invoice/getByReference(code='${code}')`);
}
getById(id, expand) {
return this.#client.get(`/v1/invoice/${id}`, expand ? { $expand: expand } : undefined);
}
list({ filter, orderBy, top = 20, skip = 0, expand } = {}) {
return this.#client.get('/v1/invoice', {
$filter: filter, $orderby: orderBy, $top: top, $skip: skip, $expand: expand,
});
}
create(data) {
return this.#client.post('/v1/invoice', data);
}
getQrCodeUrl(identifier) {
return `${this.#client.baseUrl}/v1/invoice/qrcode/${identifier}`;
}
}
export class Subscriptions {
#client;
constructor(client) { this.#client = client; }
// Products
listProducts(params) { return this.#client.get('/v1/SubscriptionProduct', params); }
createProduct(data) { return this.#client.post('/v1/SubscriptionProduct', data); }
updateProduct(id, data) { return this.#client.put(`/v1/SubscriptionProduct/${id}`, data); }
deleteProduct(id) { return this.#client.delete(`/v1/SubscriptionProduct/${id}`); }
// Plans
listPlans(params) { return this.#client.get('/v1/SubscriptionPlan', params); }
createPlan(data) { return this.#client.post('/v1/SubscriptionPlan', data); }
updatePlan(id, data) { return this.#client.put(`/v1/SubscriptionPlan/${id}`, data); }
deletePlan(id) { return this.#client.delete(`/v1/SubscriptionPlan/${id}`); }
// Inventory
listInventory(params) { return this.#client.get('/v1/SubscriptionInventoryItem', params); }
createInventoryItem(data) { return this.#client.post('/v1/SubscriptionInventoryItem', data); }
updateInventoryItem(id, data) { return this.#client.put(`/v1/SubscriptionInventoryItem/${id}`, data); }
deleteInventoryItem(id) { return this.#client.delete(`/v1/SubscriptionInventoryItem/${id}`); }
}Express.js Integration
// server.mjs
import express from 'express';
import { YardPayProClient, YppError } from './yardpaypro.mjs';
import { Payments, Invoices } from './yardpaypro-services.mjs';
const app = express();
app.use(express.json());
const client = new YardPayProClient({
apiKey: process.env.YARDPAYPRO_API_KEY,
});
const payments = new Payments(client);
const invoices = new Invoices(client);
// Create invoice endpoint
app.post('/api/invoices', async (req, res) => {
try {
const invoice = await invoices.create(req.body);
res.json(invoice);
} catch (err) {
if (err instanceof YppError) {
res.status(err.status || 500).json({ error: err.code, message: err.message });
} else {
res.status(500).json({ error: 'internal', message: err.message });
}
}
});
// Pay invoice endpoint
app.post('/api/payments', async (req, res) => {
try {
const result = await payments.payInvoice(req.body);
res.json(result);
} catch (err) {
if (err instanceof YppError) {
res.status(err.status || 500).json({ error: err.code, message: err.message });
} else {
res.status(500).json({ error: 'internal', message: err.message });
}
}
});
app.listen(3000, () => console.log('Server running on :3000'));Webhook Middleware
// webhook-middleware.mjs
import crypto from 'node:crypto';
/**
* Express middleware that verifies the webhook signature
* and attaches the parsed event to req.webhookEvent.
*/
export function webhookMiddleware({ secret }) {
return (req, res, next) => {
const signature = req.headers['x-ypp-signature'];
if (!signature) {
return res.status(401).json({ error: 'Missing signature' });
}
const payload = typeof req.body === 'string'
? req.body
: JSON.stringify(req.body);
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
return res.status(401).json({ error: 'Invalid signature' });
}
req.webhookEvent = typeof req.body === 'string'
? JSON.parse(req.body)
: req.body;
next();
};
}
// Usage:
app.post(
'/webhooks',
express.raw({ type: 'application/json' }),
webhookMiddleware({ secret: process.env.YARDPAYPRO_WEBHOOK_SECRET }),
(req, res) => {
const event = req.webhookEvent;
switch (event.type) {
case 'payment.completed':
console.log('Payment completed:', event.data.referenceNumber);
break;
case 'invoice.paid':
console.log('Invoice paid:', event.data.id);
break;
}
res.sendStatus(200);
}
);Full Example
import { YardPayProClient, YppError } from './yardpaypro.mjs';
import { Payments, Invoices, Subscriptions } from './yardpaypro-services.mjs';
const client = new YardPayProClient({
apiKey: process.env.YARDPAYPRO_API_KEY,
retries: 3, // auto-retry on 5xx and timeouts
retryDelay: 1000, // 1s, 2s, 3s backoff
});
const payments = new Payments(client);
const invoices = new Invoices(client);
const subs = new Subscriptions(client);
// --- Full payment flow ---
const invoice = await invoices.create({
description: 'Order #1234',
currencyCode: 'JMD',
items: [{ name: 'Widget', unitCost: 100, quantity: 2, invoiceItemTypeId: 'Product' }],
sourceInfo: { firstName: 'Test', lastName: 'User', email: '[email protected]' },
});
const methods = await payments.getPaymentMethods(invoice.id);
const cardMethod = methods.find(m => m.paymentMethodId === 'CreditCard');
try {
const result = await payments.payInvoice({
invoiceIdentifier: invoice.identifier,
paymentOptionId: cardMethod.id,
card: { name: 'Test User', cardNumber: '4111111111111111', expiryDate: '12/28', cvv: '123' },
});
console.log('Paid:', result.referenceNumber);
} catch (err) {
if (err instanceof YppError) {
console.error(`[${err.status}] ${err.code}: ${err.message}`);
}
}
// --- Subscription setup ---
const product = await subs.createProduct({
name: 'Business Suite',
subscriptionProductType: 'Service',
});
await subs.createPlan({
productId: product.id,
name: 'Monthly',
amount: '29.99',
currency: 'USD',
timeUnit: 'Month',
timeInterval: 1,
subscriptionBillingType: 'Recurring',
status: 'Active',
});Requirements
- Node.js 18+ (native
fetch) - No npm packages required — uses native
fetch,crypto, andAbortController - Express.js for the middleware examples (optional — services work standalone)
- ESM modules (
.mjsor"type": "module"in package.json)