All files / src/features/donation/services stripe.service.ts

83.33% Statements 25/30
87.5% Branches 14/16
100% Functions 5/5
83.33% Lines 25/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104                            12x   12x     4x   4x   4x         4x                                   2x 2x 2x               2x 1x   1x         1x 1x                 2x   2x   2x       2x 2x 1x     1x 1x       1x 1x 1x                  
import { Injectable, Logger } from '@nestjs/common'
import { IncomingHttpHeaders } from 'http'
import { ConfigService } from '@nestjs/config'
import Stripe from 'stripe'
 
import {
    PaymentProvider,
    CreatePaymentIntentResult,
    PaymentConfig,
} from '../interfaces/payment-provider.interface'
import { StripeProviderConfig } from '@fundraising/white-labeling'
 
@Injectable()
export class StripeService implements PaymentProvider {
    private readonly logger = new Logger(StripeService.name)
 
    constructor(private configService: ConfigService) {}
 
    private getClient(config?: PaymentConfig): Stripe {
        const stripeConfig = config as StripeProviderConfig
        const secretKey =
            stripeConfig?.secretKey || this.configService.get<string>('STRIPE_SECRET_KEY')
 
        Iif (!secretKey) {
            this.logger.error('Stripe Secret Key is missing (Check DB Config or ENV)')
            throw new Error('Stripe Secret Key is not configured')
        }
 
        return new Stripe(secretKey, {
            apiVersion: '2025-12-15.clover', // Updated to match SDK version
        })
    }
 
    /**
     * Creates a Stripe PaymentIntent for the specified amount.
     * @param amount Amount in cents (smallest currency unit)
     * @param currency Currency code (default: 'usd')
     * @param metadata Additional metadata to attach to the intent
     * @returns Object containing the clientSecret and intent ID
     */
    async createPaymentIntent(
        amount: number,
        currency: string = 'usd',
        metadata: Record<string, any> = {},
        config?: PaymentConfig,
    ): Promise<CreatePaymentIntentResult> {
        try {
            const stripe = this.getClient(config)
            const paymentIntent = await stripe.paymentIntents.create({
                amount, // Amount in cents
                currency,
                metadata,
                automatic_payment_methods: {
                    enabled: true,
                },
            })
            if (!paymentIntent.client_secret) {
                throw new Error('Failed to retrieve client secret from Stripe')
            }
            return {
                clientSecret: paymentIntent.client_secret,
                id: paymentIntent.id,
            }
        } catch (error) {
            this.logger.error(`Error creating payment intent: ${error.message}`)
            throw error
        }
    }
 
    constructEventFromPayload(
        headers: IncomingHttpHeaders | string,
        payload: Buffer,
        config?: PaymentConfig,
    ) {
        const stripeConfig = config as StripeProviderConfig
        const webhookSecret =
            stripeConfig?.webhookSecret || this.configService.get<string>('STRIPE_WEBHOOK_SECRET')
 
        Iif (!webhookSecret) {
            throw new Error('STRIPE_WEBHOOK_SECRET is not configured')
        }
 
        const signature = typeof headers === 'string' ? headers : headers?.['stripe-signature']
        if (!signature) {
            return Promise.reject(new Error('Missing stripe-signature header'))
        }
 
        const stripe = this.getClient(config)
        return Promise.resolve(stripe.webhooks.constructEvent(payload, signature, webhookSecret))
    }
 
    async refundDonation(paymentIntentId: string, config?: PaymentConfig): Promise<any> {
        try {
            const stripe = this.getClient(config)
            return await stripe.refunds.create({
                payment_intent: paymentIntentId,
            })
        } catch (error) {
            this.logger.error(`Error refunding donation ${paymentIntentId}: ${error.message}`)
            throw error
        }
    }
}