DEV Community

Munaf Badarpura
Munaf Badarpura

Posted on

Integrating Stripe Payments in Spring Boot: Step-by-Step Beginner’s Guide (2025)

Integrating payment processing into your Spring Boot application can seem daunting, but with Stripe's robust API and Spring Boot's flexibility, it’s a manageable task. In this guide, we'll walk through the process of integrating Stripe payments into a Spring Boot application, focusing on a booking system example. We'll cover setting up Stripe, initiating a payment session, handling webhooks, and confirming payments.

Prerequisites

  1. A Stripe account with API keys (secret key and webhook secret).
  2. A Spring Boot project set up with dependencies like spring-boot-starter-web and stripe-java.
  3. Basic knowledge of Spring Boot, REST APIs, and Java.
  4. Add the Stripe Java library to your pom.xml:
<dependency>
    <groupId>com.stripe</groupId>
    <artifactId>stripe-java</artifactId>
    <version>25.6.0</version>
</dependency>
Enter fullscreen mode Exit fullscreen mode

Step 1: Configuring Stripe in Spring Boot

To use Stripe's API, you need to set up your Stripe secret key in the application. This key authenticates your API requests.

In your application.properties or application.yml, add:

stripe.secret.key=sk_test_your_stripe_secret_key
stripe.webhook.secret=whsec_your_webhook_secret
Enter fullscreen mode Exit fullscreen mode

Create a configuration class to initialize the Stripe API with the secret key:

import com.stripe.Stripe;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

@Configuration
public class StripeConfig {

    @Value("${stripe.secret.key}")
    private String stripeSecretKey;

    @PostConstruct
    public void init() {
        Stripe.apiKey = stripeSecretKey; // Set up Stripe API key
    }
}
Enter fullscreen mode Exit fullscreen mode

This ensures the Stripe API key is set when the application starts.

Step 2: Initiating a Payment Session

To process payments, you’ll create a Stripe Checkout Session, which redirects users to a Stripe-hosted payment page. Below is an example of initiating a payment for a booking.

Controller for Initiating Payment

Create a REST endpoint to initiate a payment for a booking:

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/bookings")
public class BookingController {

    private final BookingService bookingService;

    public BookingController(BookingService bookingService) {
        this.bookingService = bookingService;
    }

    @PostMapping("/{bookingId}/payments")
    public ResponseEntity<BookingPaymentInitResponseDto> initiateBookingPayment(@PathVariable Long bookingId) {
        BookingPaymentInitResponseDto response = new BookingPaymentInitResponseDto(bookingService.initiateBookingPayment(bookingId));
        return new ResponseEntity<>(response, HttpStatus.OK);
    }
}
Enter fullscreen mode Exit fullscreen mode

The BookingPaymentInitResponseDto is a simple data transfer object (DTO) to return the Stripe session URL:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class BookingPaymentInitResponseDto {
    private String sessionUrl;
}
Enter fullscreen mode Exit fullscreen mode

Service Logic for Payment Initiation

In the service layer, validate the booking, check user authorization, and create a Stripe Checkout Session:

import com.stripe.exception.StripeException;
import com.stripe.model.Customer;
import com.stripe.model.Session;
import com.stripe.param.CustomerCreateParams;
import com.stripe.param.SessionCreateParams;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Slf4j
public class BookingService {

    private final BookingRepository bookingRepository;
    private final InventoryRepository inventoryRepository;
    private final String frontendUrl;

    public BookingService(BookingRepository bookingRepository, InventoryRepository inventoryRepository, @Value("${frontend.url}") String frontendUrl) {
        this.bookingRepository = bookingRepository;
        this.inventoryRepository = inventoryRepository;
        this.frontendUrl = frontendUrl;
    }

    @Transactional(noRollbackFor = BookingExpiredException.class)
    public String initiateBookingPayment(Long bookingId) {
        Booking booking = bookingRepository.findById(bookingId)
                .orElseThrow(() -> new ResourceNotFoundException("Booking Not Found With Id: " + bookingId));

        User user = getCurrentUser(); // Assume this retrieves the authenticated user

        if (!user.equals(booking.getUser())) {
            throw new UnAuthorisedException("Booking Does Not Belong To This User With Id: " + user.getId());
        }

        if (hasBookingExpired(booking.getCreatedAt())) {
            inventoryRepository.expireBooking(booking.getRoom().getId(),
                    booking.getCheckInDate(),
                    booking.getCheckOutDate(),
                    booking.getNumberOfRooms());
            booking.setBookingStatus(BookingStatus.EXPIRED);
            bookingRepository.save(booking);
            throw new BookingExpiredException("Booking Has Been Already Expired");
        }

        String sessionUrl = getCheckoutSession(booking, frontendUrl + "/payment/success", frontendUrl + "/payment/failure");
        booking.setBookingStatus(BookingStatus.PAYMENT_PENDING);
        bookingRepository.save(booking);

        return sessionUrl;
    }

