- 1. API with NestJS #1. Controllers, routing and the module structure
- 2. API with NestJS #2. Setting up a PostgreSQL database with TypeORM
- 3. API with NestJS #3. Authenticating users with bcrypt, Passport, JWT, and cookies
- 4. API with NestJS #4. Error handling and data validation
- 5. API with NestJS #5. Serializing the response with interceptors
- 6. API with NestJS #6. Looking into dependency injection and modules
- 7. API with NestJS #7. Creating relationships with Postgres and TypeORM
- 8. API with NestJS #8. Writing unit tests
- 9. API with NestJS #9. Testing services and controllers with integration tests
- 10. API with NestJS #10. Uploading public files to Amazon S3
- 11. API with NestJS #11. Managing private files with Amazon S3
- 12. API with NestJS #12. Introduction to Elasticsearch
- 13. API with NestJS #13. Implementing refresh tokens using JWT
- 14. API with NestJS #14. Improving performance of our Postgres database with indexes
- 15. API with NestJS #15. Defining transactions with PostgreSQL and TypeORM
- 16. API with NestJS #16. Using the array data type with PostgreSQL and TypeORM
- 17. API with NestJS #17. Offset and keyset pagination with PostgreSQL and TypeORM
- 18. API with NestJS #18. Exploring the idea of microservices
- 19. API with NestJS #19. Using RabbitMQ to communicate with microservices
- 20. API with NestJS #20. Communicating with microservices using the gRPC framework
- 21. API with NestJS #21. An introduction to CQRS
- 22. API with NestJS #22. Storing JSON with PostgreSQL and TypeORM
- 23. API with NestJS #23. Implementing in-memory cache to increase the performance
- 24. API with NestJS #24. Cache with Redis. Running the app in a Node.js cluster
- 25. API with NestJS #25. Sending scheduled emails with cron and Nodemailer
- 26. API with NestJS #26. Real-time chat with WebSockets
- 27. API with NestJS #27. Introduction to GraphQL. Queries, mutations, and authentication
- 28. API with NestJS #28. Dealing in the N + 1 problem in GraphQL
- 29. API with NestJS #29. Real-time updates with GraphQL subscriptions
- 30. API with NestJS #30. Scalar types in GraphQL
- 31. API with NestJS #31. Two-factor authentication
- 32. API with NestJS #32. Introduction to Prisma with PostgreSQL
- 33. API with NestJS #33. Managing PostgreSQL relationships with Prisma
- 34. API with NestJS #34. Handling CPU-intensive tasks with queues
- 35. API with NestJS #35. Using server-side sessions instead of JSON Web Tokens
- 36. API with NestJS #36. Introduction to Stripe with React
- 37. API with NestJS #37. Using Stripe to save credit cards for future use
- 38. API with NestJS #38. Setting up recurring payments via subscriptions with Stripe
- 39. API with NestJS #39. Reacting to Stripe events with webhooks
- 40. API with NestJS #40. Confirming the email address
- 41. API with NestJS #41. Verifying phone numbers and sending SMS messages with Twilio
- 42. API with NestJS #42. Authenticating users with Google
- 43. API with NestJS #43. Introduction to MongoDB
- 44. API with NestJS #44. Implementing relationships with MongoDB
- 45. API with NestJS #45. Virtual properties with MongoDB and Mongoose
- 46. API with NestJS #46. Managing transactions with MongoDB and Mongoose
- 47. API with NestJS #47. Implementing pagination with MongoDB and Mongoose
- 48. API with NestJS #48. Definining indexes with MongoDB and Mongoose
- 49. API with NestJS #49. Updating with PUT and PATCH with MongoDB and Mongoose
- 50. API with NestJS #50. Introduction to logging with the built-in logger and TypeORM
- 51. API with NestJS #51. Health checks with Terminus and Datadog
- 52. API with NestJS #52. Generating documentation with Compodoc and JSDoc
- 53. API with NestJS #53. Implementing soft deletes with PostgreSQL and TypeORM
- 54. API with NestJS #54. Storing files inside a PostgreSQL database
- 55. API with NestJS #55. Uploading files to the server
- 56. API with NestJS #56. Authorization with roles and claims
- 57. API with NestJS #57. Composing classes with the mixin pattern
- 58. API with NestJS #58. Using ETag to implement cache and save bandwidth
- 59. API with NestJS #59. Introduction to a monorepo with Lerna and Yarn workspaces
- 60. API with NestJS #60. The OpenAPI specification and Swagger
- 61. API with NestJS #61. Dealing with circular dependencies
- 62. API with NestJS #62. Introduction to MikroORM with PostgreSQL
- 63. API with NestJS #63. Relationships with PostgreSQL and MikroORM
- 64. API with NestJS #64. Transactions with PostgreSQL and MikroORM
- 65. API with NestJS #65. Implementing soft deletes using MikroORM and filters
- 66. API with NestJS #66. Improving PostgreSQL performance with indexes using MikroORM
- 67. API with NestJS #67. Migrating to TypeORM 0.3
- 68. API with NestJS #68. Interacting with the application through REPL
- 69. API with NestJS #69. Database migrations with TypeORM
- 70. API with NestJS #70. Defining dynamic modules
- 71. API with NestJS #71. Introduction to feature flags
- 72. API with NestJS #72. Working with PostgreSQL using raw SQL queries
- 73. API with NestJS #73. One-to-one relationships with raw SQL queries
- 74. API with NestJS #74. Designing many-to-one relationships using raw SQL queries
- 75. API with NestJS #75. Many-to-many relationships using raw SQL queries
- 76. API with NestJS #76. Working with transactions using raw SQL queries
- 77. API with NestJS #77. Offset and keyset pagination with raw SQL queries
- 78. API with NestJS #78. Generating statistics using aggregate functions in raw SQL
- 79. API with NestJS #79. Implementing searching with pattern matching and raw SQL
- 80. API with NestJS #80. Updating entities with PUT and PATCH using raw SQL queries
- 81. API with NestJS #81. Soft deletes with raw SQL queries
- 82. API with NestJS #82. Introduction to indexes with raw SQL queries
- 83. API with NestJS #83. Text search with tsvector and raw SQL
- 84. API with NestJS #84. Implementing filtering using subqueries with raw SQL
- 85. API with NestJS #85. Defining constraints with raw SQL
- 86. API with NestJS #86. Logging with the built-in logger when using raw SQL
- 87. API with NestJS #87. Writing unit tests in a project with raw SQL
- 88. API with NestJS #88. Testing a project with raw SQL using integration tests
- 89. API with NestJS #89. Replacing Express with Fastify
- 90. API with NestJS #90. Using various types of SQL joins
- 91. API with NestJS #91. Dockerizing a NestJS API with Docker Compose
- 92. API with NestJS #92. Increasing the developer experience with Docker Compose
- 93. API with NestJS #93. Deploying a NestJS app with Amazon ECS and RDS
- 94. API with NestJS #94. Deploying multiple instances on AWS with a load balancer
- 95. API with NestJS #95. CI/CD with Amazon ECS and GitHub Actions
- 96. API with NestJS #96. Running unit tests with CI/CD and GitHub Actions
- 97. API with NestJS #97. Introduction to managing logs with Amazon CloudWatch
- 98. API with NestJS #98. Health checks with Terminus and Amazon ECS
- 99. API with NestJS #99. Scaling the number of application instances with Amazon ECS
- 100. API with NestJS #100. The HTTPS protocol with Route 53 and AWS Certificate Manager
- 101. API with NestJS #101. Managing sensitive data using the AWS Secrets Manager
- 102. API with NestJS #102. Writing unit tests with Prisma
- 103. API with NestJS #103. Integration tests with Prisma
- 104. API with NestJS #104. Writing transactions with Prisma
- 105. API with NestJS #105. Implementing soft deletes with Prisma and middleware
- 106. API with NestJS #106. Improving performance through indexes with Prisma
- 107. API with NestJS #107. Offset and keyset pagination with Prisma
- 108. API with NestJS #108. Date and time with Prisma and PostgreSQL
- 109. API with NestJS #109. Arrays with PostgreSQL and Prisma
- 110. API with NestJS #110. Managing JSON data with PostgreSQL and Prisma
- 111. API with NestJS #111. Constraints with PostgreSQL and Prisma
- 112. API with NestJS #112. Serializing the response with Prisma
- 113. API with NestJS #113. Logging with Prisma
- 114. API with NestJS #114. Modifying data using PUT and PATCH methods with Prisma
- 115. API with NestJS #115. Database migrations with Prisma
- 116. API with NestJS #116. REST API versioning
- 117. API with NestJS #117. CORS – Cross-Origin Resource Sharing
- 118. API with NestJS #118. Uploading and streaming videos
- 119. API with NestJS #119. Type-safe SQL queries with Kysely and PostgreSQL
- 120. API with NestJS #120. One-to-one relationships with the Kysely query builder
- 121. API with NestJS #121. Many-to-one relationships with PostgreSQL and Kysely
With Stripe, we can build advanced custom payment flows. In this article, we continue looking into it and save credit cards for future use. To do that, we need to have a more thorough understanding of how we can integrate Stripe with our system.
Saving cards with setup intents
Our goal in this article is to have payment credentials saved and ready for future payments. Imagine creating a website for an online shop. We could require the users to provide the credit card details every time they make an order, but that wouldn’t be the best user experience. Instead, we can save the credit card details in Stripe and use them later.
To get us through this process, Stripe uses setup intents. We can set up a payment method without creating a charge and assign it to a customer.
We need to add the above functionality to the StripeService that we’ve created in the previous part of this series.
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 |
import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import Stripe from 'stripe'; @Injectable() export default class StripeService { private stripe: Stripe; constructor( private configService: ConfigService ) { this.stripe = new Stripe(configService.get('STRIPE_SECRET_KEY'), { apiVersion: '2020-08-27', }); } public async attachCreditCard(paymentMethodId: string, customerId: string) { return this.stripe.setupIntents.create({ customer: customerId, payment_method: paymentMethodId, }) } // ... } |
To use the above method, let’s create the CreditCardsController.
creditCards.controller.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import { Body, Controller, Post, Req, UseGuards } from '@nestjs/common'; import JwtAuthenticationGuard from '../authentication/jwt-authentication.guard'; import RequestWithUser from '../authentication/requestWithUser.interface'; import StripeService from '../stripe/stripe.service'; import AddCreditCardDto from './dto/addCreditCardDto'; @Controller('credit-cards') export default class CreditCardsController { constructor( private readonly stripeService: StripeService ) {} @Post() @UseGuards(JwtAuthenticationGuard) async addCreditCard(@Body() creditCard: AddCreditCardDto, @Req() request: RequestWithUser) { return this.stripeService.attachCreditCard(creditCard.paymentMethodId, request.user.stripeCustomerId); } } |
creditCards.controller.ts
1 2 3 4 5 6 7 8 9 |
import { IsString, IsNotEmpty } from 'class-validator'; export class AddCreditCardDto { @IsString() @IsNotEmpty() paymentMethodId: string; } export default AddCreditCardDto; |
As you can see, we expect the users to send us the paymentMethodId when adding the credit card. To achieve that on the frontend side with React, we need to do it the same way as in the previous article. This time, we call the /credit-cards endpoint, though.
usePaymentForm.tsx
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 |
import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js'; import { FormEvent } from 'react'; function usePaymentForm() { const stripe = useStripe(); const elements = useElements(); const getPaymentMethodId = async () => { const cardElement = elements?.getElement(CardElement); if (!stripe || !elements || !cardElement) { return; } const stripeResponse = await stripe?.createPaymentMethod({ type: 'card', card: cardElement }); const { error, paymentMethod } = stripeResponse; if (error || !paymentMethod) { return; } return paymentMethod.id; } const handleSubmit = async (event: FormEvent) => { event.preventDefault(); const paymentMethodId = await getPaymentMethodId(); if (!paymentMethodId) { return; } fetch(`${process.env.REACT_APP_API_URL}/credit-cards`, { method: 'POST', body: JSON.stringify(({ paymentMethodId, })), credentials: 'include', headers: { 'Content-Type': 'application/json' }, }) }; return { handleSubmit } } export default usePaymentForm; |
There is one additional thing we should do above, though. In the previous part of this series, we’ve used the confirm: true parameter. Using it when saving a card would attempt to confirm it immediately.
The above solution might work, but it is not guaranteed. The bank might require the user to perform some additional authentication on the frontend. To handle it, let’s use the client_secret Stripe provides us with when creating the payment intent.
usePaymentForm.tsx
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 |
import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js'; import { FormEvent } from 'react'; function usePaymentForm() { const stripe = useStripe(); const elements = useElements(); // ... const handleSubmit = async (event: FormEvent) => { event.preventDefault(); const paymentMethodId = await getPaymentMethodId(); if (!paymentMethodId) { return; } const response = await fetch(`${process.env.REACT_APP_API_URL}/credit-cards`, { method: 'POST', body: JSON.stringify(({ paymentMethodId, })), credentials: 'include', headers: { 'Content-Type': 'application/json' }, }) const responseJson = await response.json(); const clientSecret = responseJson.client_secret; stripe?.confirmCardSetup(clientSecret); }; return { handleSubmit } } export default usePaymentForm; |
When we call the stripe.confirmCardSetup on the frontend, Stripe has a chance to carry out any actions needed to confirm the card setup.
Listing saved credit cards
Another important part of the flow is listing the credit cards that the user saved. To implement it, let’s add a new method to the StripeService.
stripe.service.ts
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 |
import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import Stripe from 'stripe'; @Injectable() export default class StripeService { private stripe: Stripe; constructor( private configService: ConfigService ) { this.stripe = new Stripe(configService.get('STRIPE_SECRET_KEY'), { apiVersion: '2020-08-27', }); } public async listCreditCards(customerId: string) { return this.stripe.paymentMethods.list({ customer: customerId, type: 'card', }); } // ... } |
To use it, we also need to modify the CreditCardsController:
creditCards.controller.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import { Body, Controller, Post, Req, UseGuards, Get } from '@nestjs/common'; import JwtAuthenticationGuard from '../authentication/jwt-authentication.guard'; import RequestWithUser from '../authentication/requestWithUser.interface'; import StripeService from '../stripe/stripe.service'; import AddCreditCardDto from './dto/addCreditCardDto'; @Controller('credit-cards') export default class CreditCardsController { constructor( private readonly stripeService: StripeService ) {} @Post() @UseGuards(JwtAuthenticationGuard) async addCreditCard(@Body() creditCard: AddCreditCardDto, @Req() request: RequestWithUser) { return this.stripeService.attachCreditCard(creditCard.paymentMethodId, request.user.stripeCustomerId); } @Get() @UseGuards(JwtAuthenticationGuard) async getCreditCards(@Req() request: RequestWithUser) { return this.stripeService.listCreditCards(request.user.stripeCustomerId); } } |
A significant thing to notice here is that Stripe doesn’t allow us to view all of the card’s details. For example, we can see only the last four digits of the card number, and we can’t access the CVV code.
There is a great chance that our application doesn’t need to expose that much data about the credit cards. If that’s the case for you, feel free to modify the Stripe’s response in the /credit-cards endpoint.
By default, the paymentMethods.list returns up to 10 credit cards. If you need more, you need to use pagination with the limit and starting_after properties. For details, check the documentation.
If you want to know more about pagination in general, take a look at API with NestJS #17. Offset and keyset pagination with PostgreSQL and TypeORM
Charging using saved cards
The last part of the flow is charging the customer using the saved card. Let’s use the logic from the previous article, but with the off_session parameter.
1 2 3 4 5 6 7 8 9 10 |
public async charge(amount: number, paymentMethodId: string, customerId: string) { return this.stripe.paymentIntents.create({ amount, customer: customerId, payment_method: paymentMethodId, currency: this.configService.get('STRIPE_CURRENCY'), off_session: true, confirm: true }) } |
By setting off_session to true, we indicate that it occurs without the direct involvement of the customer with the use of previously collected credit card information. Let’s use the above logic in the ChargeController.
creditCards.controller.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import { Body, Controller, Post, Req, UseGuards } from '@nestjs/common'; import JwtAuthenticationGuard from '../authentication/jwt-authentication.guard'; import CreateChargeDto from './dto/createCharge.dto'; import RequestWithUser from '../authentication/requestWithUser.interface'; import StripeService from '../stripe/stripe.service'; @Controller('charge') export default class ChargeController { constructor( private readonly stripeService: StripeService ) {} @Post() @UseGuards(JwtAuthenticationGuard) async createCharge(@Body() charge: CreateChargeDto, @Req() request: RequestWithUser) { return this.stripeService.charge(charge.amount, charge.paymentMethodId, request.user.stripeCustomerId); } } |
An important change above is that we return the response from the stripeService.charge method. This is because setting confirm to true might work, but there is a chance that the bank will require additional authentication. We need to prepare for this scenario on the frontend.
The above controller returns all of the data returned by Stripe. Feel free to remove unused data and send only the client_secret and the status to the frontend, for example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
const chargeResponse = await fetch(`${process.env.REACT_APP_API_URL}/charge`, { method: 'POST', body: JSON.stringify(({ paymentMethodId, amount: 100 })), credentials: 'include', headers: { 'Content-Type': 'application/json' }, }) const chargeResponseJson = await chargeResponse.json(); if (chargeResponseJson.status !== 'succeeded') { const secret = chargeResponseJson.client_secret; await stripe?.confirmCardPayment(secret); } |
By doing the above, we try to confirm the charge immediately. If it fails, we call the stripe.confirmCardPayment method. It might require the customer to perform additional steps to complete the payment, depending on the bank. Stripe walks the user through this process.
Testing our integration
Stripe provides a set of credit cards for testing that always work. With them, the confirmation on the backend will succeed without issues.
To confirm that our integration works well for other cases, we can use the 3D secure test card numbers, for example. They require us to properly handle confirmation when saving the credit cards and using them later.
We can also test for specific responses, and errors with credit card numbers Stripe prepared exactly for those cases.
Summary
In this article, we’ve continued integrating Stripe with our NestJS API. We’ve learned how to save credit cards for future use. We’ve also made payments with them and learned how to confirm them on the frontend side. By doing so, we’ve learned even more about how to implement Stripe into our application. There is still quite a bit to learn, so stay tuned!
Hey, thanks for your lessons.
Can you provide the react repo link?
I followed the tutorial. It works fine to add a credit-card but. using Get creditcards the data object returns empty. Is the API changed? I tried adding same package ‘stripe’ as in original code.
{
“object”: “list”,
“data”: [],
“has_more”: false,
“url”: “/v1/payment_methods”
}