package com.paymentlink.service; import com.paymentlink.model.entity.PaymentLink; import com.paymentlink.model.entity.PaymentLinkItem; import com.paymentlink.model.entity.Product; import com.paymentlink.repository.PaymentLinkRepository; import com.paymentlink.reservation.ReservationService; import com.paymentlink.util.IdGenerator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Optional; @Service public class PaymentLinkService { private static final Logger logger = LoggerFactory.getLogger(PaymentLinkService.class); private final PaymentLinkRepository paymentLinkRepository; private final ProductService productService; private final TaxService taxService; private final ShippingService shippingService; private final ReservationService reservationService; private final NodeIdService nodeIdService; public PaymentLinkService(PaymentLinkRepository paymentLinkRepository, ProductService productService, TaxService taxService, ShippingService shippingService, ReservationService reservationService, NodeIdService nodeIdService) { this.paymentLinkRepository = paymentLinkRepository; this.productService = productService; this.taxService = taxService; this.shippingService = shippingService; this.reservationService = reservationService; this.nodeIdService = nodeIdService; } /** * Create a payment link */ @Transactional public PaymentLink createPaymentLink(List items, String customerEmail, String customerName, String customerPhone, String shippingAddress, String shippingCity, String shippingState, String shippingZip, String shippingCountry, String shippingMethod, String sessionId) { logger.info("Creating payment link for customer: {}", customerEmail); // Validate stock and calculate totals long subtotal = 0; String currency = "USD"; for (PaymentLinkItem item : items) { Optional productOpt = productService.getProductByIdRaw(item.getProductId()); if (productOpt.isEmpty()) { throw new IllegalArgumentException("Product not found: " + item.getProductId()); } Product product = productOpt.get(); currency = product.getCurrency(); // Check stock availability if (product.getStock() != null) { int reservedByOthers = reservationService.getReservedCountExcludingSession( product.getId(), sessionId ); int effectiveStock = product.getStock() - reservedByOthers; if (effectiveStock < item.getQuantity()) { throw new IllegalStateException( String.format("Insufficient stock for %s. Only %d available.", product.getName(), effectiveStock) ); } } // Set item details item.setProductName(product.getName()); item.setPrice(product.getPrice()); subtotal += product.getPrice() * item.getQuantity(); } // Calculate shipping long shippingCost = shippingService.calculateShippingCost(shippingMethod, shippingCountry); // Calculate tax long taxAmount = taxService.calculateTax(subtotal, shippingCountry, shippingState); // Calculate total long total = subtotal + shippingCost + taxAmount; // Create payment link PaymentLink paymentLink = PaymentLink.builder() .linkId(IdGenerator.generatePaymentLinkId()) .status("pending") .subtotal(subtotal) .amount(total) .shippingCost(shippingCost) .taxAmount(taxAmount) .currency(currency) .customerEmail(customerEmail) .customerName(customerName) .customerPhone(customerPhone) .shippingAddress(shippingAddress) .shippingCity(shippingCity) .shippingState(shippingState) .shippingZip(shippingZip) .shippingCountry(shippingCountry) .shippingMethod(shippingMethod) .nodeId(nodeIdService.getNodeId()) .build(); // Add items for (PaymentLinkItem item : items) { paymentLink.addItem(item); } // Save payment link paymentLink = paymentLinkRepository.save(paymentLink); logger.info("Created payment link: {}", paymentLink.getLinkId()); return paymentLink; } /** * Get payment link by ID */ public Optional getPaymentLinkById(String linkId) { return paymentLinkRepository.findByLinkIdWithItems(linkId); } /** * Update payment link status */ @Transactional public Optional updateStatus(String linkId, String status) { return paymentLinkRepository.findByLinkId(linkId).map(link -> { link.setStatus(status); return paymentLinkRepository.save(link); }); } /** * Associate payment link with Stripe payment intent */ @Transactional public Optional setStripePaymentIntent(String linkId, String paymentIntentId) { return paymentLinkRepository.findByLinkId(linkId).map(link -> { link.setStripePaymentIntentId(paymentIntentId); return paymentLinkRepository.save(link); }); } /** * Associate payment link with order */ @Transactional public Optional setOrderId(String linkId, String orderId) { return paymentLinkRepository.findByLinkId(linkId).map(link -> { link.setOrderId(orderId); return paymentLinkRepository.save(link); }); } /** * Complete payment link (mark as paid) */ @Transactional public void completePaymentLink(String linkId, String orderId) { Optional linkOpt = paymentLinkRepository.findByLinkId(linkId); if (linkOpt.isEmpty()) { throw new IllegalArgumentException("Payment link not found: " + linkId); } PaymentLink link = linkOpt.get(); link.setStatus("paid"); link.setOrderId(orderId); paymentLinkRepository.save(link); logger.info("Completed payment link: {} -> order: {}", linkId, orderId); } }