This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
package ru.soune.nocopy.entity.payout;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.DiscriminatorValue;
|
||||
import jakarta.persistence.Entity;
|
||||
import lombok.*;
|
||||
|
||||
@Entity
|
||||
@DiscriminatorValue("BANK_TRANSFER")
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Getter @Setter
|
||||
public class BankTransferPayoutMethod extends PayoutMethod {
|
||||
|
||||
@Column(name = "bank_name")
|
||||
private String bankName;
|
||||
|
||||
@Column(name = "bic")
|
||||
private String bic;
|
||||
|
||||
@Column(name = "correspondent_account")
|
||||
private String correspondentAccount;
|
||||
|
||||
@Column(name = "account_number")
|
||||
private String accountNumber;
|
||||
|
||||
@Column(name = "account_holder")
|
||||
private String accountHolder;
|
||||
|
||||
@Column(name = "inn")
|
||||
private String inn;
|
||||
|
||||
@Column(name = "kpp")
|
||||
private String kpp;
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return String.format("Tax %s in %s", getMaskedAccount(), bankName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMaskedDetails() {
|
||||
return getMaskedAccount();
|
||||
}
|
||||
|
||||
private String getMaskedAccount() {
|
||||
if (accountNumber == null || accountNumber.length() < 4) return "";
|
||||
return "..." + accountNumber.substring(accountNumber.length() - 4);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package ru.soune.nocopy.entity.payout;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Convert;
|
||||
import jakarta.persistence.DiscriminatorValue;
|
||||
import jakarta.persistence.Entity;
|
||||
import lombok.*;
|
||||
import ru.soune.nocopy.entity.payout.convertor.CardNumberEncryptor;
|
||||
|
||||
@Entity
|
||||
@DiscriminatorValue("CARD")
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Setter @Getter
|
||||
public class CardPayoutMethod extends PayoutMethod {
|
||||
@Convert(converter = CardNumberEncryptor.class)
|
||||
@Column(name = "card_number", nullable = false)
|
||||
private String cardNumber;
|
||||
|
||||
@Column(name = "card_hash", unique = true)
|
||||
private String cardHash;
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Card: " + getMaskedCardNumber();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMaskedDetails() {
|
||||
return getMaskedCardNumber();
|
||||
}
|
||||
|
||||
private String getMaskedCardNumber() {
|
||||
if (cardNumber == null || cardNumber.length() < 4) return "";
|
||||
return "****" + cardNumber.substring(cardNumber.length() - 4);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package ru.soune.nocopy.entity.payout;
|
||||
|
||||
import jakarta.persistence.DiscriminatorValue;
|
||||
import jakarta.persistence.Entity;
|
||||
|
||||
@Entity
|
||||
@DiscriminatorValue("INTERNAL")
|
||||
public class InternalPayoutMethod extends PayoutMethod {
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return "Internal balance";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMaskedDetails() {
|
||||
return "Account balance";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package ru.soune.nocopy.entity.payout;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
import org.hibernate.annotations.UpdateTimestamp;
|
||||
import ru.soune.nocopy.entity.user.User;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "payout_methods")
|
||||
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
|
||||
@DiscriminatorColumn(name = "method_type")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayoutMethod {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "user_id", nullable = false)
|
||||
private User user;
|
||||
|
||||
@Column(name = "method_type", insertable = false, updatable = false)
|
||||
private String methodType;
|
||||
|
||||
@Column(name = "is_default")
|
||||
private boolean isDefault;
|
||||
|
||||
@CreationTimestamp
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@UpdateTimestamp
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
|
||||
public String getDisplayName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getMaskedDetails() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package ru.soune.nocopy.entity.payout;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
import org.hibernate.annotations.UpdateTimestamp;
|
||||
import ru.soune.nocopy.entity.user.User;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
|
||||
@Entity
|
||||
@Table(name = "payout_requests")
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public class PayoutRequest {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "payout_method_id")
|
||||
@JsonIgnore
|
||||
private PayoutMethod payoutMethod;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "user_id")
|
||||
@JsonIgnore
|
||||
private User user;
|
||||
|
||||
@Column(nullable = false)
|
||||
private BigDecimal amount;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
private PayoutRequestStatus status;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false, name = "payout_type")
|
||||
@JsonIgnore
|
||||
private PayoutType payoutType;
|
||||
|
||||
@Column(name = "process_at")
|
||||
private LocalDateTime processAt;
|
||||
|
||||
@Column(name = "completed_at")
|
||||
private LocalDateTime completedAt;
|
||||
|
||||
@Column(name = "rejection_reason")
|
||||
private String rejectionReason;
|
||||
|
||||
@CreationTimestamp
|
||||
@Column(name = "created_at", updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@UpdateTimestamp
|
||||
@Column(name = "updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package ru.soune.nocopy.entity.payout;
|
||||
|
||||
public enum PayoutRequestStatus {
|
||||
PENDING,
|
||||
PROCESSING,
|
||||
COMPLETED,
|
||||
REJECTED,
|
||||
CANCELLED
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package ru.soune.nocopy.entity.payout;
|
||||
|
||||
public enum PayoutType {
|
||||
CARD("card"),
|
||||
BANK_TRANSFER("bank_transfer"),
|
||||
INTERNAL("internal");
|
||||
|
||||
private final String displayName;
|
||||
|
||||
PayoutType(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package ru.soune.nocopy.entity.payout.convertor;
|
||||
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
import jakarta.persistence.Converter;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
|
||||
@Converter
|
||||
public class CardNumberEncryptor implements AttributeConverter<String, String> {
|
||||
|
||||
private static final String ALGORITHM = "AES/GCM/NoPadding";
|
||||
private static final int GCM_TAG_LENGTH = 128;
|
||||
private static final int GCM_IV_LENGTH = 12;
|
||||
|
||||
private static final byte[] FIXED_KEY = "12345678901234567890123456789012".getBytes();
|
||||
|
||||
@Override
|
||||
public String convertToDatabaseColumn(String cardNumber) {
|
||||
if (cardNumber == null) return null;
|
||||
|
||||
try {
|
||||
SecretKeySpec key = new SecretKeySpec(FIXED_KEY, "AES");
|
||||
|
||||
byte[] iv = new byte[GCM_IV_LENGTH];
|
||||
new SecureRandom().nextBytes(iv);
|
||||
|
||||
Cipher cipher = Cipher.getInstance(ALGORITHM);
|
||||
GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, parameterSpec);
|
||||
|
||||
byte[] encrypted = cipher.doFinal(cardNumber.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
byte[] result = new byte[iv.length + encrypted.length];
|
||||
System.arraycopy(iv, 0, result, 0, iv.length);
|
||||
System.arraycopy(encrypted, 0, result, iv.length, encrypted.length);
|
||||
|
||||
return Base64.getEncoder().encodeToString(result);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to encrypt card number", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertToEntityAttribute(String dbData) {
|
||||
if (dbData == null) return null;
|
||||
|
||||
try {
|
||||
byte[] decoded = Base64.getDecoder().decode(dbData);
|
||||
|
||||
byte[] iv = new byte[GCM_IV_LENGTH];
|
||||
byte[] encrypted = new byte[decoded.length - GCM_IV_LENGTH];
|
||||
System.arraycopy(decoded, 0, iv, 0, iv.length);
|
||||
System.arraycopy(decoded, iv.length, encrypted, 0, encrypted.length);
|
||||
|
||||
SecretKeySpec key = new SecretKeySpec(FIXED_KEY, "AES");
|
||||
|
||||
Cipher cipher = Cipher.getInstance(ALGORITHM);
|
||||
GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, parameterSpec);
|
||||
|
||||
byte[] decrypted = cipher.doFinal(encrypted);
|
||||
return new String(decrypted, StandardCharsets.UTF_8);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to decrypt card number", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user