PaymentDtos.java

package com.rbctcsworld.ecommerce.payment;

import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;

public final class PaymentDtos {

    private PaymentDtos() {
    }

    /** Card details exist only in memory for the duration of the request - never logged, never stored. */
    public record PayRequest(
            @NotBlank @Pattern(regexp = "[0-9 ]{13,23}", message = "must be 13-19 digits") String cardNumber,
            @NotNull @Min(1) @Max(12) Integer expiryMonth,
            @NotNull @Min(2000) @Max(2100) Integer expiryYear,
            @NotBlank @Pattern(regexp = "\\d{3,4}", message = "must be 3 or 4 digits") String cvv,
            Boolean saveCard) {

        /** Without "save this card" (older clients, tests). */
        public PayRequest(String cardNumber, Integer expiryMonth, Integer expiryYear, String cvv) {
            this(cardNumber, expiryMonth, expiryYear, cvv, null);
        }

        @Override
        public String toString() {   // protects against accidental logging of card data
            return "PayRequest[card=****, expiry=**/****, cvv=***]";
        }
    }

    /** Pay with a saved card: the CVV is asked again every time and never kept. */
    public record PaySavedRequest(
            @NotNull Long paymentMethodId,
            @NotBlank @Pattern(regexp = "\\d{3,4}", message = "must be 3 or 4 digits") String cvv) {

        @Override
        public String toString() {
            return "PaySavedRequest[paymentMethodId=" + paymentMethodId + ", cvv=***]";
        }
    }

    /** Add a card to the wallet. The number is turned into the provider's token and then forgotten. */
    public record AddCardRequest(
            @NotBlank @Pattern(regexp = "[0-9 ]{13,23}", message = "must be 13-19 digits") String cardNumber,
            @NotNull @Min(1) @Max(12) Integer expiryMonth,
            @NotNull @Min(2000) @Max(2100) Integer expiryYear,
            boolean makeDefault) {

        @Override
        public String toString() {
            return "AddCardRequest[card=****, expiry=**/****]";
        }
    }

    public record MethodResponse(long id, String brand, String last4, int expiryMonth, int expiryYear, boolean expired, boolean isDefault) {

        static MethodResponse from(SavedPaymentMethod m, java.time.YearMonth now) {
            return new MethodResponse(m.getId(), m.getBrand(), m.getLast4(), m.getExpiryMonth(), m.getExpiryYear(),
                    m.expiredAt(now), m.isDefault());
        }
    }
}