UNPKG

voko-sdk

Version:
238 lines (237 loc) 9.53 kB
"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;