Files

File: src/main/java/com/paymentlink/controller/api/PaymentLinkApiController.java

1 package com.paymentlink.controller.api;
2
3 import com.paymentlink.model.entity.Order;
4 import com.paymentlink.model.entity.OrderItem;
5 import com.paymentlink.model.entity.PaymentLink;
6 import com.paymentlink.model.entity.PaymentLinkItem;
7 import com.paymentlink.model.entity.Product;
8 import com.paymentlink.service.OrderService;
9 import com.paymentlink.service.PaymentLinkService;
10 import com.paymentlink.service.ProductService;
11 import com.paymentlink.service.StripePaymentService;
12 import com.stripe.exception.StripeException;
13 import com.stripe.model.PaymentIntent;
14 import org.springframework.http.HttpStatus;
15 import org.springframework.http.ResponseEntity;
16 import org.springframework.web.bind.annotation.*;
17
18 import java.util.ArrayList;
19 import java.util.HashMap;
20 import java.util.List;
21 import java.util.Map;
22
23 @RestController
24 @RequestMapping("/api/payment-links")
25 public class PaymentLinkApiController {
26
27 private final PaymentLinkService paymentLinkService;
28 private final OrderService orderService;
29 private final ProductService productService;
30 private final StripePaymentService stripePaymentService;
31
32 public PaymentLinkApiController(PaymentLinkService paymentLinkService,
33 OrderService orderService,
34 ProductService productService,
35 StripePaymentService stripePaymentService) {
36 this.paymentLinkService = paymentLinkService;
37 this.orderService = orderService;
38 this.productService = productService;
39 this.stripePaymentService = stripePaymentService;
40 }
41
42 /**
43 * POST /api/payment-links/create - Create payment link
44 */
45 @PostMapping("/create")
46 public ResponseEntity<Map<String, Object>> createPaymentLink(
47 @RequestHeader(value = "x-session-id", required = false) String sessionId,
48 @RequestBody Map<String, Object> request) {
49
50 try {
51 // Parse request
52 @SuppressWarnings("unchecked")
53 List<Map<String, Object>> itemsData = (List<Map<String, Object>>) request.get("items");
54 @SuppressWarnings("unchecked")
55 Map<String, String> customerData = (Map<String, String>) request.get("customerInfo");
56 @SuppressWarnings("unchecked")
57 Map<String, String> shippingData = (Map<String, String>) request.get("shippingInfo");
58
59 // Convert to PaymentLinkItems
60 List<PaymentLinkItem> items = itemsData.stream().map(item -> {
61 PaymentLinkItem linkItem = new PaymentLinkItem();
62 linkItem.setProductId(toLong(item.get("productId")));
63 linkItem.setQuantity(toInteger(item.get("quantity")));
64 return linkItem;
65 }).toList();
66
67 // Create payment link
68 PaymentLink link = paymentLinkService.createPaymentLink(
69 items,
70 customerData.get("email"),
71 customerData.get("name"),
72 customerData.get("phone"),
73 shippingData.get("address"),
74 shippingData.get("city"),
75 shippingData.get("state"),
76 shippingData.get("zip"),
77 shippingData.get("country"),
78 shippingData.get("shippingMethod"),
79 sessionId
80 );
81
82 Map<String, Object> response = new HashMap<>();
83 response.put("success", true);
84 response.put("linkId", link.getLinkId());
85 response.put("paymentLink", link);
86 return ResponseEntity.status(HttpStatus.CREATED).body(response);
87
88 } catch (IllegalStateException | IllegalArgumentException e) {
89 Map<String, Object> error = new HashMap<>();
90 error.put("success", false);
91 error.put("error", e.getMessage());
92 return ResponseEntity.badRequest().body(error);
93
94 } catch (Exception e) {
95 Map<String, Object> error = new HashMap<>();
96 error.put("success", false);
97 error.put("error", e.getMessage());
98 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
99 }
100 }
101
102 /**
103 * GET /api/payment-links/{linkId} - Get payment link details
104 */
105 @GetMapping("/{linkId}")
106 public ResponseEntity<Map<String, Object>> getPaymentLink(@PathVariable String linkId) {
107 try {
108 return paymentLinkService.getPaymentLinkById(linkId)
109 .map(link -> {
110 // Get product details for each item
111 List<Map<String, Object>> products = new ArrayList<>();
112 for (PaymentLinkItem item : link.getItems()) {
113 productService.getProductByIdRaw(item.getProductId()).ifPresent(product -> {
114 Map<String, Object> productData = new HashMap<>();
115 productData.put("id", product.getId());
116 productData.put("name", product.getName());
117 productData.put("price", item.getPrice());
118 productData.put("quantity", item.getQuantity());
119 products.add(productData);
120 });
121 }
122
123 Map<String, Object> response = new HashMap<>();
124 response.put("success", true);
125 response.put("paymentLink", link);
126 response.put("products", products);
127 return ResponseEntity.ok(response);
128 })
129 .orElseGet(() -> {
130 Map<String, Object> error = new HashMap<>();
131 error.put("success", false);
132 error.put("error", "Payment link not found");
133 return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
134 });
135
136 } catch (Exception e) {
137 Map<String, Object> error = new HashMap<>();
138 error.put("success", false);
139 error.put("error", e.getMessage());
140 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
141 }
142 }
143
144 /**
145 * POST /api/payment-links/{linkId}/process - Initialize Stripe payment
146 */
147 @PostMapping("/{linkId}/process")
148 public ResponseEntity<Map<String, Object>> processPayment(@PathVariable String linkId) {
149 try {
150 PaymentLink link = paymentLinkService.getPaymentLinkById(linkId)
151 .orElseThrow(() -> new IllegalArgumentException("Payment link not found"));
152
153 if (!"pending".equals(link.getStatus())) {
154 throw new IllegalStateException("Payment link is not in pending status");
155 }
156
157 // Create Stripe PaymentIntent
158 PaymentIntent paymentIntent = stripePaymentService.createPaymentIntent(
159 link.getAmount(),
160 link.getCurrency()
161 );
162
163 // Save payment intent ID to link
164 paymentLinkService.setStripePaymentIntent(linkId, paymentIntent.getId());
165
166 Map<String, Object> response = new HashMap<>();
167 response.put("success", true);
168 response.put("clientSecret", paymentIntent.getClientSecret());
169 return ResponseEntity.ok(response);
170
171 } catch (StripeException e) {
172 Map<String, Object> error = new HashMap<>();
173 error.put("success", false);
174 error.put("error", "Stripe error: " + e.getMessage());
175 return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
176
177 } catch (Exception e) {
178 Map<String, Object> error = new HashMap<>();
179 error.put("success", false);
180 error.put("error", e.getMessage());
181 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
182 }
183 }
184
185 /**
186 * POST /api/payment-links/{linkId}/complete - Complete payment
187 */
188 @PostMapping("/{linkId}/complete")
189 public ResponseEntity<Map<String, Object>> completePayment(
190 @PathVariable String linkId,
191 @RequestHeader(value = "x-session-id", required = false) String sessionId,
192 @RequestBody Map<String, String> request) {
193
194 try {
195 String paymentIntentId = request.get("paymentIntentId");
196
197 PaymentLink link = paymentLinkService.getPaymentLinkById(linkId)
198 .orElseThrow(() -> new IllegalArgumentException("Payment link not found"));
199
200 // Verify payment intent
201 PaymentIntent paymentIntent = stripePaymentService.retrievePaymentIntent(paymentIntentId);
202
203 if (!stripePaymentService.isPaymentSuccessful(paymentIntent)) {
204 throw new IllegalStateException("Payment not successful");
205 }
206
207 // Create order from payment link
208 List<OrderItem> orderItems = link.getItems().stream().map(item -> {
209 OrderItem orderItem = new OrderItem();
210 orderItem.setProductId(item.getProductId());
211 orderItem.setProductName(item.getProductName());
212 orderItem.setQuantity(item.getQuantity());
213 orderItem.setPrice(item.getPrice());
214 return orderItem;
215 }).toList();
216
217 Order order = orderService.createOrder(
218 orderItems,
219 link.getCustomerEmail(),
220 link.getCustomerName(),
221 link.getCustomerPhone(),
222 null,
223 link.getShippingAddress(),
224 link.getShippingCity(),
225 link.getShippingState(),
226 link.getShippingZip(),
227 link.getShippingCountry(),
228 link.getShippingMethod(),
229 sessionId
230 );
231
232 // Complete order
233 orderService.completeOrder(order.getOrderId(), sessionId);
234
235 // Update payment link
236 paymentLinkService.completePaymentLink(linkId, order.getOrderId());
237
238 Map<String, Object> response = new HashMap<>();
239 response.put("success", true);
240 response.put("order", order);
241 return ResponseEntity.ok(response);
242
243 } catch (StripeException e) {
244 Map<String, Object> error = new HashMap<>();
245 error.put("success", false);
246 error.put("error", "Stripe error: " + e.getMessage());
247 return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
248
249 } catch (Exception e) {
250 Map<String, Object> error = new HashMap<>();
251 error.put("success", false);
252 error.put("error", e.getMessage());
253 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
254 }
255 }
256
257 // Helper methods to safely convert Object to Long/Integer
258 private Long toLong(Object value) {
259 if (value == null) return null;
260 if (value instanceof Number) {
261 return ((Number) value).longValue();
262 }
263 if (value instanceof String) {
264 return Long.parseLong((String) value);
265 }
266 throw new IllegalArgumentException("Cannot convert " + value.getClass() + " to Long");
267 }
268
269 private Integer toInteger(Object value) {
270 if (value == null) return null;
271 if (value instanceof Number) {
272 return ((Number) value).intValue();
273 }
274 if (value instanceof String) {
275 return Integer.parseInt((String) value);
276 }
277 throw new IllegalArgumentException("Cannot convert " + value.getClass() + " to Integer");
278 }
279 }
280
281