Careeroza
Real World Example
Payment Gateways
Every Indian checkout (Flipkart, Swiggy, BookMyShow) calls a payment gateway such as Razorpay, PayU or Cashfree from the backend. The flow: create a payment order on the gateway, the user pays in the app, then the backend verifies the result. ShopEase talks to a Razorpay-style API (simplified):
React app ShopEase backend Payment gateway
| POST /api/orders/1001/pay | |
|-------------------------->| POST /v1/orders (amount, receipt)|
| |--------------------------------->|
| |<---- 200 {id:"order_Q7x", ...} --|
|<--- {gatewayOrderId} -----| |
| user pays in checkout UI -------------------------------> |
| POST /api/payments/verify | |
|-------------------------->| verify signature, mark PAID |ShopEase: Payment Client
public record GatewayOrderRequest(long amount, String currency, String receipt) {} // amount in paise
public record GatewayOrder(String id, long amount, String currency, String status) {}
@Service
public class PaymentService {
private static final Logger log = LoggerFactory.getLogger(PaymentService.class);
private final RestClient client;
public PaymentService(RestClient paymentClient) { this.client = paymentClient; }
public GatewayOrder createGatewayOrder(String orderNumber, BigDecimal amountInr) {
long paise = amountInr.movePointRight(2).longValueExact();
GatewayOrder order = client.post()
.uri("/v1/orders")
.header("Idempotency-Key", orderNumber) // safe to retry
.body(new GatewayOrderRequest(paise, "INR", orderNumber))
.retrieve()
.onStatus(HttpStatusCode::isError, (req, res) -> {
throw new PaymentFailedException("Gateway answered " + res.getStatusCode());
})
.body(GatewayOrder.class);
log.info("Created gateway order {} for {}", order.id(), orderNumber);
return order;
}
}
@RestController
@RequestMapping("/api/orders")
class OrderPaymentController {
private final PaymentService payments;
private final OrderService orders;
OrderPaymentController(PaymentService payments, OrderService orders) { this.payments = payments; this.orders = orders; }
@PostMapping("/{orderNumber}/pay")
Map<String, Object> pay(@PathVariable String orderNumber) {
BigDecimal total = orders.totalOf(orderNumber);
GatewayOrder g = payments.createGatewayOrder(orderNumber, total);
return Map.of("orderNumber", orderNumber, "gatewayOrderId", g.id(), "amount", total);
}
}Example
curl -X POST localhost:8080/api/orders/SE-1001/payExample output
{"orderNumber":"SE-1001","gatewayOrderId":"order_Q7xK2mPz81","amount":1249.00}Example
When the gateway is down, the client gets a clean error from the global handler, not a stack trace:
curl -X POST localhost:8080/api/orders/SE-1002/payExample output
{"type":"https://shopease.in/errors/payment-failed","title":"Payment failed","status":402,"detail":"Gateway answered 503 SERVICE_UNAVAILABLE","instance":"/api/orders/SE-1002/pay","errorCode":"PAYMENT_FAILED"}Note: Always send money as an integer in the smallest unit (paise) or as BigDecimal; never use double for amounts.