voko-sdk
Version:
Process payments with ease
238 lines (237 loc) • 9.53 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AzampayAdapter = void 0;
const types_1 = require("../../core/types");
const base_adapter_1 = require("../base/base-adapter");
const azampay_config_1 = require("./azampay.config");
const errors_1 = require("../../core/errors");
const models_1 = require("../../core/models");
class AzampayTokenManager {
static instance;
tokens = new Map();
constructor() { }
static getInstance() {
if (!AzampayTokenManager.instance) {
AzampayTokenManager.instance = new AzampayTokenManager();
}
return AzampayTokenManager.instance;
}
async getToken(providerId, authenticator) {
const tokenData = this.tokens.get(providerId);
const now = new Date();
if (tokenData && now < tokenData.expiry) {
return tokenData.token;
}
const { token, expiry } = await authenticator();
this.tokens.set(providerId, {
token,
expiry: new Date(expiry),
});
return token;
}
}
class AzampayAdapter extends base_adapter_1.BaseAdapter {
provider = types_1.SupportedProvider.AZAMPAY;
appName;
clientId;
clientSecret;
static tokenManager = AzampayTokenManager.getInstance();
constructor(config) {
super(config);
this.appName = config.appName;
this.clientId = config.clientId;
this.clientSecret = config.clientSecret;
}
getBaseUrl(environment) {
return azampay_config_1.AZAMPAY_CONFIG[environment].baseUrl;
}
getPaymentEndpoint() {
return azampay_config_1.AZAMPAY_CONFIG[this.config.environment].endpoints.mno;
}
getStatusEndpoint() {
return azampay_config_1.AZAMPAY_CONFIG[this.config.environment].endpoints.status;
}
getContentHeaders() {
return {
'Content-Type': 'application/json',
};
}
getStatusHttpMethod() {
return 'POST';
}
async initiatePayment(request) {
try {
const token = await this.getAuthToken();
const transformedRequest = this.transformRequest(request);
const paymentMethod = request.paymentMethod === types_1.PaymentMethod.BANK_TRANSFER ? 'bank' : 'mno';
const endpoint = azampay_config_1.AZAMPAY_CONFIG[this.config.environment].endpoints[paymentMethod];
const response = await this.makeAuthenticatedApiCall(endpoint, transformedRequest, token);
const paymentResponse = this.transformResponse(response, request);
return paymentResponse;
}
catch (error) {
this.logger.error('Payment initiation failed', error);
throw this.handleError(error);
}
}
async getAuthToken() {
const providerId = `azampay_${this.config.environment}_${this.clientId}`;
return await AzampayAdapter.tokenManager.getToken(providerId, async () => {
const response = await this.authenticate();
if (response.data.success && response.data.statusCode === 200) {
return {
token: response.data.data.accessToken,
expiry: response.data.data.expire,
};
}
else {
throw new errors_1.VokoError('Azampay authentication failed', types_1.VokoErrorCode.AUTHENTICATION_FAILED, response.data.statusCode?.toString(), response.data.message);
}
});
}
async makeAuthenticatedApiCall(endpoint, data, token) {
const url = `${this.baseUrl}${endpoint}`;
const headers = {
...this.getContentHeaders(),
Authorization: `Bearer ${token}`,
};
const response = await this.httpClient.request({
url,
method: 'POST',
headers,
data,
});
return response.data;
}
transformRequest(request) {
if (request.paymentMethod === types_1.PaymentMethod.BANK_TRANSFER) {
const payload = {
additionalProperties: {
description: request.description || '',
payment_method: request.paymentMethod,
email: request.email,
},
amount: request.amount,
currencyCode: request.currency,
merchantAccountNumber: request.accountNumber || '',
merchantMobileNumber: request.phoneNumber || '',
merchantName: request.fullName || 'Unknown Customer',
otp: request.otp,
provider: request.operator,
referenceId: request.reference,
};
return payload;
}
else {
const payload = {
accountNumber: request.phoneNumber || '',
additionalProperties: {
description: request.description || '',
payment_method: request.paymentMethod || types_1.PaymentMethod.MOBILE_MONEY,
email: request.email,
fullName: request.fullName,
referenceId: request.reference,
},
amount: request.amount,
currency: request.currency,
externalId: request.reference,
provider: request.operator,
};
return payload;
}
}
transformResponse(providerResponse, originalRequest) {
const status = this.mapAzampayStatus(providerResponse.success ? 'success' : 'failed');
const operator = this.mapOperator(originalRequest.operator);
const response = new models_1.VokoPaymentResponse({
success: providerResponse.success,
transactionId: this.generateTransactionId(),
providerTransactionId: providerResponse.transactionId,
status,
amount: originalRequest.amount,
currency: originalRequest.currency,
phoneNumber: originalRequest.phoneNumber,
fullName: originalRequest.fullName,
email: originalRequest.email,
reference: originalRequest.reference,
message: providerResponse.message,
timestamp: new Date().toISOString(),
paymentMethod: originalRequest.paymentMethod,
operator,
providerData: providerResponse,
});
return new models_1.VokoPaymentResponse(response);
}
getAuthHeaders() {
return {};
}
async checkPaymentStatus(request) {
throw new errors_1.VokoError('Azampay does not support payment status checking. Use webhooks instead.', types_1.VokoErrorCode.PROVIDER_ERROR);
}
transformStatusResponse(_providerResponse) {
throw new errors_1.VokoError('Status check not supported by Azampay', types_1.VokoErrorCode.PROVIDER_ERROR);
}
getStatusRequestData(_request) {
throw new errors_1.VokoError('Status check not supported by Azampay', types_1.VokoErrorCode.PROVIDER_ERROR);
}
mapErrorCode(providerError) {
const errorMappings = {
'100': types_1.VokoErrorCode.AUTHENTICATION_FAILED,
'101': types_1.VokoErrorCode.INVALID_PHONE,
'102': types_1.VokoErrorCode.INSUFFICIENT_FUNDS,
'103': types_1.VokoErrorCode.TRANSACTION_NOT_FOUND,
'104': types_1.VokoErrorCode.NETWORK_ERROR,
'105': types_1.VokoErrorCode.PAYMENT_DECLINED,
'900': types_1.VokoErrorCode.PROVIDER_ERROR,
'400': types_1.VokoErrorCode.INVALID_REQUEST,
};
const errorCode = providerError?.code?.toString() || providerError?.result;
return errorMappings[errorCode] || types_1.VokoErrorCode.UNKNOWN_ERROR;
}
verifyWebhookSignature(_payload, _signature) {
return true;
}
async authenticate() {
const tokenRequest = {
appName: this.appName,
clientId: this.clientId,
clientSecret: this.clientSecret,
};
const authUrl = azampay_config_1.AZAMPAY_CONFIG[this.config.environment].authenticationUrl;
const tokenEndpoint = azampay_config_1.AZAMPAY_CONFIG[this.config.environment].endpoints.token;
const fullUrl = `${authUrl}${tokenEndpoint}`;
const response = await this.httpClient.request({
url: fullUrl,
method: 'POST',
headers: this.getContentHeaders(),
data: tokenRequest,
});
return response;
}
mapAzampayStatus(status) {
switch (status) {
case 'success':
return types_1.PaymentStatus.SUCCESS;
case 'failed':
return types_1.PaymentStatus.FAILED;
case 'pending':
return types_1.PaymentStatus.PENDING;
default:
return types_1.PaymentStatus.UNKNOWN;
}
}
mapOperator(operator) {
if (!operator)
return undefined;
const operatorMappings = {
VODACOM: types_1.MobileOperator.VODACOM_MPESA,
MPESA: types_1.MobileOperator.VODACOM_MPESA,
AIRTEL: types_1.MobileOperator.AIRTEL_MONEY,
TIGO: types_1.MobileOperator.TIGO_PESA,
HALOTEL: types_1.MobileOperator.HALO_PESA,
AZAMPESA: types_1.MobileOperator.AZAM_PESA,
};
return operatorMappings[operator.toUpperCase()];
}
}
exports.AzampayAdapter = AzampayAdapter;