Careeroza
Real World Example
Razorpay-style Payment Methods at Checkout
Indian checkouts (Razorpay, PhonePe, Paytm) support cards, UPI, net banking, wallets and cash on delivery. The order needs one Payment reference whatever the method, and finance reports sum all payments. ShopEase uses SINGLE_TABLE plus polymorphic behaviour in each subclass.
@Entity
@Table(name = "payments")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "payment_type")
public abstract class Payment extends BaseEntity {
@OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "order_id", unique = true)
private Order order;
private BigDecimal amount;
@Enumerated(EnumType.STRING) private PaymentStatus status = PaymentStatus.PENDING;
public abstract String receiptLine();
}
@Entity @DiscriminatorValue("UPI")
public class UpiPayment extends Payment {
private String upiId;
private String utr; // bank reference number
public String receiptLine() { return "UPI " + upiId + " (UTR " + utr + ")"; }
}
@Entity @DiscriminatorValue("CARD")
public class CardPayment extends Payment {
private String network; private String cardLast4;
public String receiptLine() { return network + " card ending " + cardLast4; }
}
public interface PaymentRepository extends JpaRepository<Payment, Long> {
@Query("""
select type(p) as method, count(p) as count, sum(p.amount) as total
from Payment p
where p.status = com.shopease.payment.PaymentStatus.CAPTURED
and p.createdAt >= :from and p.createdAt < :to
group by type(p)
""")
List<MethodTotal> dailyTotals(@Param("from") Instant from, @Param("to") Instant to);
}Daily Settlement Report
Example
type(p) in JPQL becomes the discriminator column in SQL:
curl "localhost:8080/api/admin/reports/payments?date=2026-03-14"Example output
DEBUG org.hibernate.SQL : select p1_0.payment_type,count(p1_0.id),sum(p1_0.amount) from payments p1_0 where p1_0.status=? and p1_0.created_at>=? and p1_0.created_at<? group by p1_0.payment_type
HTTP/1.1 200 OK
[
{"method": "UpiPayment", "count": 18420, "total": 9214350.00},
{"method": "CardPayment", "count": 6115, "total": 7810220.00},
{"method": "CodPayment", "count": 2290, "total": 1544890.00}
]- One table, one index on
(status, created_at): the report and the order page both stay fast. - Adding net banking later = one new subclass with a new discriminator value and one nullable column.
- Business logic lives in the subclasses (
receiptLine()), so services never needinstanceofchains.