Payment.java
package com.ctrlbuy.webshop.model;
import com.ctrlbuy.webshop.entity.Order;
import com.ctrlbuy.webshop.enums.PaymentStatus;
import com.ctrlbuy.webshop.enums.PaymentType;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* Payment Entity för databas-lagring
* ✅ RAILWAY-OPTIMERAD: JPA Entity med förbättrade relationships och business logic
*/
@Entity
@Table(name = "payments", indexes = {
@Index(name = "idx_payment_order", columnList = "order_id"),
@Index(name = "idx_payment_status", columnList = "status"),
@Index(name = "idx_payment_type", columnList = "type"),
@Index(name = "idx_payment_transaction", columnList = "transaction_id"),
@Index(name = "idx_payment_gateway_transaction", columnList = "gateway_transaction_id"),
@Index(name = "idx_payment_created", columnList = "created_at")
})
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Payment {
// ============================
// CORE PAYMENT FIELDS
// ============================
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "order_id", nullable = false,
foreignKey = @ForeignKey(name = "fk_payment_order"))
private Order order;
@Column(name = "amount", nullable = false, precision = 10)
private BigDecimal amount;
@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false, length = 30)
private PaymentStatus status;
@Enumerated(EnumType.STRING)
@Column(name = "type", length = 30)
private PaymentType type = PaymentType.CREDIT_CARD;
// ============================
// TRANSACTION IDENTIFIERS
// ============================
@Column(name = "transaction_id", length = 100, unique = true)
private String transactionId;
@Column(name = "gateway_transaction_id", length = 100)
private String gatewayTransactionId;
@Column(name = "merchant_reference", length = 100)
private String merchantReference;
@Column(name = "authorization_code", length = 50)
private String authorizationCode;
// ============================
// CARD INFORMATION
// ============================
@Column(name = "card_type", length = 20)
private String cardType;
@Column(name = "masked_card_number", length = 20)
private String maskedCardNumber;
@Column(name = "card_last_four_digits", length = 4)
private String cardLastFourDigits;
@Column(name = "card_holder_name", length = 100)
private String cardHolderName;
// ============================
// GATEWAY & PROCESSING
// ============================
@Column(name = "payment_gateway", length = 50)
private String paymentGateway = "STRIPE";
@Column(name = "gateway_response", length = 2000)
private String gatewayResponse;
@Column(name = "error_code", length = 50)
private String errorCode;
@Column(name = "error_message", length = 500)
private String errorMessage;
@Column(name = "failure_reason", length = 500)
private String failureReason;
// ============================
// FINANCIAL FIELDS
// ============================
@Column(name = "currency", length = 3)
private String currency = "SEK";
@Column(name = "fee_amount", precision = 10)
private BigDecimal feeAmount;
@Column(name = "refund_amount", precision = 10)
private BigDecimal refundAmount;
@Column(name = "tax_amount", precision = 10)
private BigDecimal taxAmount;
// ============================
// TIMESTAMPS
// ============================
@Column(name = "created_at", nullable = false)
private LocalDateTime createdAt = LocalDateTime.now();
@Column(name = "updated_at")
private LocalDateTime updatedAt = LocalDateTime.now();
@Column(name = "processed_at")
private LocalDateTime processedAt;
@Column(name = "completed_at")
private LocalDateTime completedAt;
@Column(name = "failed_at")
private LocalDateTime failedAt;
@Column(name = "refunded_at")
private LocalDateTime refundedAt;
// ============================
// AUDIT & SECURITY FIELDS
// ============================
@Column(name = "ip_address", length = 45)
private String ipAddress;
@Column(name = "user_agent", length = 500)
private String userAgent;
@Column(name = "device_fingerprint", length = 100)
private String deviceFingerprint;
@Column(name = "risk_score")
private Integer riskScore;
@Column(name = "fraud_check_result", length = 50)
private String fraudCheckResult;
// ============================
// LIFECYCLE CALLBACKS
// ============================
@PrePersist
protected void onCreate() {
LocalDateTime now = LocalDateTime.now();
if (createdAt == null) {
createdAt = now;
}
if (updatedAt == null) {
updatedAt = now;
}
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
// Auto-set completion timestamps based on status
LocalDateTime now = LocalDateTime.now();
if (status == PaymentStatus.COMPLETED && completedAt == null) {
completedAt = now;
}
if (status == PaymentStatus.FAILED && failedAt == null) {
failedAt = now;
}
if ((status == PaymentStatus.REFUNDED || status == PaymentStatus.PARTIALLY_REFUNDED)
&& refundedAt == null) {
refundedAt = now;
}
}
// ============================
// CONVENIENCE GETTER METHODS (för PaymentInfo compatibility)
// ============================
/**
* Alias för id (används av PaymentInfo)
*/
public Long getPaymentId() {
return this.id;
}
/**
* Get order ID direkt eller från order relationship
*/
public Long getOrderId() {
return this.order != null ? this.order.getId() : null;
}
/**
* Get error code (required by PaymentInfo)
*/
public String getErrorCode() {
return errorCode;
}
/**
* Get error message (required by PaymentInfo)
*/
public String getErrorMessage() {
return errorMessage;
}
/**
* Get currency (required by PaymentInfo)
*/
public String getCurrency() {
return currency;
}
/**
* Get fee amount (required by PaymentInfo)
*/
public BigDecimal getFeeAmount() {
return feeAmount;
}
/**
* Get gateway response (required by PaymentInfo)
*/
public String getGatewayResponse() {
return gatewayResponse;
}
// ============================
// BUSINESS LOGIC METHODS
// ============================
/**
* Kontrollera om betalningen är lyckad
*/
public boolean isSuccessful() {
return status == PaymentStatus.COMPLETED;
}
/**
* Kontrollera om betalningen är misslyckad
*/
public boolean isFailed() {
return status == PaymentStatus.FAILED || status == PaymentStatus.CANCELLED;
}
/**
* Kontrollera om betalningen pågår
*/
public boolean isInProgress() {
return status == PaymentStatus.PENDING || status == PaymentStatus.PROCESSING;
}
/**
* Kontrollera om det är en återbetalning
*/
public boolean isRefund() {
return type == PaymentType.REFUND ||
status == PaymentStatus.REFUNDED ||
status == PaymentStatus.PARTIALLY_REFUNDED;
}
/**
* Kontrollera om betalningen kan återbetalas
*/
public boolean canBeRefunded() {
return status == PaymentStatus.COMPLETED &&
type != PaymentType.REFUND &&
(refundAmount == null || refundAmount.compareTo(amount) < 0);
}
/**
* Kontrollera om betalningen är högrisk
*/
public boolean isHighRisk() {
return riskScore != null && riskScore > 75;
}
/**
* Hämta total kostnad inklusive avgifter
*/
public BigDecimal getTotalCost() {
BigDecimal total = amount != null ? amount : BigDecimal.ZERO;
if (feeAmount != null) {
total = total.add(feeAmount);
}
if (taxAmount != null) {
total = total.add(taxAmount);
}
return total;
}
/**
* Hämta netto belopp efter återbetalningar
*/
public BigDecimal getNetAmount() {
BigDecimal net = amount != null ? amount : BigDecimal.ZERO;
if (refundAmount != null) {
net = net.subtract(refundAmount);
}
return net;
}
/**
* Hämta återstående belopp som kan återbetalas
*/
public BigDecimal getRefundableAmount() {
BigDecimal total = amount != null ? amount : BigDecimal.ZERO;
BigDecimal refunded = refundAmount != null ? refundAmount : BigDecimal.ZERO;
return total.subtract(refunded);
}
/**
* Hämta maskerat kortnummer
*/
public String getMaskedCardNumber() {
if (maskedCardNumber != null) {
return maskedCardNumber;
}
if (cardLastFourDigits != null) {
return "**** **** **** " + cardLastFourDigits;
}
return "****";
}
/**
* Beräkna behandlingstid i minuter
*/
public Long getProcessingTimeMinutes() {
if (createdAt == null || processedAt == null) {
return null;
}
return java.time.Duration.between(createdAt, processedAt).toMinutes();
}
/**
* Kontrollera om betalningen är gammal (över 30 minuter utan processing)
*/
public boolean isStale() {
if (createdAt == null || !isInProgress()) {
return false;
}
return createdAt.isBefore(LocalDateTime.now().minusMinutes(30));
}
// ============================
// BUSINESS OPERATIONS
// ============================
/**
* Markera som behandlad
*/
public Payment markAsProcessing() {
this.status = PaymentStatus.PROCESSING;
this.processedAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
return this;
}
/**
* Markera som genomförd
*/
public Payment markAsCompleted(String transactionId) {
this.status = PaymentStatus.COMPLETED;
this.transactionId = transactionId;
this.completedAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
return this;
}
/**
* Markera som misslyckad
*/
public Payment markAsFailed(String errorCode, String errorMessage) {
this.status = PaymentStatus.FAILED;
this.errorCode = errorCode;
this.errorMessage = errorMessage;
this.failedAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
return this;
}
/**
* Lägg till återbetalning
*/
public Payment addRefund(BigDecimal refundAmount) {
if (this.refundAmount == null) {
this.refundAmount = BigDecimal.ZERO;
}
this.refundAmount = this.refundAmount.add(refundAmount);
// Uppdatera status baserat på återbetalningsbelopp
if (this.refundAmount.compareTo(this.amount) >= 0) {
this.status = PaymentStatus.REFUNDED;
} else {
this.status = PaymentStatus.PARTIALLY_REFUNDED;
}
this.refundedAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
return this;
}
/**
* Sätt riskbedömning
*/
public Payment setRiskAssessment(Integer riskScore, String fraudResult) {
this.riskScore = riskScore;
this.fraudCheckResult = fraudResult;
this.updatedAt = LocalDateTime.now();
return this;
}
// ============================
// CUSTOM SETTER WITH BUSINESS LOGIC
// ============================
/**
* Update timestamp when status changes
*/
public void setStatus(PaymentStatus newStatus) {
PaymentStatus oldStatus = this.status;
this.status = newStatus;
this.updatedAt = LocalDateTime.now();
// Auto-set timestamps based on status transitions
LocalDateTime now = LocalDateTime.now();
if (newStatus == PaymentStatus.PROCESSING && processedAt == null) {
processedAt = now;
}
if (newStatus == PaymentStatus.COMPLETED && completedAt == null) {
completedAt = now;
}
if (newStatus == PaymentStatus.FAILED && failedAt == null) {
failedAt = now;
}
if ((newStatus == PaymentStatus.REFUNDED || newStatus == PaymentStatus.PARTIALLY_REFUNDED)
&& refundedAt == null) {
refundedAt = now;
}
}
// ============================
// PAYMENTSERVICE COMPATIBILITY METHODS
// ============================
/**
* Set payment date (alias för processedAt) - CRITICAL för PaymentService
*/
public void setPaymentDate(LocalDateTime paymentDate) {
this.processedAt = paymentDate;
this.updatedAt = LocalDateTime.now();
}
/**
* Get payment date (alias för processedAt)
*/
public LocalDateTime getPaymentDate() {
return this.processedAt;
}
/**
* Set transaction ID
*/
public void setTransactionId(String transactionId) {
this.transactionId = transactionId;
this.updatedAt = LocalDateTime.now();
}
/**
* Get transaction ID
*/
public String getTransactionId() {
return this.transactionId;
}
/**
* Set order and sync relationship (för PaymentService)
*/
public void setOrder(Order order) {
this.order = order;
this.updatedAt = LocalDateTime.now();
}
/**
* Get order
*/
public Order getOrder() {
return this.order;
}
/**
* Set card last four digits
*/
public void setCardLastFourDigits(String cardLastFourDigits) {
this.cardLastFourDigits = cardLastFourDigits;
this.updatedAt = LocalDateTime.now();
}
/**
* Get card last four digits
*/
public String getCardLastFourDigits() {
return this.cardLastFourDigits;
}
/**
* Set completed at timestamp
*/
public void setCompletedAt(LocalDateTime completedAt) {
this.completedAt = completedAt;
this.updatedAt = LocalDateTime.now();
}
/**
* Get completed at timestamp
*/
public LocalDateTime getCompletedAt() {
return this.completedAt;
}
/**
* Set failure reason
*/
public void setFailureReason(String failureReason) {
this.failureReason = failureReason;
this.updatedAt = LocalDateTime.now();
}
/**
* Get failure reason
*/
public String getFailureReason() {
return this.failureReason;
}
@Override
public String toString() {
return String.format("Payment{id=%d, orderId=%d, amount=%s, status=%s, type=%s, transactionId='%s', processedAt=%s}",
id, getOrderId(), amount, status, type, transactionId, processedAt);
}
// ============================
// EQUALS & HASHCODE (baserat på transaction ID för business logic)
// ============================
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Payment)) return false;
Payment payment = (Payment) o;
return id != null && id.equals(payment.id) ||
(transactionId != null && transactionId.equals(payment.transactionId));
}
@Override
public int hashCode() {
return id != null ? id.hashCode() :
(transactionId != null ? transactionId.hashCode() : 0);
}
// ============================
// CUSTOM BUILDER FOR PAYMENTINFO COMPATIBILITY
// ============================
/**
* Static builder method
*/
public static PaymentBuilder builder() {
return new PaymentBuilder();
}
/**
* Custom PaymentBuilder som stöder både paymentId() och id() methods
*/
public static class PaymentBuilder {
private Long id;
private Order order;
private BigDecimal amount;
private PaymentStatus status;
private PaymentType type = PaymentType.CREDIT_CARD;
private String transactionId;
private String gatewayTransactionId;
private String merchantReference;
private String authorizationCode;
private String cardType;
private String maskedCardNumber;
private String cardLastFourDigits;
private String cardHolderName;
private String paymentGateway = "STRIPE";
private String gatewayResponse;
private String errorCode;
private String errorMessage;
private String failureReason;
private String currency = "SEK";
private BigDecimal feeAmount;
private BigDecimal refundAmount;
private BigDecimal taxAmount;
private LocalDateTime createdAt = LocalDateTime.now();
private LocalDateTime updatedAt = LocalDateTime.now();
private LocalDateTime processedAt;
private LocalDateTime completedAt;
private LocalDateTime failedAt;
private LocalDateTime refundedAt;
private String ipAddress;
private String userAgent;
private String deviceFingerprint;
private Integer riskScore;
private String fraudCheckResult;
/**
* CRITICAL: paymentId method för PaymentInfo.toPaymentEntity() compatibility
*/
public PaymentBuilder paymentId(Long paymentId) {
this.id = paymentId;
return this;
}
public PaymentBuilder id(Long id) {
this.id = id;
return this;
}
public PaymentBuilder order(Order order) {
this.order = order;
return this;
}
public PaymentBuilder orderId(Long orderId) {
// Set orderId by creating a minimal Order object
if (orderId != null) {
Order tempOrder = new Order();
tempOrder.setId(orderId);
this.order = tempOrder;
}
return this;
}
public PaymentBuilder amount(BigDecimal amount) {
this.amount = amount;
return this;
}
public PaymentBuilder status(PaymentStatus status) {
this.status = status;
return this;
}
public PaymentBuilder type(PaymentType type) {
this.type = type;
return this;
}
public PaymentBuilder transactionId(String transactionId) {
this.transactionId = transactionId;
return this;
}
public PaymentBuilder gatewayTransactionId(String gatewayTransactionId) {
this.gatewayTransactionId = gatewayTransactionId;
return this;
}
public PaymentBuilder merchantReference(String merchantReference) {
this.merchantReference = merchantReference;
return this;
}
public PaymentBuilder authorizationCode(String authorizationCode) {
this.authorizationCode = authorizationCode;
return this;
}
public PaymentBuilder cardType(String cardType) {
this.cardType = cardType;
return this;
}
public PaymentBuilder maskedCardNumber(String maskedCardNumber) {
this.maskedCardNumber = maskedCardNumber;
return this;
}
public PaymentBuilder cardLastFourDigits(String cardLastFourDigits) {
this.cardLastFourDigits = cardLastFourDigits;
return this;
}
public PaymentBuilder cardHolderName(String cardHolderName) {
this.cardHolderName = cardHolderName;
return this;
}
public PaymentBuilder paymentGateway(String paymentGateway) {
this.paymentGateway = paymentGateway;
return this;
}
public PaymentBuilder gatewayResponse(String gatewayResponse) {
this.gatewayResponse = gatewayResponse;
return this;
}
public PaymentBuilder errorCode(String errorCode) {
this.errorCode = errorCode;
return this;
}
public PaymentBuilder errorMessage(String errorMessage) {
this.errorMessage = errorMessage;
return this;
}
public PaymentBuilder failureReason(String failureReason) {
this.failureReason = failureReason;
return this;
}
public PaymentBuilder currency(String currency) {
this.currency = currency;
return this;
}
public PaymentBuilder feeAmount(BigDecimal feeAmount) {
this.feeAmount = feeAmount;
return this;
}
public PaymentBuilder refundAmount(BigDecimal refundAmount) {
this.refundAmount = refundAmount;
return this;
}
public PaymentBuilder taxAmount(BigDecimal taxAmount) {
this.taxAmount = taxAmount;
return this;
}
public PaymentBuilder createdAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
public PaymentBuilder updatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
return this;
}
public PaymentBuilder processedAt(LocalDateTime processedAt) {
this.processedAt = processedAt;
return this;
}
public PaymentBuilder completedAt(LocalDateTime completedAt) {
this.completedAt = completedAt;
return this;
}
public PaymentBuilder failedAt(LocalDateTime failedAt) {
this.failedAt = failedAt;
return this;
}
public PaymentBuilder refundedAt(LocalDateTime refundedAt) {
this.refundedAt = refundedAt;
return this;
}
public PaymentBuilder ipAddress(String ipAddress) {
this.ipAddress = ipAddress;
return this;
}
public PaymentBuilder userAgent(String userAgent) {
this.userAgent = userAgent;
return this;
}
public PaymentBuilder deviceFingerprint(String deviceFingerprint) {
this.deviceFingerprint = deviceFingerprint;
return this;
}
public PaymentBuilder riskScore(Integer riskScore) {
this.riskScore = riskScore;
return this;
}
public PaymentBuilder fraudCheckResult(String fraudCheckResult) {
this.fraudCheckResult = fraudCheckResult;
return this;
}
/**
* Build the Payment object
*/
public Payment build() {
Payment payment = new Payment();
payment.id = this.id;
payment.order = this.order;
payment.amount = this.amount;
payment.status = this.status;
payment.type = this.type;
payment.transactionId = this.transactionId;
payment.gatewayTransactionId = this.gatewayTransactionId;
payment.merchantReference = this.merchantReference;
payment.authorizationCode = this.authorizationCode;
payment.cardType = this.cardType;
payment.maskedCardNumber = this.maskedCardNumber;
payment.cardLastFourDigits = this.cardLastFourDigits;
payment.cardHolderName = this.cardHolderName;
payment.paymentGateway = this.paymentGateway;
payment.gatewayResponse = this.gatewayResponse;
payment.errorCode = this.errorCode;
payment.errorMessage = this.errorMessage;
payment.failureReason = this.failureReason;
payment.currency = this.currency;
payment.feeAmount = this.feeAmount;
payment.refundAmount = this.refundAmount;
payment.taxAmount = this.taxAmount;
payment.createdAt = this.createdAt;
payment.updatedAt = this.updatedAt;
payment.processedAt = this.processedAt;
payment.completedAt = this.completedAt;
payment.failedAt = this.failedAt;
payment.refundedAt = this.refundedAt;
payment.ipAddress = this.ipAddress;
payment.userAgent = this.userAgent;
payment.deviceFingerprint = this.deviceFingerprint;
payment.riskScore = this.riskScore;
payment.fraudCheckResult = this.fraudCheckResult;
return payment;
}
}
}