"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var PaystackService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.PaystackService = void 0; const common_1 = require("@nestjs/common"); const config_1 = require("@nestjs/config"); let PaystackService = PaystackService_1 = class PaystackService { configService; logger = new common_1.Logger(PaystackService_1.name); secretKey; publicKey; baseUrl = 'https://api.paystack.co'; callbackUrl; constructor(configService) { this.configService = configService; this.secretKey = this.configService.get('PAYSTACK_SECRET_KEY') || ''; this.publicKey = this.configService.get('PAYSTACK_PUBLIC_KEY') || ''; this.callbackUrl = this.configService.get('PAYSTACK_CALLBACK_URL') || ''; if (!this.secretKey) { this.logger.warn('PAYSTACK_SECRET_KEY is not configured'); } } isConfigured() { return Boolean(this.secretKey); } getPublicKey() { return this.publicKey; } getHeaders() { return { Authorization: `Bearer ${this.secretKey}`, 'Content-Type': 'application/json', }; } async initializeTransaction(input) { if (!this.secretKey) { throw new Error('Paystack is not configured'); } const body = { email: input.email, amount: Math.round(input.amount), reference: input.reference, }; if (input.currency) body.currency = input.currency; if (input.callbackUrl) body.callback_url = input.callbackUrl; if (input.metadata) body.metadata = JSON.stringify(input.metadata); if (input.channels?.length) body.channels = input.channels; const response = await fetch(`${this.baseUrl}/transaction/initialize`, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify(body), }); const payload = (await response.json()); if (!response.ok || !payload.status || !payload.data) { this.logger.error(`Paystack initialize failed: ${JSON.stringify(payload)}`); throw new Error(payload.message || 'Failed to initialize Paystack transaction'); } return { authorizationUrl: payload.data.authorization_url, accessCode: payload.data.access_code, reference: payload.data.reference, }; } async createMobileMoneyCharge(input) { if (!this.secretKey) { throw new Error('Paystack is not configured'); } const body = { email: input.email, amount: Math.round(input.amount), reference: input.reference, mobile_money: { phone: this.normalizeMobileMoneyPhone(input.mobileMoney.phone), provider: input.mobileMoney.provider, }, }; if (input.currency) body.currency = input.currency; if (input.callbackUrl) body.callback_url = input.callbackUrl; if (input.metadata) body.metadata = JSON.stringify(input.metadata); const response = await fetch(`${this.baseUrl}/charge`, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify(body), }); const payload = (await response.json()); if (!response.ok || !payload.status || !payload.data) { this.logger.error(`Paystack mobile money charge failed: ${JSON.stringify(payload)}`); throw new Error(payload.message || 'Failed to initiate Paystack mobile money charge'); } return { status: payload.data.status, reference: payload.data.reference, displayMessage: payload.data.display_message, providerReference: payload.data.provider_reference, }; } async verifyTransaction(reference) { if (!this.secretKey) { throw new Error('Paystack is not configured'); } const response = await fetch(`${this.baseUrl}/transaction/verify/${encodeURIComponent(reference)}`, { method: 'GET', headers: this.getHeaders() }); const payload = (await response.json()); if (!response.ok || !payload.status || !payload.data) { this.logger.error(`Paystack verify failed: ${JSON.stringify(payload)}`); throw new Error(payload.message || 'Failed to verify Paystack transaction'); } return { status: payload.data.status, amount: payload.data.amount, currency: payload.data.currency, reference: payload.data.reference, channel: payload.data.channel, paidAt: payload.data.paid_at ?? null, gatewayResponse: payload.data.gateway_response, authorizationCode: payload.data.authorization?.authorization_code, }; } verifyWebhookSignature(rawBody, signatureHeader) { if (!this.secretKey || !signatureHeader) return false; const { createHmac } = require('crypto'); const expected = createHmac('sha512', this.secretKey).update(rawBody).digest('hex'); if (expected.length !== signatureHeader.length) return false; let mismatch = 0; for (let i = 0; i < expected.length; i++) { mismatch |= expected.charCodeAt(i) ^ signatureHeader.charCodeAt(i); } return mismatch === 0; } async createPlan(input) { if (!this.secretKey) throw new Error('Paystack is not configured'); const body = { name: input.name, amount: Math.round(input.amount), interval: input.interval, }; if (input.currency) body.currency = input.currency; if (input.description) body.description = input.description; if (input.planCode) body.plan_code = input.planCode; const response = await fetch(`${this.baseUrl}/plan`, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify(body), }); const payload = (await response.json()); if (!response.ok || !payload.status || !payload.data) { this.logger.error(`Paystack plan creation failed: ${JSON.stringify(payload)}`); throw new Error(payload.message || 'Failed to create Paystack plan'); } return payload.data; } async fetchPlan(idOrCode) { if (!this.secretKey) throw new Error('Paystack is not configured'); const response = await fetch(`${this.baseUrl}/plan/${encodeURIComponent(idOrCode)}`, { method: 'GET', headers: this.getHeaders() }); const payload = (await response.json()); if (!response.ok || !payload.status || !payload.data) return null; return payload.data; } async createSubscription(input) { if (!this.secretKey) throw new Error('Paystack is not configured'); const body = { customer: input.customerCode, plan: input.planCode, }; if (input.authorizationCode) body.authorization = input.authorizationCode; if (input.startDate) body.start_date = input.startDate; const response = await fetch(`${this.baseUrl}/subscription`, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify(body), }); const payload = (await response.json()); if (!response.ok || !payload.status || !payload.data) { this.logger.error(`Paystack subscription creation failed: ${JSON.stringify(payload)}`); throw new Error(payload.message || 'Failed to create Paystack subscription'); } return payload.data; } async fetchSubscription(idOrCode) { if (!this.secretKey) throw new Error('Paystack is not configured'); const response = await fetch(`${this.baseUrl}/subscription/${encodeURIComponent(idOrCode)}`, { method: 'GET', headers: this.getHeaders() }); const payload = (await response.json()); if (!response.ok || !payload.status || !payload.data) return null; return payload.data; } async disableSubscription(subscriptionCode, token) { if (!this.secretKey) throw new Error('Paystack is not configured'); const response = await fetch(`${this.baseUrl}/subscription/disable`, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify({ code: subscriptionCode, token, }), }); const payload = (await response.json()); if (!response.ok || !payload.status || !payload.data) { this.logger.error(`Paystack subscription disable failed: ${JSON.stringify(payload)}`); throw new Error(payload.message || 'Failed to disable Paystack subscription'); } return payload.data; } async enableSubscription(subscriptionCode, token) { if (!this.secretKey) throw new Error('Paystack is not configured'); const response = await fetch(`${this.baseUrl}/subscription/enable`, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify({ code: subscriptionCode, token, }), }); const payload = (await response.json()); if (!response.ok || !payload.status || !payload.data) { this.logger.error(`Paystack subscription enable failed: ${JSON.stringify(payload)}`); throw new Error(payload.message || 'Failed to enable Paystack subscription'); } return payload.data; } async refundTransaction(reference, amountInSubunits) { if (!this.secretKey) throw new Error('Paystack is not configured'); const body = { transaction: reference }; if (typeof amountInSubunits === 'number') body.amount = amountInSubunits; const response = await fetch(`${this.baseUrl}/refund`, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify(body), }); const payload = (await response.json()); if (!response.ok || !payload.status || !payload.data) { this.logger.error(`Paystack refund failed: ${JSON.stringify(payload)}`); throw new Error(payload.message || 'Failed to refund Paystack transaction'); } return payload.data; } async createCustomer(input) { if (!this.secretKey) throw new Error('Paystack is not configured'); const body = { email: input.email }; if (input.firstName) body.first_name = input.firstName; if (input.lastName) body.last_name = input.lastName; if (input.phone) body.phone = input.phone; if (input.metadata) body.metadata = input.metadata; const response = await fetch(`${this.baseUrl}/customer`, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify(body), }); const payload = (await response.json()); if (!response.ok || !payload.status || !payload.data) { this.logger.error(`Paystack customer creation failed: ${JSON.stringify(payload)}`); throw new Error(payload.message || 'Failed to create Paystack customer'); } return payload.data; } normalizeMobileMoneyPhone(phone) { const digits = phone.replace(/\D/g, ''); if (digits.startsWith('254') && digits.length === 12) { return `0${digits.slice(3)}`; } if (digits.startsWith('0') && digits.length === 10) { return digits; } if (digits.length === 9) { return `0${digits}`; } throw new Error('Invalid phone number. Use 07XX, 01XX, or 2547XXXXXXXX format.'); } }; exports.PaystackService = PaystackService; exports.PaystackService = PaystackService = PaystackService_1 = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", [config_1.ConfigService]) ], PaystackService); //# sourceMappingURL=paystack.service.js.map