All files / src/modules/domain/payment/gateway/stripe/services stripe.service.ts

0% Statements 0/46
0% Branches 0/6
0% Functions 0/11
0% Lines 0/42

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 128 129 130                                                                                                                                                                                                                                                                   
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
import { ERRORS } from 'src/exception/exception.const';
import ENV from 'src/shared/env';
import Stripe from 'stripe';
import { MOCK_CARD_TOKEN_MAPPING } from '../shared/stripe.const';
import { CardDetailDto, TransactionDto } from '../shared/stripe.dto';
import { IUser } from '../shared/stripe.interface';
import { STRIPE_TOKEN } from '../shared/stripe.provider';
 
@Injectable()
export class StripeService {
  constructor(@Inject(STRIPE_TOKEN) private readonly stripe: Stripe) {}
 
  async getCardToken(card: CardDetailDto) {
    /** https://stripe.com/docs/testing?testing-method=tokens#visa */
    Iif (ENV.PAYMENT_GATEWAY.STRIPE.TEST_MODE) return MOCK_CARD_TOKEN_MAPPING[card.brand];
 
    const cardToken = await this.stripe.tokens.create({
      card: {
        number: card.number,
        name: card.name,
        cvc: card.cvc,
        // eslint-disable-next-line @typescript-eslint/naming-convention, camelcase
        exp_month: card.expiryMonth,
        // eslint-disable-next-line @typescript-eslint/naming-convention, camelcase
        exp_year: card.expiryYear,
      },
    });
 
    return cardToken.id;
  }
 
  async getCustomerByEmail(email: string): Promise<Stripe.Customer | undefined> {
    const {
      data: [customer],
    } = await this.stripe.customers.search({
      query: `email:"${email}"`,
    });
 
    return customer;
  }
 
  async getCustomer(email: string) {
    const customer = await this.getCustomerByEmail(email);
 
    Iif (!customer) {
      throw new BadRequestException(ERRORS.CUSTOMER_NOT_FOUND);
    }
 
    const paymentMethods = await this.stripe.customers.listPaymentMethods(customer.id);
 
    return { ...customer, cards: paymentMethods.data };
  }
 
  async getOrAddCustomer(user: IUser) {
    let customer = await this.getCustomerByEmail(user.email);
 
    let isNew = false;
    Iif (!customer) {
      customer = await this.stripe.customers.create({
        email: user.email,
        name: user.fullName,
        metadata: {
          id: user.id,
        },
      });
      isNew = true;
    }
 
    return { ...customer, isNew };
  }
 
  async addCustomerCard(email: string, card: CardDetailDto) {
    const customer = await this.getCustomer(email);
 
    const cardToken = await this.getCardToken(card);
 
    return this.stripe.customers.createSource(customer.id, {
      source: cardToken,
    });
  }
 
  async removeCustomerCard(email: string, cardId: string) {
    const customer = await this.getCustomer(email);
 
    Iif (!customer.cards.find((card) => card.id === cardId)) {
      throw new BadRequestException(ERRORS.CUSTOMER_CARD_NOT_FOUND);
    }
 
    return this.stripe.customers.deleteSource(customer.id, cardId);
  }
 
  async charge(email: string, transaction: TransactionDto) {
    const customer = await this.getCustomer(email);
 
    const charge = await this.stripe.charges.create({
      customer: customer.id,
      amount: transaction.amount,
      currency: transaction.currency,
      description: transaction.description,
    });
 
    return charge;
  }
 
  async updateCustomer(user: IUser) {
    const customer = await this.getCustomerByEmail(user.email);
    Iif (!customer) {
      throw new BadRequestException(ERRORS.CUSTOMER_NOT_FOUND);
    }
 
    return this.stripe.customers.update(customer.id, {
      email: user.email,
      name: user.fullName,
      metadata: {
        id: user.id,
      },
    });
  }
 
  async removeCustomerByEmail(email: string) {
    const customer = await this.getCustomerByEmail(email);
    Iif (!customer) {
      throw new BadRequestException(ERRORS.CUSTOMER_NOT_FOUND);
    }
 
    return this.stripe.customers.del(customer.id);
  }
}