diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..f853048 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @BitWeb/tech diff --git a/build.gradle b/build.gradle index c99be62..59febad 100644 --- a/build.gradle +++ b/build.gradle @@ -31,6 +31,7 @@ repositories { dependencies { implementation 'tools.jackson.core:jackson-databind:3.0.1' + implementation 'jakarta.annotation:jakarta.annotation-api:3.0.0' implementation 'com.auth0:java-jwt:4.5.0' testImplementation platform('org.junit:junit-bom:5.14.3') diff --git a/docs/plans/2026-04-10-payment-order-models-design.md b/docs/plans/2026-04-10-payment-order-models-design.md new file mode 100644 index 0000000..aa14768 --- /dev/null +++ b/docs/plans/2026-04-10-payment-order-models-design.md @@ -0,0 +1,129 @@ +# Payment Order Models Design + +**Date:** 2026-04-10 +**Issue:** #14 — Payment order models — requests, responses, and enums +**Status:** Implemented + +## Overview + +Define all DTOs and enums needed for the Montonio payment order API. This covers the +`POST /orders` request/response and the `GET /orders/{uuid}` response, including nested +models for payment details, addresses, line items, and payment intents. + +## Sources + +Field definitions were derived by cross-referencing: + +- [montonio/montonio-js](https://github.com/montonio/montonio-js) — official front-end SDK (enums, payment statuses) +- [maitkaa/montonio-client](https://github.com/maitkaa/montonio-client) — community TypeScript client (full Order, OrderResponse, PaymentIntent types) +- [montonio/montonio-shopware-php-sdk](https://github.com/montonio/montonio-shopware-php-sdk) — official PHP SDK (PaymentData, Address, LineItem, Payment structs) +- Issue #14 requirements + +## Design Decisions + +### Package layout — feature-based + +```text +ee.bitweb.montonio.sdk.model/ — shared enums (Currency, Locale, etc.) +ee.bitweb.montonio.sdk.order.model/ — order-scoped models and enums +ee.bitweb.montonio.sdk.order.request/ — request DTOs +ee.bitweb.montonio.sdk.order.response/ — response DTOs +``` + +Reusable types (`Currency`, `Locale`, `PaymentMethodType`, `CardPaymentMethod`, +`WalletProvider`) live in `sdk.model`. Order-specific types (`PaymentStatus`, `Address`, +`LineItem`, `Payment`, `PaymentMethodOptions`) live under `sdk.order.model`. + +### Enum serialization — `@JsonValue` with wire-value field + +Each enum constant stores its API wire value in a `String value` field, exposed via +`@JsonValue`. This decouples Java naming conventions (UPPER_CASE) from the API's mixed +casing (e.g., `paymentInitiation`, `cardPayments`, `EUR`, `en`). + +### Request DTOs — Lombok `@Builder` with constructor validation + +Request DTOs use `@Builder` + `@Getter` with fail-fast validation in a `@JsonCreator` +constructor, consistent with `MontonioSdkConfiguration`. Required fields throw +`MontonioValidationException` if null/blank. + +### Response DTOs — immutable with `@JsonCreator` + +Response DTOs are `final` classes with all-args `@JsonCreator` constructors, `@Getter`, +and final fields. The `-parameters` compiler flag lets Jackson resolve constructor +parameter names without `@JsonProperty` annotations. + +### `@JsonCreator` over `@Jacksonized` + +Lombok's `@Jacksonized` generates `@com.fasterxml.jackson.databind.annotation.JsonDeserialize`, +but Jackson 3 moved that annotation to `tools.jackson.databind.annotation`. To avoid +compatibility issues, all classes use explicit `@JsonCreator` constructors. + +### Monetary amounts — `BigDecimal` for requests, `String` for responses + +Request DTOs use `BigDecimal` to avoid floating-point precision issues. Response DTOs +use `String` for `grandTotal`, `amount`, and `serviceFee` because that is what the API +returns on the wire. + +### Nullability — `@jakarta.annotation.Nullable` + +Optional fields are annotated with `@Nullable`. Absence of the annotation implies +non-null by contract. + +### `PaymentMethodOptions` — single flat class + +Rather than a type hierarchy mirroring the TypeScript union type +(`PaymentInitiationOptions | CardPaymentsOptions | ...`), all method-specific options +are merged into one class with all fields nullable. This avoids polymorphic +deserialization complexity for no real gain — the fields don't conflict. + +### Refund fields — omitted + +`OrderResponse` in the TypeScript client includes `refunds`, `availableForRefund`, and +`isRefundableType`. Refund-related fields are mostly omitted (only `isRefundableType` +is included) pending follow-up issue #30 for refund models. Jackson ignores unknown fields. + +## File Inventory + +### Shared enums (`sdk.model`) + +| File | Wire values | +|------|-------------| +| `Currency.java` | `EUR`, `PLN` | +| `Locale.java` | `de`, `en`, `et`, `fi`, `lt`, `lv`, `pl`, `ru` | +| `PaymentMethodType.java` | `paymentInitiation`, `cardPayments`, `blik`, `bnpl`, `hirePurchase` | +| `CardPaymentMethod.java` | `card`, `wallet` | +| `WalletProvider.java` | `applePay`, `googlePay` | + +### Order models (`order.model`) + +| File | Description | +|------|-------------| +| `PaymentStatus.java` | 10 statuses: PENDING through AUTHORIZED | +| `Address.java` | 15 optional fields (name, email, phone, address, company) | +| `LineItem.java` | `name`, `quantity`, `finalPrice` (all required) | +| `Payment.java` | `method`, `currency`, `amount` (required) + `methodOptions`, `methodDisplay` | +| `PaymentMethodOptions.java` | 8 optional fields covering all payment method types | + +### Request DTOs (`order.request`) + +| File | Required fields | +|------|----------------| +| `CreateOrderRequest.java` | `merchantReference`, `returnUrl`, `notificationUrl`, `grandTotal`, `currency`, `payment` | + +### Response DTOs (`order.response`) + +| File | Key fields | +|------|------------| +| `CreateOrderResponse.java` | `uuid`, `paymentUrl` | +| `OrderResponse.java` | 21 fields including nested `paymentIntents`, `lineItems`, addresses | +| `PaymentIntent.java` | `uuid`, `paymentMethodType`, `amount`, `status`, `serviceFee`, etc. | + +## Testing + +Each class has a corresponding test class covering: + +- Enum constant count and wire value assertions +- Serialization/deserialization round-trips via Jackson `ObjectMapper` +- Builder construction with all fields and required-only fields +- Validation — `MontonioValidationException` for null/blank required fields +- Unknown JSON field tolerance (deserialization with `FAIL_ON_UNKNOWN_PROPERTIES` disabled) diff --git a/src/main/java/ee/bitweb/montonio/sdk/model/CardPaymentMethod.java b/src/main/java/ee/bitweb/montonio/sdk/model/CardPaymentMethod.java new file mode 100644 index 0000000..7dc77b7 --- /dev/null +++ b/src/main/java/ee/bitweb/montonio/sdk/model/CardPaymentMethod.java @@ -0,0 +1,20 @@ +package ee.bitweb.montonio.sdk.model; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum CardPaymentMethod { + + CARD("card"), + WALLET("wallet"); + + private final String value; + + CardPaymentMethod(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } +} diff --git a/src/main/java/ee/bitweb/montonio/sdk/model/Currency.java b/src/main/java/ee/bitweb/montonio/sdk/model/Currency.java new file mode 100644 index 0000000..b566713 --- /dev/null +++ b/src/main/java/ee/bitweb/montonio/sdk/model/Currency.java @@ -0,0 +1,20 @@ +package ee.bitweb.montonio.sdk.model; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum Currency { + + EUR("EUR"), + PLN("PLN"); + + private final String value; + + Currency(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } +} diff --git a/src/main/java/ee/bitweb/montonio/sdk/model/Locale.java b/src/main/java/ee/bitweb/montonio/sdk/model/Locale.java new file mode 100644 index 0000000..79c607d --- /dev/null +++ b/src/main/java/ee/bitweb/montonio/sdk/model/Locale.java @@ -0,0 +1,26 @@ +package ee.bitweb.montonio.sdk.model; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum Locale { + + DE("de"), + EN("en"), + ET("et"), + FI("fi"), + LT("lt"), + LV("lv"), + PL("pl"), + RU("ru"); + + private final String value; + + Locale(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } +} diff --git a/src/main/java/ee/bitweb/montonio/sdk/model/PaymentMethodType.java b/src/main/java/ee/bitweb/montonio/sdk/model/PaymentMethodType.java new file mode 100644 index 0000000..a52b7ef --- /dev/null +++ b/src/main/java/ee/bitweb/montonio/sdk/model/PaymentMethodType.java @@ -0,0 +1,23 @@ +package ee.bitweb.montonio.sdk.model; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum PaymentMethodType { + + PAYMENT_INITIATION("paymentInitiation"), + CARD_PAYMENTS("cardPayments"), + BLIK("blik"), + BNPL("bnpl"), + HIRE_PURCHASE("hirePurchase"); + + private final String value; + + PaymentMethodType(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } +} diff --git a/src/main/java/ee/bitweb/montonio/sdk/model/WalletProvider.java b/src/main/java/ee/bitweb/montonio/sdk/model/WalletProvider.java new file mode 100644 index 0000000..1ff61e1 --- /dev/null +++ b/src/main/java/ee/bitweb/montonio/sdk/model/WalletProvider.java @@ -0,0 +1,20 @@ +package ee.bitweb.montonio.sdk.model; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum WalletProvider { + + APPLE_PAY("applePay"), + GOOGLE_PAY("googlePay"); + + private final String value; + + WalletProvider(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } +} diff --git a/src/main/java/ee/bitweb/montonio/sdk/order/model/Address.java b/src/main/java/ee/bitweb/montonio/sdk/order/model/Address.java new file mode 100644 index 0000000..d16d774 --- /dev/null +++ b/src/main/java/ee/bitweb/montonio/sdk/order/model/Address.java @@ -0,0 +1,91 @@ +package ee.bitweb.montonio.sdk.order.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import jakarta.annotation.Nullable; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class Address { + + @Nullable + private final String firstName; + + @Nullable + private final String lastName; + + @Nullable + private final String email; + + @Nullable + private final String phoneNumber; + + @Nullable + private final String phoneCountry; + + @Nullable + private final String addressLine1; + + @Nullable + private final String addressLine2; + + @Nullable + private final String locality; + + @Nullable + private final String region; + + @Nullable + private final String postalCode; + + @Nullable + private final String country; + + @Nullable + private final String companyName; + + @Nullable + private final String companyLegalName; + + @Nullable + private final String companyRegCode; + + @Nullable + private final String companyVatNumber; + + @JsonCreator + Address( + String firstName, + String lastName, + String email, + String phoneNumber, + String phoneCountry, + String addressLine1, + String addressLine2, + String locality, + String region, + String postalCode, + String country, + String companyName, + String companyLegalName, + String companyRegCode, + String companyVatNumber + ) { + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + this.phoneNumber = phoneNumber; + this.phoneCountry = phoneCountry; + this.addressLine1 = addressLine1; + this.addressLine2 = addressLine2; + this.locality = locality; + this.region = region; + this.postalCode = postalCode; + this.country = country; + this.companyName = companyName; + this.companyLegalName = companyLegalName; + this.companyRegCode = companyRegCode; + this.companyVatNumber = companyVatNumber; + } +} diff --git a/src/main/java/ee/bitweb/montonio/sdk/order/model/LineItem.java b/src/main/java/ee/bitweb/montonio/sdk/order/model/LineItem.java new file mode 100644 index 0000000..db0bebf --- /dev/null +++ b/src/main/java/ee/bitweb/montonio/sdk/order/model/LineItem.java @@ -0,0 +1,33 @@ +package ee.bitweb.montonio.sdk.order.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import ee.bitweb.montonio.sdk.exception.MontonioValidationException; +import lombok.Builder; +import lombok.Getter; + +import java.math.BigDecimal; + +@Getter +@Builder +public class LineItem { + + private final String name; + private final BigDecimal quantity; + private final BigDecimal finalPrice; + + @JsonCreator + LineItem(String name, BigDecimal quantity, BigDecimal finalPrice) { + if (name == null || name.isBlank()) { + throw new MontonioValidationException("name", "must not be null or blank"); + } + if (quantity == null) { + throw new MontonioValidationException("quantity", "must not be null"); + } + if (finalPrice == null) { + throw new MontonioValidationException("finalPrice", "must not be null"); + } + this.name = name; + this.quantity = quantity; + this.finalPrice = finalPrice; + } +} diff --git a/src/main/java/ee/bitweb/montonio/sdk/order/model/Payment.java b/src/main/java/ee/bitweb/montonio/sdk/order/model/Payment.java new file mode 100644 index 0000000..9411ec0 --- /dev/null +++ b/src/main/java/ee/bitweb/montonio/sdk/order/model/Payment.java @@ -0,0 +1,53 @@ +package ee.bitweb.montonio.sdk.order.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import ee.bitweb.montonio.sdk.exception.MontonioValidationException; +import ee.bitweb.montonio.sdk.model.Currency; +import ee.bitweb.montonio.sdk.model.PaymentMethodType; +import jakarta.annotation.Nullable; +import lombok.Builder; +import lombok.Getter; + +import java.math.BigDecimal; + +@Getter +@Builder +public class Payment { + + private final PaymentMethodType method; + private final Currency currency; + private final BigDecimal amount; + + @Nullable + private final PaymentMethodOptions methodOptions; + + @Nullable + private final String methodDisplay; + + @JsonCreator + Payment( + PaymentMethodType method, + Currency currency, + BigDecimal amount, + PaymentMethodOptions methodOptions, + String methodDisplay + ) { + if (method == null) { + throw new MontonioValidationException("method", "must not be null"); + } + if (currency == null) { + throw new MontonioValidationException("currency", "must not be null"); + } + if (amount == null) { + throw new MontonioValidationException("amount", "must not be null"); + } + if (amount.signum() <= 0) { + throw new MontonioValidationException("amount", "must be greater than zero"); + } + this.method = method; + this.currency = currency; + this.amount = amount; + this.methodOptions = methodOptions; + this.methodDisplay = methodDisplay; + } +} diff --git a/src/main/java/ee/bitweb/montonio/sdk/order/model/PaymentMethodOptions.java b/src/main/java/ee/bitweb/montonio/sdk/order/model/PaymentMethodOptions.java new file mode 100644 index 0000000..f1fffa9 --- /dev/null +++ b/src/main/java/ee/bitweb/montonio/sdk/order/model/PaymentMethodOptions.java @@ -0,0 +1,59 @@ +package ee.bitweb.montonio.sdk.order.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import ee.bitweb.montonio.sdk.model.CardPaymentMethod; +import ee.bitweb.montonio.sdk.model.Locale; +import ee.bitweb.montonio.sdk.model.WalletProvider; +import jakarta.annotation.Nullable; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class PaymentMethodOptions { + + @Nullable + private final String preferredProvider; + + @Nullable + private final String preferredCountry; + + @Nullable + private final Locale preferredLocale; + + @Nullable + private final CardPaymentMethod preferredMethod; + + @Nullable + private final WalletProvider preferredWallet; + + @Nullable + private final String paymentDescription; + + @Nullable + private final String paymentReference; + + @Nullable + private final Integer period; + + @JsonCreator + PaymentMethodOptions( + String preferredProvider, + String preferredCountry, + Locale preferredLocale, + CardPaymentMethod preferredMethod, + WalletProvider preferredWallet, + String paymentDescription, + String paymentReference, + Integer period + ) { + this.preferredProvider = preferredProvider; + this.preferredCountry = preferredCountry; + this.preferredLocale = preferredLocale; + this.preferredMethod = preferredMethod; + this.preferredWallet = preferredWallet; + this.paymentDescription = paymentDescription; + this.paymentReference = paymentReference; + this.period = period; + } +} diff --git a/src/main/java/ee/bitweb/montonio/sdk/order/model/PaymentStatus.java b/src/main/java/ee/bitweb/montonio/sdk/order/model/PaymentStatus.java new file mode 100644 index 0000000..768766a --- /dev/null +++ b/src/main/java/ee/bitweb/montonio/sdk/order/model/PaymentStatus.java @@ -0,0 +1,28 @@ +package ee.bitweb.montonio.sdk.order.model; + +import com.fasterxml.jackson.annotation.JsonValue; + +public enum PaymentStatus { + + PENDING("PENDING"), + PAID("PAID"), + VOIDED("VOIDED"), + PARTIALLY_REFUNDED("PARTIALLY_REFUNDED"), + REFUNDED("REFUNDED"), + CANCELED("CANCELED"), + ABANDONED("ABANDONED"), + DECLINED("DECLINED"), + SETTLED("SETTLED"), + AUTHORIZED("AUTHORIZED"); + + private final String value; + + PaymentStatus(String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } +} diff --git a/src/main/java/ee/bitweb/montonio/sdk/order/request/CreateOrderRequest.java b/src/main/java/ee/bitweb/montonio/sdk/order/request/CreateOrderRequest.java new file mode 100644 index 0000000..e69b7d8 --- /dev/null +++ b/src/main/java/ee/bitweb/montonio/sdk/order/request/CreateOrderRequest.java @@ -0,0 +1,85 @@ +package ee.bitweb.montonio.sdk.order.request; + +import com.fasterxml.jackson.annotation.JsonCreator; +import ee.bitweb.montonio.sdk.exception.MontonioValidationException; +import ee.bitweb.montonio.sdk.model.Currency; +import ee.bitweb.montonio.sdk.model.Locale; +import ee.bitweb.montonio.sdk.order.model.Address; +import ee.bitweb.montonio.sdk.order.model.LineItem; +import ee.bitweb.montonio.sdk.order.model.Payment; +import jakarta.annotation.Nullable; +import lombok.Builder; +import lombok.Getter; + +import java.math.BigDecimal; +import java.util.List; + +@Getter +@Builder +public class CreateOrderRequest { + + private final String merchantReference; + private final String returnUrl; + private final String notificationUrl; + private final BigDecimal grandTotal; + private final Currency currency; + private final Payment payment; + + @Nullable + private final Locale locale; + + @Nullable + private final Address billingAddress; + + @Nullable + private final Address shippingAddress; + + @Nullable + private final List lineItems; + + @JsonCreator + CreateOrderRequest( + String merchantReference, + String returnUrl, + String notificationUrl, + BigDecimal grandTotal, + Currency currency, + Payment payment, + Locale locale, + Address billingAddress, + Address shippingAddress, + List lineItems + ) { + if (merchantReference == null || merchantReference.isBlank()) { + throw new MontonioValidationException("merchantReference", "must not be null or blank"); + } + if (returnUrl == null || returnUrl.isBlank()) { + throw new MontonioValidationException("returnUrl", "must not be null or blank"); + } + if (notificationUrl == null || notificationUrl.isBlank()) { + throw new MontonioValidationException("notificationUrl", "must not be null or blank"); + } + if (grandTotal == null) { + throw new MontonioValidationException("grandTotal", "must not be null"); + } + if (grandTotal.signum() <= 0) { + throw new MontonioValidationException("grandTotal", "must be greater than zero"); + } + if (currency == null) { + throw new MontonioValidationException("currency", "must not be null"); + } + if (payment == null) { + throw new MontonioValidationException("payment", "must not be null"); + } + this.merchantReference = merchantReference; + this.returnUrl = returnUrl; + this.notificationUrl = notificationUrl; + this.grandTotal = grandTotal; + this.currency = currency; + this.payment = payment; + this.locale = locale; + this.billingAddress = billingAddress; + this.shippingAddress = shippingAddress; + this.lineItems = lineItems == null ? null : List.copyOf(lineItems); + } +} diff --git a/src/main/java/ee/bitweb/montonio/sdk/order/response/CreateOrderResponse.java b/src/main/java/ee/bitweb/montonio/sdk/order/response/CreateOrderResponse.java new file mode 100644 index 0000000..3db5b64 --- /dev/null +++ b/src/main/java/ee/bitweb/montonio/sdk/order/response/CreateOrderResponse.java @@ -0,0 +1,17 @@ +package ee.bitweb.montonio.sdk.order.response; + +import com.fasterxml.jackson.annotation.JsonCreator; +import lombok.Getter; + +@Getter +public final class CreateOrderResponse { + + private final String uuid; + private final String paymentUrl; + + @JsonCreator + public CreateOrderResponse(String uuid, String paymentUrl) { + this.uuid = uuid; + this.paymentUrl = paymentUrl; + } +} diff --git a/src/main/java/ee/bitweb/montonio/sdk/order/response/OrderResponse.java b/src/main/java/ee/bitweb/montonio/sdk/order/response/OrderResponse.java new file mode 100644 index 0000000..c0b47b2 --- /dev/null +++ b/src/main/java/ee/bitweb/montonio/sdk/order/response/OrderResponse.java @@ -0,0 +1,92 @@ +package ee.bitweb.montonio.sdk.order.response; + +import com.fasterxml.jackson.annotation.JsonCreator; +import ee.bitweb.montonio.sdk.model.Currency; +import ee.bitweb.montonio.sdk.model.Locale; +import ee.bitweb.montonio.sdk.model.PaymentMethodType; +import ee.bitweb.montonio.sdk.order.model.Address; +import ee.bitweb.montonio.sdk.order.model.LineItem; +import ee.bitweb.montonio.sdk.order.model.PaymentStatus; +import jakarta.annotation.Nullable; +import lombok.Getter; + +import java.util.List; + +@Getter +public final class OrderResponse { + + private final String uuid; + private final PaymentStatus paymentStatus; + private final Locale locale; + private final String merchantReference; + private final String merchantReferenceDisplay; + private final String merchantReturnUrl; + private final String merchantNotificationUrl; + private final String grandTotal; + private final Currency currency; + private final PaymentMethodType paymentMethodType; + private final String storeUuid; + @Nullable + private final List paymentIntents; + + @Nullable + private final List lineItems; + + private final Address billingAddress; + private final Address shippingAddress; + private final String expiresAt; + private final String createdAt; + private final String storeName; + private final String businessName; + private final String paymentUrl; + + @Nullable + private final Boolean isRefundableType; + + @JsonCreator + public OrderResponse( + String uuid, + PaymentStatus paymentStatus, + Locale locale, + String merchantReference, + String merchantReferenceDisplay, + String merchantReturnUrl, + String merchantNotificationUrl, + String grandTotal, + Currency currency, + PaymentMethodType paymentMethodType, + String storeUuid, + List paymentIntents, + List lineItems, + Address billingAddress, + Address shippingAddress, + String expiresAt, + String createdAt, + String storeName, + String businessName, + String paymentUrl, + Boolean isRefundableType + ) { + this.uuid = uuid; + this.paymentStatus = paymentStatus; + this.locale = locale; + this.merchantReference = merchantReference; + this.merchantReferenceDisplay = merchantReferenceDisplay; + this.merchantReturnUrl = merchantReturnUrl; + this.merchantNotificationUrl = merchantNotificationUrl; + this.grandTotal = grandTotal; + this.currency = currency; + this.paymentMethodType = paymentMethodType; + this.storeUuid = storeUuid; + this.paymentIntents = paymentIntents == null ? null : List.copyOf(paymentIntents); + this.lineItems = lineItems == null ? null : List.copyOf(lineItems); + this.billingAddress = billingAddress; + this.shippingAddress = shippingAddress; + this.expiresAt = expiresAt; + this.createdAt = createdAt; + this.storeName = storeName; + this.businessName = businessName; + this.paymentUrl = paymentUrl; + this.isRefundableType = isRefundableType; + } +} diff --git a/src/main/java/ee/bitweb/montonio/sdk/order/response/PaymentIntent.java b/src/main/java/ee/bitweb/montonio/sdk/order/response/PaymentIntent.java new file mode 100644 index 0000000..7ae8182 --- /dev/null +++ b/src/main/java/ee/bitweb/montonio/sdk/order/response/PaymentIntent.java @@ -0,0 +1,49 @@ +package ee.bitweb.montonio.sdk.order.response; + +import com.fasterxml.jackson.annotation.JsonCreator; +import ee.bitweb.montonio.sdk.model.Currency; +import ee.bitweb.montonio.sdk.model.PaymentMethodType; +import ee.bitweb.montonio.sdk.order.model.PaymentStatus; +import jakarta.annotation.Nullable; +import lombok.Getter; + +import java.util.Map; + +@Getter +public final class PaymentIntent { + + private final String uuid; + private final PaymentMethodType paymentMethodType; + private final String amount; + private final Currency currency; + private final PaymentStatus status; + private final String serviceFee; + private final Currency serviceFeeCurrency; + private final String createdAt; + + @Nullable + private final Map paymentMethodMetadata; + + @JsonCreator + public PaymentIntent( + String uuid, + PaymentMethodType paymentMethodType, + String amount, + Currency currency, + PaymentStatus status, + String serviceFee, + Currency serviceFeeCurrency, + String createdAt, + Map paymentMethodMetadata + ) { + this.uuid = uuid; + this.paymentMethodType = paymentMethodType; + this.amount = amount; + this.currency = currency; + this.status = status; + this.serviceFee = serviceFee; + this.serviceFeeCurrency = serviceFeeCurrency; + this.createdAt = createdAt; + this.paymentMethodMetadata = paymentMethodMetadata == null ? null : Map.copyOf(paymentMethodMetadata); + } +} diff --git a/src/test/java/ee/bitweb/montonio/sdk/model/CardPaymentMethodTest.java b/src/test/java/ee/bitweb/montonio/sdk/model/CardPaymentMethodTest.java new file mode 100644 index 0000000..1324fae --- /dev/null +++ b/src/test/java/ee/bitweb/montonio/sdk/model/CardPaymentMethodTest.java @@ -0,0 +1,40 @@ +package ee.bitweb.montonio.sdk.model; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class CardPaymentMethodTest { + + private final ObjectMapper mapper = new JsonMapper(); + + @Test + void enumContainsExpectedValues() { + assertEquals(2, CardPaymentMethod.values().length); + } + + @Test + void wireValues() { + assertEquals("card", CardPaymentMethod.CARD.getValue()); + assertEquals("wallet", CardPaymentMethod.WALLET.getValue()); + } + + @ParameterizedTest + @EnumSource(CardPaymentMethod.class) + void serializesToWireValue(CardPaymentMethod method) throws Exception { + String json = mapper.writeValueAsString(method); + assertEquals("\"" + method.getValue() + "\"", json); + } + + @ParameterizedTest + @EnumSource(CardPaymentMethod.class) + void deserializesFromWireValue(CardPaymentMethod method) throws Exception { + String json = "\"" + method.getValue() + "\""; + CardPaymentMethod result = mapper.readValue(json, CardPaymentMethod.class); + assertEquals(method, result); + } +} diff --git a/src/test/java/ee/bitweb/montonio/sdk/model/CurrencyTest.java b/src/test/java/ee/bitweb/montonio/sdk/model/CurrencyTest.java new file mode 100644 index 0000000..705b427 --- /dev/null +++ b/src/test/java/ee/bitweb/montonio/sdk/model/CurrencyTest.java @@ -0,0 +1,47 @@ +package ee.bitweb.montonio.sdk.model; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class CurrencyTest { + + private final ObjectMapper mapper = new JsonMapper(); + + @Test + void enumContainsExpectedValues() { + assertEquals(2, Currency.values().length); + assertNotNull(Currency.valueOf("EUR")); + assertNotNull(Currency.valueOf("PLN")); + } + + @Test + void eurHasCorrectWireValue() { + assertEquals("EUR", Currency.EUR.getValue()); + } + + @Test + void plnHasCorrectWireValue() { + assertEquals("PLN", Currency.PLN.getValue()); + } + + @ParameterizedTest + @EnumSource(Currency.class) + void serializesToWireValue(Currency currency) throws Exception { + String json = mapper.writeValueAsString(currency); + assertEquals("\"" + currency.getValue() + "\"", json); + } + + @ParameterizedTest + @EnumSource(Currency.class) + void deserializesFromWireValue(Currency currency) throws Exception { + String json = "\"" + currency.getValue() + "\""; + Currency result = mapper.readValue(json, Currency.class); + assertEquals(currency, result); + } +} diff --git a/src/test/java/ee/bitweb/montonio/sdk/model/LocaleTest.java b/src/test/java/ee/bitweb/montonio/sdk/model/LocaleTest.java new file mode 100644 index 0000000..344303f --- /dev/null +++ b/src/test/java/ee/bitweb/montonio/sdk/model/LocaleTest.java @@ -0,0 +1,46 @@ +package ee.bitweb.montonio.sdk.model; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class LocaleTest { + + private final ObjectMapper mapper = new JsonMapper(); + + @Test + void enumContainsExpectedValues() { + assertEquals(8, Locale.values().length); + } + + @Test + void wireValuesAreLowercase() { + assertEquals("de", Locale.DE.getValue()); + assertEquals("en", Locale.EN.getValue()); + assertEquals("et", Locale.ET.getValue()); + assertEquals("fi", Locale.FI.getValue()); + assertEquals("lt", Locale.LT.getValue()); + assertEquals("lv", Locale.LV.getValue()); + assertEquals("pl", Locale.PL.getValue()); + assertEquals("ru", Locale.RU.getValue()); + } + + @ParameterizedTest + @EnumSource(Locale.class) + void serializesToWireValue(Locale locale) throws Exception { + String json = mapper.writeValueAsString(locale); + assertEquals("\"" + locale.getValue() + "\"", json); + } + + @ParameterizedTest + @EnumSource(Locale.class) + void deserializesFromWireValue(Locale locale) throws Exception { + String json = "\"" + locale.getValue() + "\""; + Locale result = mapper.readValue(json, Locale.class); + assertEquals(locale, result); + } +} diff --git a/src/test/java/ee/bitweb/montonio/sdk/model/PaymentMethodTypeTest.java b/src/test/java/ee/bitweb/montonio/sdk/model/PaymentMethodTypeTest.java new file mode 100644 index 0000000..53175b8 --- /dev/null +++ b/src/test/java/ee/bitweb/montonio/sdk/model/PaymentMethodTypeTest.java @@ -0,0 +1,43 @@ +package ee.bitweb.montonio.sdk.model; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class PaymentMethodTypeTest { + + private final ObjectMapper mapper = new JsonMapper(); + + @Test + void enumContainsExpectedValues() { + assertEquals(5, PaymentMethodType.values().length); + } + + @Test + void wireValuesAreCamelCase() { + assertEquals("paymentInitiation", PaymentMethodType.PAYMENT_INITIATION.getValue()); + assertEquals("cardPayments", PaymentMethodType.CARD_PAYMENTS.getValue()); + assertEquals("blik", PaymentMethodType.BLIK.getValue()); + assertEquals("bnpl", PaymentMethodType.BNPL.getValue()); + assertEquals("hirePurchase", PaymentMethodType.HIRE_PURCHASE.getValue()); + } + + @ParameterizedTest + @EnumSource(PaymentMethodType.class) + void serializesToWireValue(PaymentMethodType type) throws Exception { + String json = mapper.writeValueAsString(type); + assertEquals("\"" + type.getValue() + "\"", json); + } + + @ParameterizedTest + @EnumSource(PaymentMethodType.class) + void deserializesFromWireValue(PaymentMethodType type) throws Exception { + String json = "\"" + type.getValue() + "\""; + PaymentMethodType result = mapper.readValue(json, PaymentMethodType.class); + assertEquals(type, result); + } +} diff --git a/src/test/java/ee/bitweb/montonio/sdk/model/WalletProviderTest.java b/src/test/java/ee/bitweb/montonio/sdk/model/WalletProviderTest.java new file mode 100644 index 0000000..7486cb1 --- /dev/null +++ b/src/test/java/ee/bitweb/montonio/sdk/model/WalletProviderTest.java @@ -0,0 +1,40 @@ +package ee.bitweb.montonio.sdk.model; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class WalletProviderTest { + + private final ObjectMapper mapper = new JsonMapper(); + + @Test + void enumContainsExpectedValues() { + assertEquals(2, WalletProvider.values().length); + } + + @Test + void wireValues() { + assertEquals("applePay", WalletProvider.APPLE_PAY.getValue()); + assertEquals("googlePay", WalletProvider.GOOGLE_PAY.getValue()); + } + + @ParameterizedTest + @EnumSource(WalletProvider.class) + void serializesToWireValue(WalletProvider provider) throws Exception { + String json = mapper.writeValueAsString(provider); + assertEquals("\"" + provider.getValue() + "\"", json); + } + + @ParameterizedTest + @EnumSource(WalletProvider.class) + void deserializesFromWireValue(WalletProvider provider) throws Exception { + String json = "\"" + provider.getValue() + "\""; + WalletProvider result = mapper.readValue(json, WalletProvider.class); + assertEquals(provider, result); + } +} diff --git a/src/test/java/ee/bitweb/montonio/sdk/order/model/AddressTest.java b/src/test/java/ee/bitweb/montonio/sdk/order/model/AddressTest.java new file mode 100644 index 0000000..4fd6d28 --- /dev/null +++ b/src/test/java/ee/bitweb/montonio/sdk/order/model/AddressTest.java @@ -0,0 +1,111 @@ +package ee.bitweb.montonio.sdk.order.model; + +import org.junit.jupiter.api.Test; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class AddressTest { + + private final ObjectMapper mapper = JsonMapper.builder() + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .build(); + + @Test + void buildWithAllFields() { + Address address = Address.builder() + .firstName("John") + .lastName("Doe") + .email("john@example.com") + .phoneNumber("123456789") + .phoneCountry("EE") + .addressLine1("Kai 1") + .addressLine2("Apt 2") + .locality("Tallinn") + .region("Harjumaa") + .postalCode("10111") + .country("EE") + .companyName("Acme") + .companyLegalName("Acme OÜ") + .companyRegCode("12345678") + .companyVatNumber("EE123456789") + .build(); + + assertEquals("John", address.getFirstName()); + assertEquals("Doe", address.getLastName()); + assertEquals("john@example.com", address.getEmail()); + assertEquals("123456789", address.getPhoneNumber()); + assertEquals("EE", address.getPhoneCountry()); + assertEquals("Kai 1", address.getAddressLine1()); + assertEquals("Apt 2", address.getAddressLine2()); + assertEquals("Tallinn", address.getLocality()); + assertEquals("Harjumaa", address.getRegion()); + assertEquals("10111", address.getPostalCode()); + assertEquals("EE", address.getCountry()); + assertEquals("Acme", address.getCompanyName()); + assertEquals("Acme OÜ", address.getCompanyLegalName()); + assertEquals("12345678", address.getCompanyRegCode()); + assertEquals("EE123456789", address.getCompanyVatNumber()); + } + + @Test + void buildWithNoFieldsDefaultsToNull() { + Address address = Address.builder().build(); + + assertNull(address.getFirstName()); + assertNull(address.getLastName()); + assertNull(address.getEmail()); + assertNull(address.getPhoneNumber()); + assertNull(address.getPhoneCountry()); + assertNull(address.getAddressLine1()); + assertNull(address.getAddressLine2()); + assertNull(address.getLocality()); + assertNull(address.getRegion()); + assertNull(address.getPostalCode()); + assertNull(address.getCountry()); + assertNull(address.getCompanyName()); + assertNull(address.getCompanyLegalName()); + assertNull(address.getCompanyRegCode()); + assertNull(address.getCompanyVatNumber()); + } + + @Test + void serializationRoundTrip() throws Exception { + Address address = Address.builder() + .firstName("John") + .lastName("Doe") + .email("john@example.com") + .addressLine1("Kai 1") + .locality("Tallinn") + .region("Harjumaa") + .postalCode("10111") + .country("EE") + .build(); + + String json = mapper.writeValueAsString(address); + Address deserialized = mapper.readValue(json, Address.class); + + assertEquals(address.getFirstName(), deserialized.getFirstName()); + assertEquals(address.getLastName(), deserialized.getLastName()); + assertEquals(address.getEmail(), deserialized.getEmail()); + assertEquals(address.getAddressLine1(), deserialized.getAddressLine1()); + assertEquals(address.getLocality(), deserialized.getLocality()); + assertEquals(address.getRegion(), deserialized.getRegion()); + assertEquals(address.getPostalCode(), deserialized.getPostalCode()); + assertEquals(address.getCountry(), deserialized.getCountry()); + } + + @Test + void deserializesWithUnknownFieldsIgnored() throws Exception { + String json = """ + {"firstName":"John","unknownField":"value"} + """; + + Address address = mapper.readValue(json, Address.class); + + assertEquals("John", address.getFirstName()); + } +} diff --git a/src/test/java/ee/bitweb/montonio/sdk/order/model/LineItemTest.java b/src/test/java/ee/bitweb/montonio/sdk/order/model/LineItemTest.java new file mode 100644 index 0000000..38e9b3d --- /dev/null +++ b/src/test/java/ee/bitweb/montonio/sdk/order/model/LineItemTest.java @@ -0,0 +1,101 @@ +package ee.bitweb.montonio.sdk.order.model; + +import ee.bitweb.montonio.sdk.exception.MontonioValidationException; +import org.junit.jupiter.api.Test; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + +import java.math.BigDecimal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class LineItemTest { + + private final ObjectMapper mapper = JsonMapper.builder() + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .build(); + + @Test + void buildWithAllFields() { + LineItem item = LineItem.builder() + .name("Hoverboard") + .quantity(new BigDecimal("2")) + .finalPrice(new BigDecimal("49.99")) + .build(); + + assertEquals("Hoverboard", item.getName()); + assertEquals(new BigDecimal("2"), item.getQuantity()); + assertEquals(new BigDecimal("49.99"), item.getFinalPrice()); + } + + @Test + void buildWithNullNameThrows() { + MontonioValidationException exception = assertThrows( + MontonioValidationException.class, + () -> LineItem.builder() + .quantity(BigDecimal.ONE) + .finalPrice(BigDecimal.TEN) + .build() + ); + + assertEquals("name", exception.getField()); + } + + @Test + void buildWithBlankNameThrows() { + MontonioValidationException exception = assertThrows( + MontonioValidationException.class, + () -> LineItem.builder() + .name(" ") + .quantity(BigDecimal.ONE) + .finalPrice(BigDecimal.TEN) + .build() + ); + + assertEquals("name", exception.getField()); + } + + @Test + void buildWithNullQuantityThrows() { + MontonioValidationException exception = assertThrows( + MontonioValidationException.class, + () -> LineItem.builder() + .name("Item") + .finalPrice(BigDecimal.TEN) + .build() + ); + + assertEquals("quantity", exception.getField()); + } + + @Test + void buildWithNullFinalPriceThrows() { + MontonioValidationException exception = assertThrows( + MontonioValidationException.class, + () -> LineItem.builder() + .name("Item") + .quantity(BigDecimal.ONE) + .build() + ); + + assertEquals("finalPrice", exception.getField()); + } + + @Test + void serializationRoundTrip() throws Exception { + LineItem item = LineItem.builder() + .name("Hoverboard") + .quantity(new BigDecimal("1")) + .finalPrice(new BigDecimal("100.00")) + .build(); + + String json = mapper.writeValueAsString(item); + LineItem deserialized = mapper.readValue(json, LineItem.class); + + assertEquals(item.getName(), deserialized.getName()); + assertEquals(0, item.getQuantity().compareTo(deserialized.getQuantity())); + assertEquals(0, item.getFinalPrice().compareTo(deserialized.getFinalPrice())); + } +} diff --git a/src/test/java/ee/bitweb/montonio/sdk/order/model/PaymentMethodOptionsTest.java b/src/test/java/ee/bitweb/montonio/sdk/order/model/PaymentMethodOptionsTest.java new file mode 100644 index 0000000..c7912e3 --- /dev/null +++ b/src/test/java/ee/bitweb/montonio/sdk/order/model/PaymentMethodOptionsTest.java @@ -0,0 +1,100 @@ +package ee.bitweb.montonio.sdk.order.model; + +import ee.bitweb.montonio.sdk.model.CardPaymentMethod; +import ee.bitweb.montonio.sdk.model.Locale; +import ee.bitweb.montonio.sdk.model.WalletProvider; +import org.junit.jupiter.api.Test; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class PaymentMethodOptionsTest { + + private final ObjectMapper mapper = JsonMapper.builder() + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .build(); + + @Test + void buildWithNoFieldsDefaultsToNull() { + PaymentMethodOptions options = PaymentMethodOptions.builder().build(); + + assertNull(options.getPreferredProvider()); + assertNull(options.getPreferredCountry()); + assertNull(options.getPreferredLocale()); + assertNull(options.getPreferredMethod()); + assertNull(options.getPreferredWallet()); + assertNull(options.getPaymentDescription()); + assertNull(options.getPaymentReference()); + assertNull(options.getPeriod()); + } + + @Test + void buildPaymentInitiationOptions() { + PaymentMethodOptions options = PaymentMethodOptions.builder() + .preferredProvider("LHVBEE22") + .preferredCountry("EE") + .preferredLocale(Locale.ET) + .paymentDescription("Payment for order 123") + .paymentReference("RF123456") + .build(); + + assertEquals("LHVBEE22", options.getPreferredProvider()); + assertEquals("EE", options.getPreferredCountry()); + assertEquals(Locale.ET, options.getPreferredLocale()); + assertEquals("Payment for order 123", options.getPaymentDescription()); + assertEquals("RF123456", options.getPaymentReference()); + } + + @Test + void buildCardPaymentOptions() { + PaymentMethodOptions options = PaymentMethodOptions.builder() + .preferredMethod(CardPaymentMethod.CARD) + .preferredLocale(Locale.EN) + .build(); + + assertEquals(CardPaymentMethod.CARD, options.getPreferredMethod()); + assertEquals(Locale.EN, options.getPreferredLocale()); + } + + @Test + void buildWalletOptions() { + PaymentMethodOptions options = PaymentMethodOptions.builder() + .preferredMethod(CardPaymentMethod.WALLET) + .preferredWallet(WalletProvider.APPLE_PAY) + .preferredLocale(Locale.EN) + .build(); + + assertEquals(CardPaymentMethod.WALLET, options.getPreferredMethod()); + assertEquals(WalletProvider.APPLE_PAY, options.getPreferredWallet()); + } + + @Test + void buildBnplOptions() { + PaymentMethodOptions options = PaymentMethodOptions.builder() + .period(3) + .build(); + + assertEquals(3, options.getPeriod()); + } + + @Test + void serializationRoundTrip() throws Exception { + PaymentMethodOptions options = PaymentMethodOptions.builder() + .preferredProvider("LHVBEE22") + .preferredCountry("EE") + .preferredLocale(Locale.ET) + .paymentDescription("Payment for order 123") + .build(); + + String json = mapper.writeValueAsString(options); + PaymentMethodOptions deserialized = mapper.readValue(json, PaymentMethodOptions.class); + + assertEquals(options.getPreferredProvider(), deserialized.getPreferredProvider()); + assertEquals(options.getPreferredCountry(), deserialized.getPreferredCountry()); + assertEquals(options.getPreferredLocale(), deserialized.getPreferredLocale()); + assertEquals(options.getPaymentDescription(), deserialized.getPaymentDescription()); + } +} diff --git a/src/test/java/ee/bitweb/montonio/sdk/order/model/PaymentStatusTest.java b/src/test/java/ee/bitweb/montonio/sdk/order/model/PaymentStatusTest.java new file mode 100644 index 0000000..60e3695 --- /dev/null +++ b/src/test/java/ee/bitweb/montonio/sdk/order/model/PaymentStatusTest.java @@ -0,0 +1,48 @@ +package ee.bitweb.montonio.sdk.order.model; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class PaymentStatusTest { + + private final ObjectMapper mapper = new JsonMapper(); + + @Test + void enumContainsExpectedValues() { + assertEquals(10, PaymentStatus.values().length); + } + + @Test + void wireValuesAreUpperCase() { + assertEquals("PENDING", PaymentStatus.PENDING.getValue()); + assertEquals("PAID", PaymentStatus.PAID.getValue()); + assertEquals("VOIDED", PaymentStatus.VOIDED.getValue()); + assertEquals("PARTIALLY_REFUNDED", PaymentStatus.PARTIALLY_REFUNDED.getValue()); + assertEquals("REFUNDED", PaymentStatus.REFUNDED.getValue()); + assertEquals("CANCELED", PaymentStatus.CANCELED.getValue()); + assertEquals("ABANDONED", PaymentStatus.ABANDONED.getValue()); + assertEquals("DECLINED", PaymentStatus.DECLINED.getValue()); + assertEquals("SETTLED", PaymentStatus.SETTLED.getValue()); + assertEquals("AUTHORIZED", PaymentStatus.AUTHORIZED.getValue()); + } + + @ParameterizedTest + @EnumSource(PaymentStatus.class) + void serializesToWireValue(PaymentStatus status) throws Exception { + String json = mapper.writeValueAsString(status); + assertEquals("\"" + status.getValue() + "\"", json); + } + + @ParameterizedTest + @EnumSource(PaymentStatus.class) + void deserializesFromWireValue(PaymentStatus status) throws Exception { + String json = "\"" + status.getValue() + "\""; + PaymentStatus result = mapper.readValue(json, PaymentStatus.class); + assertEquals(status, result); + } +} diff --git a/src/test/java/ee/bitweb/montonio/sdk/order/model/PaymentTest.java b/src/test/java/ee/bitweb/montonio/sdk/order/model/PaymentTest.java new file mode 100644 index 0000000..fb28155 --- /dev/null +++ b/src/test/java/ee/bitweb/montonio/sdk/order/model/PaymentTest.java @@ -0,0 +1,145 @@ +package ee.bitweb.montonio.sdk.order.model; + +import ee.bitweb.montonio.sdk.exception.MontonioValidationException; +import ee.bitweb.montonio.sdk.model.Currency; +import ee.bitweb.montonio.sdk.model.Locale; +import ee.bitweb.montonio.sdk.model.PaymentMethodType; +import org.junit.jupiter.api.Test; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + +import java.math.BigDecimal; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class PaymentTest { + + private final ObjectMapper mapper = JsonMapper.builder() + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .build(); + + @Test + void buildWithRequiredFieldsOnly() { + Payment payment = Payment.builder() + .method(PaymentMethodType.PAYMENT_INITIATION) + .currency(Currency.EUR) + .amount(new BigDecimal("100.00")) + .build(); + + assertEquals(PaymentMethodType.PAYMENT_INITIATION, payment.getMethod()); + assertEquals(Currency.EUR, payment.getCurrency()); + assertEquals(new BigDecimal("100.00"), payment.getAmount()); + assertNull(payment.getMethodOptions()); + assertNull(payment.getMethodDisplay()); + } + + @Test + void buildWithAllFields() { + PaymentMethodOptions options = PaymentMethodOptions.builder() + .preferredCountry("EE") + .preferredLocale(Locale.ET) + .build(); + + Payment payment = Payment.builder() + .method(PaymentMethodType.PAYMENT_INITIATION) + .currency(Currency.EUR) + .amount(new BigDecimal("100.00")) + .methodOptions(options) + .methodDisplay("Bank transfer") + .build(); + + assertEquals(PaymentMethodType.PAYMENT_INITIATION, payment.getMethod()); + assertEquals(Currency.EUR, payment.getCurrency()); + assertEquals(new BigDecimal("100.00"), payment.getAmount()); + assertEquals(options, payment.getMethodOptions()); + assertEquals("Bank transfer", payment.getMethodDisplay()); + } + + @Test + void buildWithNullMethodThrows() { + MontonioValidationException exception = assertThrows( + MontonioValidationException.class, + () -> Payment.builder() + .currency(Currency.EUR) + .amount(BigDecimal.TEN) + .build() + ); + + assertEquals("method", exception.getField()); + } + + @Test + void buildWithNullCurrencyThrows() { + MontonioValidationException exception = assertThrows( + MontonioValidationException.class, + () -> Payment.builder() + .method(PaymentMethodType.CARD_PAYMENTS) + .amount(BigDecimal.TEN) + .build() + ); + + assertEquals("currency", exception.getField()); + } + + @Test + void buildWithNullAmountThrows() { + MontonioValidationException exception = assertThrows( + MontonioValidationException.class, + () -> Payment.builder() + .method(PaymentMethodType.CARD_PAYMENTS) + .currency(Currency.EUR) + .build() + ); + + assertEquals("amount", exception.getField()); + } + + @Test + void buildWithZeroAmountThrows() { + MontonioValidationException exception = assertThrows( + MontonioValidationException.class, + () -> Payment.builder() + .method(PaymentMethodType.CARD_PAYMENTS) + .currency(Currency.EUR) + .amount(BigDecimal.ZERO) + .build() + ); + + assertEquals("amount", exception.getField()); + } + + @Test + void buildWithNegativeAmountThrows() { + MontonioValidationException exception = assertThrows( + MontonioValidationException.class, + () -> Payment.builder() + .method(PaymentMethodType.CARD_PAYMENTS) + .currency(Currency.EUR) + .amount(new BigDecimal("-1")) + .build() + ); + + assertEquals("amount", exception.getField()); + } + + @Test + void serializationRoundTrip() throws Exception { + Payment payment = Payment.builder() + .method(PaymentMethodType.CARD_PAYMENTS) + .currency(Currency.EUR) + .amount(new BigDecimal("50.00")) + .methodDisplay("Card") + .build(); + + String json = mapper.writeValueAsString(payment); + Payment deserialized = mapper.readValue(json, Payment.class); + + assertEquals(payment.getMethod(), deserialized.getMethod()); + assertEquals(payment.getCurrency(), deserialized.getCurrency()); + assertEquals(0, payment.getAmount().compareTo(deserialized.getAmount())); + assertEquals(payment.getMethodDisplay(), deserialized.getMethodDisplay()); + } +} diff --git a/src/test/java/ee/bitweb/montonio/sdk/order/request/CreateOrderRequestTest.java b/src/test/java/ee/bitweb/montonio/sdk/order/request/CreateOrderRequestTest.java new file mode 100644 index 0000000..ad7c903 --- /dev/null +++ b/src/test/java/ee/bitweb/montonio/sdk/order/request/CreateOrderRequestTest.java @@ -0,0 +1,297 @@ +package ee.bitweb.montonio.sdk.order.request; + +import ee.bitweb.montonio.sdk.exception.MontonioValidationException; +import ee.bitweb.montonio.sdk.model.Currency; +import ee.bitweb.montonio.sdk.model.Locale; +import ee.bitweb.montonio.sdk.model.PaymentMethodType; +import ee.bitweb.montonio.sdk.order.model.Address; +import ee.bitweb.montonio.sdk.order.model.LineItem; +import ee.bitweb.montonio.sdk.order.model.Payment; +import org.junit.jupiter.api.Test; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + +import java.math.BigDecimal; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class CreateOrderRequestTest { + + private final ObjectMapper mapper = JsonMapper.builder() + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .build(); + + private Payment validPayment() { + return Payment.builder() + .method(PaymentMethodType.PAYMENT_INITIATION) + .currency(Currency.EUR) + .amount(new BigDecimal("100.00")) + .build(); + } + + @Test + void buildWithRequiredFieldsOnly() { + CreateOrderRequest request = CreateOrderRequest.builder() + .merchantReference("order-123") + .returnUrl("https://example.com/return") + .notificationUrl("https://example.com/notify") + .grandTotal(new BigDecimal("100.00")) + .currency(Currency.EUR) + .payment(validPayment()) + .build(); + + assertEquals("order-123", request.getMerchantReference()); + assertEquals("https://example.com/return", request.getReturnUrl()); + assertEquals("https://example.com/notify", request.getNotificationUrl()); + assertEquals(new BigDecimal("100.00"), request.getGrandTotal()); + assertEquals(Currency.EUR, request.getCurrency()); + assertNotNull(request.getPayment()); + assertNull(request.getLocale()); + assertNull(request.getBillingAddress()); + assertNull(request.getShippingAddress()); + assertNull(request.getLineItems()); + } + + @Test + void buildWithAllFields() { + Address address = Address.builder() + .firstName("John") + .lastName("Doe") + .email("john@example.com") + .build(); + + LineItem item = LineItem.builder() + .name("Hoverboard") + .quantity(BigDecimal.ONE) + .finalPrice(new BigDecimal("100.00")) + .build(); + + CreateOrderRequest request = CreateOrderRequest.builder() + .merchantReference("order-123") + .returnUrl("https://example.com/return") + .notificationUrl("https://example.com/notify") + .grandTotal(new BigDecimal("100.00")) + .currency(Currency.EUR) + .payment(validPayment()) + .locale(Locale.ET) + .billingAddress(address) + .shippingAddress(address) + .lineItems(List.of(item)) + .build(); + + assertEquals(Locale.ET, request.getLocale()); + assertNotNull(request.getBillingAddress()); + assertNotNull(request.getShippingAddress()); + assertEquals(1, request.getLineItems().size()); + } + + @Test + void buildWithNullMerchantReferenceThrows() { + MontonioValidationException exception = assertThrows( + MontonioValidationException.class, + () -> CreateOrderRequest.builder() + .returnUrl("https://example.com/return") + .notificationUrl("https://example.com/notify") + .grandTotal(BigDecimal.TEN) + .currency(Currency.EUR) + .payment(validPayment()) + .build() + ); + + assertEquals("merchantReference", exception.getField()); + } + + @Test + void buildWithBlankMerchantReferenceThrows() { + MontonioValidationException exception = assertThrows( + MontonioValidationException.class, + () -> CreateOrderRequest.builder() + .merchantReference(" ") + .returnUrl("https://example.com/return") + .notificationUrl("https://example.com/notify") + .grandTotal(BigDecimal.TEN) + .currency(Currency.EUR) + .payment(validPayment()) + .build() + ); + + assertEquals("merchantReference", exception.getField()); + } + + @Test + void buildWithNullReturnUrlThrows() { + MontonioValidationException exception = assertThrows( + MontonioValidationException.class, + () -> CreateOrderRequest.builder() + .merchantReference("order-123") + .notificationUrl("https://example.com/notify") + .grandTotal(BigDecimal.TEN) + .currency(Currency.EUR) + .payment(validPayment()) + .build() + ); + + assertEquals("returnUrl", exception.getField()); + } + + @Test + void buildWithNullNotificationUrlThrows() { + MontonioValidationException exception = assertThrows( + MontonioValidationException.class, + () -> CreateOrderRequest.builder() + .merchantReference("order-123") + .returnUrl("https://example.com/return") + .grandTotal(BigDecimal.TEN) + .currency(Currency.EUR) + .payment(validPayment()) + .build() + ); + + assertEquals("notificationUrl", exception.getField()); + } + + @Test + void buildWithBlankReturnUrlThrows() { + MontonioValidationException exception = assertThrows( + MontonioValidationException.class, + () -> CreateOrderRequest.builder() + .merchantReference("order-123") + .returnUrl(" ") + .notificationUrl("https://example.com/notify") + .grandTotal(BigDecimal.TEN) + .currency(Currency.EUR) + .payment(validPayment()) + .build() + ); + + assertEquals("returnUrl", exception.getField()); + } + + @Test + void buildWithBlankNotificationUrlThrows() { + MontonioValidationException exception = assertThrows( + MontonioValidationException.class, + () -> CreateOrderRequest.builder() + .merchantReference("order-123") + .returnUrl("https://example.com/return") + .notificationUrl(" ") + .grandTotal(BigDecimal.TEN) + .currency(Currency.EUR) + .payment(validPayment()) + .build() + ); + + assertEquals("notificationUrl", exception.getField()); + } + + @Test + void buildWithNullGrandTotalThrows() { + MontonioValidationException exception = assertThrows( + MontonioValidationException.class, + () -> CreateOrderRequest.builder() + .merchantReference("order-123") + .returnUrl("https://example.com/return") + .notificationUrl("https://example.com/notify") + .currency(Currency.EUR) + .payment(validPayment()) + .build() + ); + + assertEquals("grandTotal", exception.getField()); + } + + @Test + void buildWithZeroGrandTotalThrows() { + MontonioValidationException exception = assertThrows( + MontonioValidationException.class, + () -> CreateOrderRequest.builder() + .merchantReference("order-123") + .returnUrl("https://example.com/return") + .notificationUrl("https://example.com/notify") + .grandTotal(BigDecimal.ZERO) + .currency(Currency.EUR) + .payment(validPayment()) + .build() + ); + + assertEquals("grandTotal", exception.getField()); + } + + @Test + void buildWithNegativeGrandTotalThrows() { + MontonioValidationException exception = assertThrows( + MontonioValidationException.class, + () -> CreateOrderRequest.builder() + .merchantReference("order-123") + .returnUrl("https://example.com/return") + .notificationUrl("https://example.com/notify") + .grandTotal(new BigDecimal("-1")) + .currency(Currency.EUR) + .payment(validPayment()) + .build() + ); + + assertEquals("grandTotal", exception.getField()); + } + + @Test + void buildWithNullCurrencyThrows() { + MontonioValidationException exception = assertThrows( + MontonioValidationException.class, + () -> CreateOrderRequest.builder() + .merchantReference("order-123") + .returnUrl("https://example.com/return") + .notificationUrl("https://example.com/notify") + .grandTotal(BigDecimal.TEN) + .payment(validPayment()) + .build() + ); + + assertEquals("currency", exception.getField()); + } + + @Test + void buildWithNullPaymentThrows() { + MontonioValidationException exception = assertThrows( + MontonioValidationException.class, + () -> CreateOrderRequest.builder() + .merchantReference("order-123") + .returnUrl("https://example.com/return") + .notificationUrl("https://example.com/notify") + .grandTotal(BigDecimal.TEN) + .currency(Currency.EUR) + .build() + ); + + assertEquals("payment", exception.getField()); + } + + @Test + void serializationRoundTrip() throws Exception { + CreateOrderRequest request = CreateOrderRequest.builder() + .merchantReference("order-123") + .returnUrl("https://example.com/return") + .notificationUrl("https://example.com/notify") + .grandTotal(new BigDecimal("100.00")) + .currency(Currency.EUR) + .payment(validPayment()) + .locale(Locale.ET) + .build(); + + String json = mapper.writeValueAsString(request); + CreateOrderRequest deserialized = mapper.readValue(json, CreateOrderRequest.class); + + assertEquals(request.getMerchantReference(), deserialized.getMerchantReference()); + assertEquals(request.getReturnUrl(), deserialized.getReturnUrl()); + assertEquals(request.getNotificationUrl(), deserialized.getNotificationUrl()); + assertEquals(0, request.getGrandTotal().compareTo(deserialized.getGrandTotal())); + assertEquals(request.getCurrency(), deserialized.getCurrency()); + assertEquals(request.getLocale(), deserialized.getLocale()); + assertEquals(request.getPayment().getMethod(), deserialized.getPayment().getMethod()); + } +} diff --git a/src/test/java/ee/bitweb/montonio/sdk/order/response/CreateOrderResponseTest.java b/src/test/java/ee/bitweb/montonio/sdk/order/response/CreateOrderResponseTest.java new file mode 100644 index 0000000..689833e --- /dev/null +++ b/src/test/java/ee/bitweb/montonio/sdk/order/response/CreateOrderResponseTest.java @@ -0,0 +1,71 @@ +package ee.bitweb.montonio.sdk.order.response; + +import org.junit.jupiter.api.Test; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class CreateOrderResponseTest { + + private final ObjectMapper mapper = JsonMapper.builder() + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .build(); + + @Test + void constructorSetsFields() { + CreateOrderResponse response = new CreateOrderResponse( + "550e8400-e29b-41d4-a716-446655440000", + "https://sandbox-stargate.montonio.com/pay" + ); + + assertEquals("550e8400-e29b-41d4-a716-446655440000", response.getUuid()); + assertEquals("https://sandbox-stargate.montonio.com/pay", response.getPaymentUrl()); + } + + @Test + void deserializesFromJson() throws Exception { + String json = """ + { + "uuid": "550e8400-e29b-41d4-a716-446655440000", + "paymentUrl": "https://sandbox-stargate.montonio.com/pay" + } + """; + + CreateOrderResponse response = mapper.readValue(json, CreateOrderResponse.class); + + assertEquals("550e8400-e29b-41d4-a716-446655440000", response.getUuid()); + assertEquals("https://sandbox-stargate.montonio.com/pay", response.getPaymentUrl()); + } + + @Test + void deserializesWithUnknownFieldsIgnored() throws Exception { + String json = """ + { + "uuid": "550e8400-e29b-41d4-a716-446655440000", + "paymentUrl": "https://example.com/pay", + "extraField": "ignored" + } + """; + + CreateOrderResponse response = mapper.readValue(json, CreateOrderResponse.class); + + assertEquals("550e8400-e29b-41d4-a716-446655440000", response.getUuid()); + assertEquals("https://example.com/pay", response.getPaymentUrl()); + } + + @Test + void serializationRoundTrip() throws Exception { + CreateOrderResponse original = new CreateOrderResponse( + "550e8400-e29b-41d4-a716-446655440000", + "https://example.com/pay" + ); + + String json = mapper.writeValueAsString(original); + CreateOrderResponse deserialized = mapper.readValue(json, CreateOrderResponse.class); + + assertEquals(original.getUuid(), deserialized.getUuid()); + assertEquals(original.getPaymentUrl(), deserialized.getPaymentUrl()); + } +} diff --git a/src/test/java/ee/bitweb/montonio/sdk/order/response/OrderResponseTest.java b/src/test/java/ee/bitweb/montonio/sdk/order/response/OrderResponseTest.java new file mode 100644 index 0000000..e91fa36 --- /dev/null +++ b/src/test/java/ee/bitweb/montonio/sdk/order/response/OrderResponseTest.java @@ -0,0 +1,210 @@ +package ee.bitweb.montonio.sdk.order.response; + +import ee.bitweb.montonio.sdk.model.Currency; +import ee.bitweb.montonio.sdk.model.Locale; +import ee.bitweb.montonio.sdk.model.PaymentMethodType; +import ee.bitweb.montonio.sdk.order.model.Address; +import ee.bitweb.montonio.sdk.order.model.LineItem; +import ee.bitweb.montonio.sdk.order.model.PaymentStatus; +import org.junit.jupiter.api.Test; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class OrderResponseTest { + + private final ObjectMapper mapper = JsonMapper.builder() + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .build(); + + @Test + void deserializesFullOrderResponse() throws Exception { + String json = """ + { + "uuid": "order-uuid", + "paymentStatus": "PAID", + "locale": "en", + "merchantReference": "Order1234567", + "merchantReferenceDisplay": "Order 1234567", + "merchantReturnUrl": "http://localhost:3000/return", + "merchantNotificationUrl": "http://example.com/notify", + "grandTotal": "100.00", + "currency": "EUR", + "paymentMethodType": "cardPayments", + "storeUuid": "store-uuid", + "paymentIntents": [ + { + "uuid": "intent-uuid", + "paymentMethodType": "cardPayments", + "paymentMethodMetadata": { + "preferredCountry": "US", + "preferredProvider": "Visa", + "paymentDescription": "Payment for Order 1234567" + }, + "amount": "100.00", + "currency": "EUR", + "status": "PAID", + "serviceFee": "0.00", + "serviceFeeCurrency": "EUR", + "createdAt": "2026-04-10T12:00:00Z" + } + ], + "lineItems": [ + { + "name": "Test Item", + "quantity": 1, + "finalPrice": 100 + } + ], + "billingAddress": { + "firstName": "John", + "lastName": "Doe", + "email": "john@example.com", + "country": "US" + }, + "shippingAddress": { + "firstName": "John", + "lastName": "Doe", + "country": "US" + }, + "expiresAt": "2026-04-10T12:10:00Z", + "createdAt": "2026-04-10T12:00:00Z", + "storeName": "Test Store", + "businessName": "Test Business", + "paymentUrl": "https://sandbox-stargate.montonio.com/pay", + "isRefundableType": true + } + """; + + OrderResponse response = mapper.readValue(json, OrderResponse.class); + + assertEquals("order-uuid", response.getUuid()); + assertEquals(PaymentStatus.PAID, response.getPaymentStatus()); + assertEquals(Locale.EN, response.getLocale()); + assertEquals("Order1234567", response.getMerchantReference()); + assertEquals("Order 1234567", response.getMerchantReferenceDisplay()); + assertEquals("http://localhost:3000/return", response.getMerchantReturnUrl()); + assertEquals("http://example.com/notify", response.getMerchantNotificationUrl()); + assertEquals("100.00", response.getGrandTotal()); + assertEquals(Currency.EUR, response.getCurrency()); + assertEquals(PaymentMethodType.CARD_PAYMENTS, response.getPaymentMethodType()); + assertEquals("store-uuid", response.getStoreUuid()); + + assertNotNull(response.getPaymentIntents()); + assertEquals(1, response.getPaymentIntents().size()); + assertEquals("intent-uuid", response.getPaymentIntents().get(0).getUuid()); + assertEquals(PaymentStatus.PAID, response.getPaymentIntents().get(0).getStatus()); + + assertNotNull(response.getLineItems()); + assertEquals(1, response.getLineItems().size()); + assertEquals("Test Item", response.getLineItems().get(0).getName()); + + assertEquals("John", response.getBillingAddress().getFirstName()); + assertEquals("John", response.getShippingAddress().getFirstName()); + + assertEquals("2026-04-10T12:10:00Z", response.getExpiresAt()); + assertEquals("2026-04-10T12:00:00Z", response.getCreatedAt()); + assertEquals("Test Store", response.getStoreName()); + assertEquals("Test Business", response.getBusinessName()); + assertEquals("https://sandbox-stargate.montonio.com/pay", response.getPaymentUrl()); + assertTrue(response.getIsRefundableType()); + } + + @Test + void deserializesWithUnknownFieldsIgnored() throws Exception { + String json = """ + { + "uuid": "order-uuid", + "paymentStatus": "PENDING", + "locale": "et", + "merchantReference": "ref", + "merchantReferenceDisplay": "ref", + "merchantReturnUrl": "http://example.com", + "merchantNotificationUrl": "http://example.com", + "grandTotal": "10.00", + "currency": "EUR", + "paymentMethodType": "paymentInitiation", + "storeUuid": "store", + "paymentIntents": [], + "lineItems": [], + "billingAddress": {}, + "shippingAddress": {}, + "expiresAt": "2026-04-10T12:10:00Z", + "createdAt": "2026-04-10T12:00:00Z", + "storeName": "Store", + "businessName": "Business", + "paymentUrl": "", + "refunds": [], + "availableForRefund": 0, + "unknownField": "ignored" + } + """; + + OrderResponse response = mapper.readValue(json, OrderResponse.class); + + assertEquals("order-uuid", response.getUuid()); + assertEquals(PaymentStatus.PENDING, response.getPaymentStatus()); + } + + @Test + void serializationRoundTrip() throws Exception { + OrderResponse original = new OrderResponse( + "order-uuid", + PaymentStatus.PAID, + Locale.EN, + "ref-123", + "Ref 123", + "http://example.com/return", + "http://example.com/notify", + "100.00", + Currency.EUR, + PaymentMethodType.CARD_PAYMENTS, + "store-uuid", + List.of(new PaymentIntent( + "intent-uuid", + PaymentMethodType.CARD_PAYMENTS, + "100.00", + Currency.EUR, + PaymentStatus.PAID, + "0.00", + Currency.EUR, + "2026-04-10T12:00:00Z", + Map.of("preferredCountry", "EE") + )), + List.of(LineItem.builder() + .name("Item") + .quantity(BigDecimal.ONE) + .finalPrice(new BigDecimal("100.00")) + .build()), + Address.builder().firstName("John").build(), + Address.builder().firstName("John").build(), + "2026-04-10T12:10:00Z", + "2026-04-10T12:00:00Z", + "Test Store", + "Test Business", + "https://example.com/pay", + true + ); + + String json = mapper.writeValueAsString(original); + OrderResponse deserialized = mapper.readValue(json, OrderResponse.class); + + assertEquals(original.getUuid(), deserialized.getUuid()); + assertEquals(original.getPaymentStatus(), deserialized.getPaymentStatus()); + assertEquals(original.getLocale(), deserialized.getLocale()); + assertEquals(original.getMerchantReference(), deserialized.getMerchantReference()); + assertEquals(original.getGrandTotal(), deserialized.getGrandTotal()); + assertEquals(original.getCurrency(), deserialized.getCurrency()); + assertEquals(original.getPaymentMethodType(), deserialized.getPaymentMethodType()); + assertEquals(1, deserialized.getPaymentIntents().size()); + assertEquals(1, deserialized.getLineItems().size()); + } +} diff --git a/src/test/java/ee/bitweb/montonio/sdk/order/response/PaymentIntentTest.java b/src/test/java/ee/bitweb/montonio/sdk/order/response/PaymentIntentTest.java new file mode 100644 index 0000000..39ab933 --- /dev/null +++ b/src/test/java/ee/bitweb/montonio/sdk/order/response/PaymentIntentTest.java @@ -0,0 +1,127 @@ +package ee.bitweb.montonio.sdk.order.response; + +import ee.bitweb.montonio.sdk.model.Currency; +import ee.bitweb.montonio.sdk.model.PaymentMethodType; +import ee.bitweb.montonio.sdk.order.model.PaymentStatus; +import org.junit.jupiter.api.Test; +import tools.jackson.databind.DeserializationFeature; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +class PaymentIntentTest { + + private final ObjectMapper mapper = JsonMapper.builder() + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + .build(); + + @Test + void constructorSetsAllFields() { + Map metadata = Map.of( + "preferredCountry", "EE", + "preferredProvider", "LHVBEE22" + ); + + PaymentIntent intent = new PaymentIntent( + "intent-uuid", + PaymentMethodType.PAYMENT_INITIATION, + "100.00", + Currency.EUR, + PaymentStatus.PAID, + "0.50", + Currency.EUR, + "2026-04-10T12:00:00Z", + metadata + ); + + assertEquals("intent-uuid", intent.getUuid()); + assertEquals(PaymentMethodType.PAYMENT_INITIATION, intent.getPaymentMethodType()); + assertEquals("100.00", intent.getAmount()); + assertEquals(Currency.EUR, intent.getCurrency()); + assertEquals(PaymentStatus.PAID, intent.getStatus()); + assertEquals("0.50", intent.getServiceFee()); + assertEquals(Currency.EUR, intent.getServiceFeeCurrency()); + assertEquals("2026-04-10T12:00:00Z", intent.getCreatedAt()); + assertNotNull(intent.getPaymentMethodMetadata()); + assertEquals("EE", intent.getPaymentMethodMetadata().get("preferredCountry")); + } + + @Test + void constructorWithNullMetadata() { + PaymentIntent intent = new PaymentIntent( + "intent-uuid", + PaymentMethodType.CARD_PAYMENTS, + "50.00", + Currency.EUR, + PaymentStatus.PENDING, + "0.00", + Currency.EUR, + "2026-04-10T12:00:00Z", + null + ); + + assertNull(intent.getPaymentMethodMetadata()); + } + + @Test + void deserializesFromJson() throws Exception { + String json = """ + { + "uuid": "intent-uuid", + "paymentMethodType": "cardPayments", + "amount": "100.00", + "currency": "EUR", + "status": "PAID", + "serviceFee": "0.50", + "serviceFeeCurrency": "EUR", + "createdAt": "2026-04-10T12:00:00Z", + "paymentMethodMetadata": { + "preferredCountry": "EE", + "preferredProvider": "Visa", + "paymentDescription": "Payment for order" + } + } + """; + + PaymentIntent intent = mapper.readValue(json, PaymentIntent.class); + + assertEquals("intent-uuid", intent.getUuid()); + assertEquals(PaymentMethodType.CARD_PAYMENTS, intent.getPaymentMethodType()); + assertEquals("100.00", intent.getAmount()); + assertEquals(Currency.EUR, intent.getCurrency()); + assertEquals(PaymentStatus.PAID, intent.getStatus()); + assertEquals("0.50", intent.getServiceFee()); + assertEquals("EE", intent.getPaymentMethodMetadata().get("preferredCountry")); + } + + @Test + void serializationRoundTrip() throws Exception { + PaymentIntent original = new PaymentIntent( + "intent-uuid", + PaymentMethodType.PAYMENT_INITIATION, + "100.00", + Currency.EUR, + PaymentStatus.AUTHORIZED, + "0.25", + Currency.EUR, + "2026-04-10T12:00:00Z", + Map.of("preferredCountry", "EE") + ); + + String json = mapper.writeValueAsString(original); + PaymentIntent deserialized = mapper.readValue(json, PaymentIntent.class); + + assertEquals(original.getUuid(), deserialized.getUuid()); + assertEquals(original.getPaymentMethodType(), deserialized.getPaymentMethodType()); + assertEquals(original.getAmount(), deserialized.getAmount()); + assertEquals(original.getCurrency(), deserialized.getCurrency()); + assertEquals(original.getStatus(), deserialized.getStatus()); + assertEquals(original.getServiceFee(), deserialized.getServiceFee()); + assertEquals(original.getServiceFeeCurrency(), deserialized.getServiceFeeCurrency()); + } +}