How to perform transaction when I have to make request to external API?

Viewed 288

I am developing cart functionality and for payment processing I am using stripe.

The thing I am concerned is this: I heard that we should avoid long-running transactions to avoid deadlocking whole db? Not sure about this but that's why I want to understand it better.

I am using TypeORM, MySQL, Nest.js for my back-end. For payment processing I am using stripe.

So this means that I will need to perform request (to get payment from the user), but some things could happen. For example it could be insufficient funds, stripe server-side error?

And I am concerned whether this request should be part of transaction? On successful stripe response I have to create records in database and on failure I have to roll-back everything.

There are many things that could go wrong - item which is sold could run out of stock?

This is demo could which shows what actions I want to perform:

@Controller('orders')
@UsePipes(new ValidationPipe({ transform: true }))
@UseInterceptors(ClassSerializerInterceptor)
export class OrdersController {
  constructor(
    private readonly cartsService: CartsService,
    private readonly ordersService: OrdersService,
    private readonly ticketsService: TicketsService,
    private connection: Connection,
  ) {}

  @Post()
  async fullfilOrder(@Body() dto: CreateOrderDto) {
    
    // Get cart for checking the integrity
    const cart = await this.cartsService.findByPublicId(dto.cart.publicId);

    // Calculate payable amount
    const pricePerCompetition = cart.cartItems.map(
      item => item.quantity * item.competition.entryPrice,
    );

    const amount =
      pricePerCompetition.length === 0
        ? 0
        : pricePerCompetition.reduce((a, b) => a + b);

    // charge user
    const stripeCharge = await this.ordersService.submitStripeCharge(
      dto.stripeToken,
      amount,
      cart,
      { firstName: dto.firstName, lastName: dto.lastName, email: dto.email },
    );

    // create order
    const order = await this.ordersService.createOrder(
      cart,
      dto,
      stripeCharge.id,
    );

    // mark cart status as fulfiled
    cart.status = EnumerableCartStatus.FULFILED;
    await cart.save();

    // create ticket
    const ticket = await this.ticketsService.addUserTickets(
      AddTicketsDto.fromSuccessfulPayment(
        cart.user,
        dto.firstName,
        dto.lastName,
        dto.email,
        cart.cartItems,
      ),
    );

    //create new cart for user
    const newCart = new Cart();
    newCart.publicId = v4();
    newCart.user = cart.user;
    newCart.cartItems = [];
    await newCart.save();

    delete newCart.user;

    return { cart: newCart };
  }
}

Any ideas, what approach would I need for this situation? I read about queryRunner: https://docs.nestjs.com/techniques/database but this made me think about stripe request which I am performing, It wouldn't be wise to include it as part of transaction?

0 Answers
Related