"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);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PaymentGatewayService = void 0;
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const client_1 = require("@prisma/client");
const crypto_1 = require("crypto");
const api_paths_1 = require("../common/api-paths");
const integration_config_service_1 = require("../integrations/integration-config.service");
let PaymentGatewayService = class PaymentGatewayService {
    config;
    integrations;
    constructor(config, integrations) {
        this.config = config;
        this.integrations = integrations;
    }
    createReference(provider) {
        return `${provider.slice(0, 3)}-${Date.now()}-${(0, crypto_1.randomUUID)().slice(0, 8)}`;
    }
    getProviderRuntime(provider) {
        return {
            provider,
            mode: this.mode,
            configured: this.isProviderConfigured(provider),
            baseUrl: this.providerBaseUrl(provider)
        };
    }
    isProviderConfigured(provider) {
        switch (provider) {
            case client_1.PaymentProvider.FLUTTERWAVE:
                return this.hasUsableValue(this.configValue(provider, 'FLUTTERWAVE_SECRET_KEY'));
            case client_1.PaymentProvider.MONNIFY:
                return (this.hasUsableValue(this.configValue(provider, 'MONNIFY_API_KEY')) &&
                    this.hasUsableValue(this.configValue(provider, 'MONNIFY_SECRET_KEY')) &&
                    this.hasUsableValue(this.configValue(provider, 'MONNIFY_CONTRACT_CODE')));
            case client_1.PaymentProvider.PAYSTACK:
                return this.hasUsableValue(this.configValue(provider, 'PAYSTACK_SECRET_KEY'));
            case client_1.PaymentProvider.STRIPE:
                return this.hasUsableValue(this.configValue(provider, 'STRIPE_SECRET_KEY'));
            default:
                return false;
        }
    }
    shouldVerifyGatewayPayment(metadata) {
        const gateway = this.gatewayMetadata(metadata);
        return gateway.initialized === true;
    }
    async initializePayment(input) {
        switch (input.provider) {
            case client_1.PaymentProvider.FLUTTERWAVE:
                return this.initializeFlutterwavePayment(input);
            case client_1.PaymentProvider.MONNIFY:
                return this.initializeMonnifyPayment(input);
            case client_1.PaymentProvider.PAYSTACK:
                return this.initializePaystackPayment(input);
            case client_1.PaymentProvider.STRIPE:
                return this.initializeStripePayment(input);
            default:
                throw new common_1.ServiceUnavailableException('Payment provider is not supported');
        }
    }
    async verifyPayment(input) {
        switch (input.provider) {
            case client_1.PaymentProvider.FLUTTERWAVE:
                return this.verifyFlutterwavePayment(input);
            case client_1.PaymentProvider.MONNIFY:
                return this.verifyMonnifyPayment(input);
            case client_1.PaymentProvider.PAYSTACK:
                return this.verifyPaystackPayment(input);
            case client_1.PaymentProvider.STRIPE:
                return this.verifyStripePayment(input);
            default:
                throw new common_1.ServiceUnavailableException('Payment provider is not supported');
        }
    }
    async initializePaystackPayment(input) {
        const secretKey = this.requiredConfig('PAYSTACK_SECRET_KEY', input.provider);
        const payload = {
            email: input.customerEmail,
            amount: input.amountKobo,
            currency: input.currency,
            reference: input.reference,
            callback_url: this.callbackUrl(input.provider, input.reference),
            metadata: {
                estateId: input.estateId,
                planCode: input.planCode,
                customerPhone: input.customerPhone,
                ...(input.metadata ?? {})
            }
        };
        const raw = await this.fetchJson(`${this.providerBaseUrl(input.provider)}/transaction/initialize`, input.provider, {
            method: 'POST',
            headers: {
                Authorization: `Bearer ${secretKey}`,
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(payload)
        });
        const checkoutUrl = raw.data?.authorization_url;
        if (!raw.status || !checkoutUrl) {
            throw new common_1.BadGatewayException(raw.message ?? 'Paystack did not return an authorization URL');
        }
        return {
            provider: input.provider,
            mode: this.mode,
            reference: input.reference,
            providerReference: raw.data?.reference ?? input.reference,
            checkoutUrl,
            accessCode: raw.data?.access_code ?? null,
            raw
        };
    }
    async initializeFlutterwavePayment(input) {
        const secretKey = this.requiredConfig('FLUTTERWAVE_SECRET_KEY', input.provider);
        const payload = {
            tx_ref: input.reference,
            amount: this.toMajorCurrencyAmount(input.amountKobo),
            currency: input.currency,
            redirect_url: this.callbackUrl(input.provider, input.reference),
            customer: {
                email: input.customerEmail,
                phonenumber: input.customerPhone,
                name: input.customerName
            },
            customizations: {
                title: 'Smart Estate subscription',
                description: `${input.planName} subscription for ${input.estateName}`
            },
            meta: {
                estateId: input.estateId,
                planCode: input.planCode,
                ...(input.metadata ?? {})
            }
        };
        const raw = await this.fetchJson(`${this.providerBaseUrl(input.provider)}/payments`, input.provider, {
            method: 'POST',
            headers: {
                Authorization: `Bearer ${secretKey}`,
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(payload)
        });
        const checkoutUrl = raw.data?.link;
        if (raw.status !== 'success' || !checkoutUrl) {
            throw new common_1.BadGatewayException(raw.message ?? 'Flutterwave did not return a checkout link');
        }
        return {
            provider: input.provider,
            mode: this.mode,
            reference: input.reference,
            providerReference: input.reference,
            checkoutUrl,
            raw
        };
    }
    async initializeMonnifyPayment(input) {
        const accessToken = await this.monnifyAccessToken(input.provider);
        const contractCode = this.requiredConfig('MONNIFY_CONTRACT_CODE', input.provider);
        const payload = {
            amount: this.toMajorCurrencyAmount(input.amountKobo),
            customerName: input.customerName,
            customerEmail: input.customerEmail,
            paymentReference: input.reference,
            paymentDescription: `${input.planName} subscription for ${input.estateName}`,
            currencyCode: input.currency,
            contractCode,
            redirectUrl: this.callbackUrl(input.provider, input.reference),
            paymentMethods: ['CARD', 'ACCOUNT_TRANSFER'],
            metaData: {
                estateId: input.estateId,
                planCode: input.planCode,
                customerPhone: input.customerPhone,
                ...(input.metadata ?? {})
            }
        };
        const raw = await this.fetchJson(`${this.providerBaseUrl(input.provider)}/api/v1/merchant/transactions/init-transaction`, input.provider, {
            method: 'POST',
            headers: {
                Authorization: `Bearer ${accessToken}`,
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(payload)
        });
        const checkoutUrl = raw.responseBody?.checkoutUrl;
        if (!raw.requestSuccessful || !checkoutUrl) {
            throw new common_1.BadGatewayException(raw.responseMessage ?? 'Monnify did not return a checkout URL');
        }
        return {
            provider: input.provider,
            mode: this.mode,
            reference: raw.responseBody?.paymentReference ?? input.reference,
            providerReference: raw.responseBody?.transactionReference ?? null,
            checkoutUrl,
            raw
        };
    }
    async initializeStripePayment(input) {
        const secretKey = this.requiredConfig('STRIPE_SECRET_KEY', input.provider);
        const body = new URLSearchParams();
        body.set('mode', 'payment');
        body.set('client_reference_id', input.reference);
        body.set('success_url', this.stripeReturnUrl(input.reference, true));
        body.set('cancel_url', this.stripeReturnUrl(input.reference, false));
        body.set('line_items[0][quantity]', '1');
        body.set('line_items[0][price_data][currency]', input.currency.toLowerCase());
        body.set('line_items[0][price_data][unit_amount]', String(input.amountKobo));
        body.set('line_items[0][price_data][product_data][name]', `${input.planName} subscription`);
        body.set('line_items[0][price_data][product_data][description]', `${input.estateName} estate subscription`);
        body.set('metadata[reference]', input.reference);
        body.set('metadata[estateId]', input.estateId);
        body.set('metadata[planCode]', input.planCode);
        body.set('payment_intent_data[metadata][reference]', input.reference);
        body.set('payment_intent_data[metadata][estateId]', input.estateId);
        body.set('payment_intent_data[metadata][planCode]', input.planCode);
        const raw = await this.fetchJson(`${this.providerBaseUrl(input.provider)}/v1/checkout/sessions`, input.provider, {
            method: 'POST',
            headers: {
                Authorization: `Basic ${Buffer.from(`${secretKey}:`).toString('base64')}`,
                'Content-Type': 'application/x-www-form-urlencoded'
            },
            body
        });
        if (!raw.id || !raw.url) {
            throw new common_1.BadGatewayException('Stripe did not return a checkout session URL');
        }
        return {
            provider: input.provider,
            mode: this.mode,
            reference: input.reference,
            providerReference: raw.id,
            checkoutUrl: raw.url,
            raw
        };
    }
    async verifyPaystackPayment(input) {
        const secretKey = this.requiredConfig('PAYSTACK_SECRET_KEY', input.provider);
        const raw = await this.fetchJson(`${this.providerBaseUrl(input.provider)}/transaction/verify/${encodeURIComponent(input.reference)}`, input.provider, {
            headers: { Authorization: `Bearer ${secretKey}` }
        });
        const data = raw.data;
        return this.verificationResult(input, {
            providerReference: data?.reference ?? input.reference,
            status: data?.status ?? raw.message ?? 'unknown',
            amountKobo: data?.amount ?? null,
            currency: data?.currency ?? null,
            raw,
            paid: raw.status === true && data?.status === 'success'
        });
    }
    async verifyFlutterwavePayment(input) {
        const secretKey = this.requiredConfig('FLUTTERWAVE_SECRET_KEY', input.provider);
        const gateway = this.gatewayMetadata(input.metadata);
        const transactionId = this.stringValue(gateway.providerTransactionId);
        const endpoint = transactionId
            ? `${this.providerBaseUrl(input.provider)}/transactions/${encodeURIComponent(transactionId)}/verify`
            : `${this.providerBaseUrl(input.provider)}/transactions/verify_by_reference?tx_ref=${encodeURIComponent(input.reference)}`;
        const raw = await this.fetchJson(endpoint, input.provider, {
            headers: { Authorization: `Bearer ${secretKey}` }
        });
        const data = raw.data;
        return this.verificationResult(input, {
            providerReference: data?.id ? String(data.id) : (data?.tx_ref ?? input.reference),
            status: data?.status ?? raw.message ?? 'unknown',
            amountKobo: data?.amount === undefined ? null : this.toKobo(data.amount),
            currency: data?.currency ?? null,
            raw,
            paid: raw.status === 'success' && data?.status === 'successful'
        });
    }
    async verifyMonnifyPayment(input) {
        const accessToken = await this.monnifyAccessToken(input.provider);
        const gateway = this.gatewayMetadata(input.metadata);
        const transactionReference = this.stringValue(gateway.providerReference);
        const query = transactionReference
            ? `transactionReference=${encodeURIComponent(transactionReference)}`
            : `paymentReference=${encodeURIComponent(input.reference)}`;
        const raw = await this.fetchJson(`${this.providerBaseUrl(input.provider)}/api/v2/merchant/transactions/query?${query}`, input.provider, {
            headers: { Authorization: `Bearer ${accessToken}` }
        });
        const data = raw.responseBody;
        const status = data?.paymentStatus ?? raw.responseMessage ?? 'unknown';
        return this.verificationResult(input, {
            providerReference: data?.transactionReference ?? transactionReference ?? input.reference,
            status,
            amountKobo: data?.amountPaid === undefined && data?.amount === undefined ? null : this.toKobo(data.amountPaid ?? data.amount ?? 0),
            currency: data?.currencyCode ?? null,
            raw,
            paid: raw.requestSuccessful === true && ['PAID', 'SUCCESS', 'SUCCESSFUL'].includes(status.toUpperCase())
        });
    }
    async verifyStripePayment(input) {
        const secretKey = this.requiredConfig('STRIPE_SECRET_KEY', input.provider);
        const gateway = this.gatewayMetadata(input.metadata);
        const sessionId = this.stringValue(gateway.providerReference) ?? this.stringValue(gateway.providerTransactionId);
        if (!sessionId) {
            throw new common_1.BadGatewayException('Stripe checkout session id is missing from payment metadata');
        }
        const raw = await this.fetchJson(`${this.providerBaseUrl(input.provider)}/v1/checkout/sessions/${encodeURIComponent(sessionId)}`, input.provider, {
            headers: { Authorization: `Basic ${Buffer.from(`${secretKey}:`).toString('base64')}` }
        });
        return this.verificationResult(input, {
            providerReference: raw.id ?? sessionId,
            status: raw.payment_status ?? raw.status ?? 'unknown',
            amountKobo: raw.amount_total ?? null,
            currency: raw.currency?.toUpperCase() ?? null,
            raw,
            paid: raw.payment_status === 'paid'
        });
    }
    verificationResult(input, data) {
        const amountMatches = input.amountKobo === null || input.amountKobo === undefined || data.amountKobo === null || data.amountKobo === input.amountKobo;
        const currencyMatches = !input.currency || data.currency === null || data.currency.toUpperCase() === input.currency.toUpperCase();
        return {
            provider: input.provider,
            mode: this.mode,
            reference: input.reference,
            providerReference: data.providerReference,
            paid: data.paid && amountMatches && currencyMatches,
            status: data.status,
            amountKobo: data.amountKobo,
            currency: data.currency,
            raw: data.raw,
            verifiedAt: new Date().toISOString()
        };
    }
    async monnifyAccessToken(provider) {
        const apiKey = this.requiredConfig('MONNIFY_API_KEY', provider);
        const secretKey = this.requiredConfig('MONNIFY_SECRET_KEY', provider);
        const raw = await this.fetchJson(`${this.providerBaseUrl(provider)}/api/v1/auth/login`, provider, {
            method: 'POST',
            headers: {
                Authorization: `Basic ${Buffer.from(`${apiKey}:${secretKey}`).toString('base64')}`
            }
        });
        const token = raw.responseBody?.accessToken;
        if (!raw.requestSuccessful || !token) {
            throw new common_1.BadGatewayException(raw.responseMessage ?? 'Monnify did not return an access token');
        }
        return token;
    }
    async fetchJson(url, provider, init = {}) {
        let response;
        try {
            response = await fetch(url, init);
        }
        catch (error) {
            throw new common_1.BadGatewayException(`${provider} sandbox request failed: ${String(error)}`);
        }
        const text = await response.text();
        const payload = text ? this.parseJson(text) : {};
        if (!response.ok) {
            throw new common_1.BadGatewayException(`${provider} sandbox request failed: ${this.gatewayMessage(payload)}`);
        }
        return payload;
    }
    parseJson(text) {
        try {
            return JSON.parse(text);
        }
        catch {
            return { message: text };
        }
    }
    gatewayMessage(payload) {
        if (payload && typeof payload === 'object') {
            const data = payload;
            return (this.stringValue(data.message) ??
                this.stringValue(data.responseMessage) ??
                this.stringValue(data.error) ??
                'Unexpected gateway response');
        }
        return 'Unexpected gateway response';
    }
    requiredConfig(key, provider) {
        const value = this.configValue(provider, key);
        if (!this.hasUsableValue(value)) {
            throw new common_1.ServiceUnavailableException(`${provider} credentials are not configured. Set ${key} in .env or update /admin/integrations/config/${provider}.`);
        }
        return value;
    }
    hasUsableValue(value) {
        return Boolean(value && !/REPLACE_ME|YOUR_|your-|placeholder/i.test(value));
    }
    providerBaseUrl(provider) {
        switch (provider) {
            case client_1.PaymentProvider.FLUTTERWAVE:
                return this.configValue(provider, 'FLUTTERWAVE_BASE_URL', 'https://api.flutterwave.com/v3');
            case client_1.PaymentProvider.MONNIFY:
                return this.configValue(provider, 'MONNIFY_BASE_URL', this.mode === 'live' ? 'https://api.monnify.com' : 'https://sandbox.monnify.com');
            case client_1.PaymentProvider.PAYSTACK:
                return this.configValue(provider, 'PAYSTACK_BASE_URL', 'https://api.paystack.co');
            case client_1.PaymentProvider.STRIPE:
                return this.configValue(provider, 'STRIPE_BASE_URL', 'https://api.stripe.com');
            default:
                return '';
        }
    }
    callbackUrl(provider, reference) {
        const configured = this.config.get('PAYMENT_CALLBACK_URL');
        const base = configured ?? `${this.apiBaseUrl()}/v${(0, api_paths_1.apiVersion)(this.config)}/onboarding/payments/callback`;
        const separator = base.includes('?') ? '&' : '?';
        return `${base}${separator}provider=${provider}&reference=${encodeURIComponent(reference)}`;
    }
    stripeReturnUrl(reference, success) {
        const configured = this.configValue(client_1.PaymentProvider.STRIPE, success ? 'STRIPE_SUCCESS_URL' : 'STRIPE_CANCEL_URL');
        if (configured) {
            return configured
                .replaceAll('{reference}', encodeURIComponent(reference))
                .replaceAll('{provider}', client_1.PaymentProvider.STRIPE)
                .replaceAll('{CHECKOUT_SESSION_ID}', '{CHECKOUT_SESSION_ID}');
        }
        const outcome = success ? 'success' : 'cancelled';
        return `${this.apiBaseUrl()}/v${(0, api_paths_1.apiVersion)(this.config)}/onboarding/payments/callback?provider=STRIPE&reference=${encodeURIComponent(reference)}&status=${outcome}&session_id={CHECKOUT_SESSION_ID}`;
    }
    apiBaseUrl() {
        const configured = this.config.get('PUBLIC_API_BASE_URL');
        if (configured)
            return configured.replace(/\/$/, '');
        const port = this.config.get('API_PORT') ?? '4000';
        return `http://localhost:${port}/${(0, api_paths_1.apiPrefix)(this.config)}`;
    }
    toMajorCurrencyAmount(amountKobo) {
        return Math.round(amountKobo) / 100;
    }
    toKobo(amount) {
        return Math.round(amount * 100);
    }
    gatewayMetadata(metadata) {
        if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) {
            return {};
        }
        const root = metadata;
        const gateway = root.gateway;
        if (!gateway || typeof gateway !== 'object' || Array.isArray(gateway)) {
            return {};
        }
        return gateway;
    }
    stringValue(value) {
        return typeof value === 'string' && value.length > 0 ? value : null;
    }
    get mode() {
        return this.config.get('PAYMENT_GATEWAY_MODE') === 'live' ? 'live' : 'sandbox';
    }
    configValue(provider, envKey, fallback = '') {
        return this.integrations.getValue(provider, this.integrationKey(envKey), envKey, fallback);
    }
    integrationKey(envKey) {
        const key = envKey
            .replace(/^(FLUTTERWAVE|MONNIFY|PAYSTACK|STRIPE)_/, '')
            .toLowerCase()
            .replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
        if (key === 'baseUrl')
            return 'baseUrl';
        if (key === 'publicKey')
            return 'publicKey';
        if (key === 'secretKey')
            return 'secretKey';
        if (key === 'encryptionKey')
            return 'encryptionKey';
        if (key === 'apiKey')
            return 'apiKey';
        if (key === 'contractCode')
            return 'contractCode';
        if (key === 'successUrl')
            return 'successUrl';
        if (key === 'cancelUrl')
            return 'cancelUrl';
        return key;
    }
};
exports.PaymentGatewayService = PaymentGatewayService;
exports.PaymentGatewayService = PaymentGatewayService = __decorate([
    (0, common_1.Injectable)(),
    __metadata("design:paramtypes", [config_1.ConfigService,
        integration_config_service_1.IntegrationConfigService])
], PaymentGatewayService);
//# sourceMappingURL=payment-gateway.service.js.map