    public String getCheckoutSession(Booking booking, String successUrl, String failureUrl) {
        log.info("Creating session for booking with Id: {}", booking.getId());
        User user = getCurrentUser();

        try {
            CustomerCreateParams customerParams = CustomerCreateParams.builder()
                    .setName(user.getName())
                    .setEmail(user.getEmail())
                    .build();
            Customer customer = Customer.create(customerParams);

            SessionCreateParams sessionParams = SessionCreateParams.builder()
                    .setMode(SessionCreateParams.Mode.PAYMENT)
                    .setBillingAddressCollection(SessionCreateParams.BillingAddressCollection.REQUIRED)
                    .setCustomer(customer.getId())
                    .setSuccessUrl(successUrl)
                    .setCancelUrl(failureUrl)
                    .addLineItem(
                            SessionCreateParams.LineItem.builder()
                                    .setQuantity(Long.valueOf(booking.getNumberOfRooms()))
                                    .setPriceData(
                                            SessionCreateParams.LineItem.PriceData.builder()
                                                    .setCurrency("inr")
                                                    .setUnitAmount(booking.getAmount().multiply(BigDecimal.valueOf(100)).longValue())
                                                    .setProductData(
                                                            SessionCreateParams.LineItem.PriceData.ProductData.builder()
                                                                    .setName(booking.getHotel().getName() + " : " + booking.getRoom().getType())
                                                                    .setDescription("Booking ID: " + booking.getId())
                                                                    .build()
                                                    )
                                                    .build()
                                    )
                                    .build()
                    )
                    .build();

            Session session = Session.create(sessionParams);
            booking.setPaymentSessionId(session.getId());
            bookingRepository.save(booking);

            log.info("Session created successfully for booking with Id: {}", booking.getId());
            return session.getUrl();
        } catch (StripeException e) {
            throw new RuntimeException("Failed to create Stripe session", e);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This code:

  1. Validates the booking and user.
  2. Checks if the booking has expired.
  3. Creates a Stripe customer and a checkout session with details like the hotel name, room type, and amount (in INR, multiplied by 100 for Stripe’s cent-based system).
  4. Updates the booking status to PAYMENT_PENDING and saves the session ID.

Step 3: Handling Stripe Webhooks

Stripe uses webhooks to notify your application of payment events, such as a completed checkout session. Set up a webhook endpoint to handle these events.

Webhook Controller

import com.stripe.exception.SignatureVerificationException;
import com.stripe.model.Event;
import com.stripe.net.Webhook;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/webhook")
public class StripeWebhookController {

    private final BookingService bookingService;
    private final String endpointSecret;

    public StripeWebhookController(BookingService bookingService, @Value("${stripe.webhook.secret}") String endpointSecret) {
        this.bookingService = bookingService;
        this.endpointSecret = endpointSecret;
    }

    @PostMapping("/payment")
    public ResponseEntity<Void> capturePayments(@RequestBody String payload, @RequestHeader("Stripe-Signature") String sigHeader) {
        try {
            Event event = Webhook.constructEvent(payload, sigHeader, endpointSecret);
            bookingService.capturePayment(event);
            return ResponseEntity.noContent().build();
        } catch (SignatureVerificationException e) {
            throw new RuntimeException("Invalid webhook signature", e);
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Webhook Handling Logic

Add the webhook handling logic to the BookingService:

import com.stripe.model.Session;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Slf4j
public class BookingService {

    // Other methods as above...

    @Transactional
    public void capturePayment(Event event) {
        if ("checkout.session.completed".equals(event.getType())) {
            Session session = (Session) event.getDataObjectDeserializer().getObject().orElse(null);
            if (session == null) return;

            String sessionId = session.getId();
            Booking booking = bookingRepository.findByPaymentSessionId(sessionId)
                    .orElseThrow(() -> new ResourceNotFoundException("Booking not found for session Id: " + sessionId));

            booking.setBookingStatus(BookingStatus.CONFIRMED);
            bookingRepository.save(booking);

            List<Inventory> lockReservedInventory = inventoryRepository.findAndLockReservedInventory(
                    booking.getRoom().getId(),
                    booking.getCheckInDate(),
                    booking.getCheckOutDate(),
                    booking.getNumberOfRooms());

            inventoryRepository.confirmBooking(
                    booking.getRoom().getId(),
                    booking.getCheckInDate(),
                    booking.getCheckOutDate(),
                    booking.getNumberOfRooms());

            log.info("Successfully confirmed the booking for Booking Id: {}", booking.getId());
        } else {
            log.warn("Unhandled event type: {}", event.getType());
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

This code:

  1. Verifies the webhook event using the Stripe webhook secret.
  2. Processes the checkout.session.completed event to confirm the booking.
  3. Updates the booking status to CONFIRMED and adjusts inventory (e.g., decreases reserved rooms and increases booked rooms).

Step 4: Testing the Integration

  1. Set Up Webhooks Locally: Use a tool like ngrok to expose your local server to Stripe’s webhook events. Configure the webhook URL in the Stripe Dashboard (e.g., https://your-ngrok-url/webhook/payment).
  2. Test Payment Flow:
    • Create a booking and initiate a payment via the /api/bookings/{bookingId}/payments endpoint.
    • Use Stripe’s test card (e.g., 4242 4242 4242 4242) to complete the payment.
    • Verify that the webhook updates the booking status to CONFIRMED.
  3. Handle Errors: Test edge cases like expired bookings or invalid payments to ensure proper exception handling.

Read complete blog here : https://www.codingshuttle.com/blogs/integrating-stripe-payments-in-spring-boot-step-by-step-beginner-s-guide-2025/

Top comments (0)