@zed-io/wam-payment-sdk
Version:
Official WAM Payment SDK for creating and signing payment links
230 lines (222 loc) • 8.18 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var crypto = require('crypto');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var crypto__namespace = /*#__PURE__*/_interopNamespaceDefault(crypto);
class WamPaymentError extends Error {
constructor(message, code, validationErrors) {
super(message);
this.code = code;
this.validationErrors = validationErrors;
this.name = 'WamPaymentError';
}
}
exports.WamPaymentErrorCode = void 0;
(function (WamPaymentErrorCode) {
WamPaymentErrorCode["INVALID_CONFIG"] = "INVALID_CONFIG";
WamPaymentErrorCode["INVALID_AMOUNT"] = "INVALID_AMOUNT";
WamPaymentErrorCode["INVALID_CURRENCY"] = "INVALID_CURRENCY";
WamPaymentErrorCode["INVALID_REFERENCE"] = "INVALID_REFERENCE";
WamPaymentErrorCode["INVALID_RETURN_URL"] = "INVALID_RETURN_URL";
WamPaymentErrorCode["SIGNING_ERROR"] = "SIGNING_ERROR";
WamPaymentErrorCode["VALIDATION_ERROR"] = "VALIDATION_ERROR";
})(exports.WamPaymentErrorCode || (exports.WamPaymentErrorCode = {}));
class WamPaymentSDK {
constructor(config) {
this.validateConfig(config);
this.config = config;
this.baseUrl = this.getBaseUrl();
}
/**
* Generates a signed payment link for WAM payment processing
* @param params Payment parameters including amount, currency, reference, and optional return URL
* @returns Payment link response with URL, signature, and metadata
*/
generatePaymentLink(params) {
// Validate input parameters
this.validatePaymentParams(params);
// Generate timestamp
const timestamp = Date.now();
// Create the payload to sign (must match the format expected by WAM)
const payload = `${params.amount}|${params.currency}|${params.reference}|${timestamp}`;
// Generate HMAC-SHA256 signature
const signature = this.generateSignature(payload);
// Build the payment URL
const paymentUrl = this.buildPaymentUrl(params, timestamp, signature);
return {
url: paymentUrl,
signature,
timestamp,
payload
};
}
/**
* Validates payment parameters
* @param params Payment parameters to validate
* @throws WamPaymentError if validation fails
*/
validatePaymentParams(params) {
const errors = [];
// Validate amount
if (typeof params.amount !== 'number' || params.amount <= 0) {
errors.push({
field: 'amount',
message: 'Amount must be a positive number'
});
}
// Validate currency
if (!params.currency || (params.currency !== 'TTD' && params.currency !== 'USD')) {
errors.push({
field: 'currency',
message: 'Currency must be either "TTD" or "USD"'
});
}
// Validate reference
if (!params.reference || typeof params.reference !== 'string' || params.reference.trim().length === 0) {
errors.push({
field: 'reference',
message: 'Reference must be a non-empty string'
});
}
// Validate return URL if provided
if (params.returnUrl && typeof params.returnUrl !== 'string') {
errors.push({
field: 'returnUrl',
message: 'Return URL must be a string'
});
}
if (params.returnUrl && !this.isValidUrl(params.returnUrl)) {
errors.push({
field: 'returnUrl',
message: 'Return URL must be a valid URL'
});
}
if (errors.length > 0) {
throw new WamPaymentError('Validation failed', exports.WamPaymentErrorCode.VALIDATION_ERROR, errors);
}
}
/**
* Validates the SDK configuration
* @param config Configuration to validate
* @throws WamPaymentError if configuration is invalid
*/
validateConfig(config) {
const errors = [];
if (!config.businessId || typeof config.businessId !== 'string') {
errors.push({
field: 'businessId',
message: 'Business ID is required and must be a string'
});
}
if (!config.privateKey || typeof config.privateKey !== 'string') {
errors.push({
field: 'privateKey',
message: 'Private key is required and must be a string'
});
}
if (errors.length > 0) {
throw new WamPaymentError('Invalid configuration', exports.WamPaymentErrorCode.INVALID_CONFIG, errors);
}
}
/**
* Generates HMAC-SHA256 signature
* @param payload The payload to sign
* @returns Hex-encoded signature
*/
generateSignature(payload) {
try {
return crypto__namespace
.createHmac('sha256', this.config.privateKey)
.update(payload)
.digest('hex');
}
catch (error) {
throw new WamPaymentError('Failed to generate signature', exports.WamPaymentErrorCode.SIGNING_ERROR);
}
}
/**
* Gets the base URL from environment variables or defaults to production
* @returns Base URL for WAM payment gateway
*/
getBaseUrl() {
// Check for environment variable first
if (process.env.WAM_PAY_URL) {
return process.env.WAM_PAY_URL;
}
// Check for common environment indicators
const nodeEnv = process.env.NODE_ENV?.toLowerCase();
const wamEnv = process.env.WAM_ENVIRONMENT?.toLowerCase();
if (nodeEnv === 'development' || wamEnv === 'development') {
return 'https://wam-payments-v2-git-development-zed-io.vercel.app';
}
if (nodeEnv === 'staging' || wamEnv === 'staging') {
return 'https://pay-staging.wam.money';
}
// Default to production
return 'https://pay.wam.money';
}
/**
* Builds the complete payment URL with all parameters
* @param params Payment parameters
* @param timestamp Generated timestamp
* @param signature Generated signature
* @returns Complete payment URL
*/
buildPaymentUrl(params, timestamp, signature) {
const paymentUrl = new URL(`${this.baseUrl}/pay/external/generic/${this.config.businessId}`);
// Add required parameters
paymentUrl.searchParams.set('amount', params.amount.toString());
paymentUrl.searchParams.set('currency', params.currency);
paymentUrl.searchParams.set('reference', params.reference);
paymentUrl.searchParams.set('timestamp', timestamp.toString());
paymentUrl.searchParams.set('signature', signature);
// Add optional return URL
if (params.returnUrl) {
paymentUrl.searchParams.set('returnUrl', params.returnUrl);
}
return paymentUrl.toString();
}
/**
* Validates if a string is a valid URL
* @param url String to validate
* @returns true if valid URL, false otherwise
*/
isValidUrl(url) {
try {
new URL(url);
return true;
}
catch {
return false;
}
}
/**
* Gets the current configuration (without sensitive data)
* @returns Configuration object without private key
*/
getConfig() {
return {
businessId: this.config.businessId,
baseUrl: this.baseUrl
};
}
}
exports.WamPaymentError = WamPaymentError;
exports.WamPaymentSDK = WamPaymentSDK;
exports.default = WamPaymentSDK;
//# sourceMappingURL=index.js.map