All files / src/platform/billing/providers razorpay.provider.ts

0% Statements 0/28
0% Branches 0/13
0% Functions 0/4
0% Lines 0/28

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 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127                                                                                                                                                                                                                                                             
import { PaymentProvider } from '../payment-provider.interface';
import Razorpay from 'razorpay';
import * as crypto from 'crypto';
import { Logger } from '@nestjs/common';
 
export class RazorpayProvider implements PaymentProvider {
  name = 'Razorpay';
  private razorpay: any;
  private readonly logger = new Logger(RazorpayProvider.name);
  private webhookSecret: string;
 
  constructor(keyId: string, keySecret: string, webhookSecret: string = '') {
    this.razorpay = new Razorpay({
      key_id: keyId,
      key_secret: keySecret,
    });
    this.webhookSecret = webhookSecret;
  }
 
  async createCheckoutLink(params: {
    tenantId: string;
    amount: number;
    currency: string;
    invoiceId: string;
    callbackUrl: string;
  }): Promise<{ checkoutUrl: string; providerIntentId: string }> {
    try {
      // Razorpay Payment Links API
      const paymentLink = await this.razorpay.paymentLink.create({
        amount: params.amount,
        currency: params.currency,
        accept_partial: false,
        reference_id: params.invoiceId,
        description: `Invoice ${params.invoiceId}`,
        customer: {
          name: params.tenantId, // Ideally get from DB
          contact: '',
          email: '',
        },
        notify: {
          sms: false,
          email: false,
        },
        reminder_enable: false,
        notes: {
          invoiceId: params.invoiceId,
          tenantId: params.tenantId,
        },
        callback_url: params.callbackUrl,
        callback_method: 'get',
      });
 
      return {
        checkoutUrl: paymentLink.short_url,
        providerIntentId: paymentLink.id,
      };
    } catch (error: any) {
      this.logger.error(`Razorpay create checkout failed: ${error.message}`);
      throw error;
    }
  }
 
  async verifyWebhook(
    payload: any,
    signature: string,
  ): Promise<{
    success: boolean;
    transactionId: string;
    amountPaid: number;
    status: 'SUCCESS' | 'FAILED';
  }> {
    // Verify signature
    if (this.webhookSecret && typeof payload === 'string') {
      const expectedSignature = crypto
        .createHmac('sha256', this.webhookSecret)
        .update(payload)
        .digest('hex');
 
      if (expectedSignature !== signature) {
        this.logger.error('Razorpay webhook signature verification failed');
        return {
          success: false,
          transactionId: '',
          amountPaid: 0,
          status: 'FAILED',
        };
      }
    }
 
    const event = typeof payload === 'string' ? JSON.parse(payload) : payload;
 
    if (event.event === 'payment_link.paid') {
      const paymentLink = event.payload.payment_link.entity;
      return {
        success: true,
        transactionId: paymentLink.id,
        amountPaid: paymentLink.amount_paid || 0,
        status: 'SUCCESS',
      };
    }
 
    return {
      success: true,
      transactionId: '',
      amountPaid: 0,
      status: 'FAILED',
    };
  }
 
  async refund(
    transactionId: string,
    amount: number,
  ): Promise<{ success: boolean; refundId: string }> {
    try {
      // Razorpay refunds require the actual payment ID, which we'd typically store
      // Assuming transactionId here is the payment ID
      const refund = await this.razorpay.payments.refund(transactionId, {
        amount,
      });
      return { success: true, refundId: refund.id };
    } catch (error: any) {
      this.logger.error(`Razorpay refund failed: ${error.message}`);
      return { success: false, refundId: '' };
    }
  }
}