Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ plugins {
id("org.flywaydb.flyway") version "9.3.1"
id("org.springframework.boot") version "2.7.3"
id("io.spring.dependency-management") version "1.0.14.RELEASE"
kotlin("jvm") version "1.5.0"
kotlin("plugin.spring") version "1.5.0"
kotlin("jvm") version "1.8.22"
kotlin("plugin.spring") version "1.8.22"
application
distribution
}
Expand All @@ -27,20 +27,29 @@ repositories {
}

dependencies {
// Spring Boot Starters
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")

implementation("org.springdoc:springdoc-openapi-data-rest:1.6.11")
implementation("org.springdoc:springdoc-openapi-ui:1.6.11")
implementation("org.springdoc:springdoc-openapi-kotlin:1.6.11")

// Kotlin
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

// Database
runtimeOnly("org.postgresql:postgresql")

// Test Dependencies
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
testImplementation("com.h2database:h2")
testImplementation("org.mockito:mockito-inline:4.+")
}

flyway {
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
178 changes: 89 additions & 89 deletions gradlew.bat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/main/kotlin/io/billie/Application.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package io.billie

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.domain.EntityScan
import org.springframework.boot.runApplication
import org.springframework.context.annotation.ComponentScan

@SpringBootApplication
@ComponentScan(basePackages = ["io.billie.shipment"])
class Application

fun main(args: Array<String>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@ import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import org.springframework.http.HttpStatus
import org.springframework.http.HttpStatus.BAD_REQUEST
import org.springframework.web.bind.annotation.*
import org.springframework.web.server.ResponseStatusException
import java.util.*
import javax.validation.Valid


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.billie.shipment.application.command

import io.billie.shipment.domain.model.valueobjects.MerchantId
import io.billie.shipment.domain.model.valueobjects.Money
import io.billie.shipment.domain.model.valueobjects.OrderId

data class CreateShipmentCommand(
val orderId: OrderId,
val merchantId: MerchantId,
val amount: Money,
val trackingNumber: String? = null
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.billie.shipment.application.dto

import java.math.BigDecimal
import javax.validation.constraints.DecimalMin
import javax.validation.constraints.Digits
import javax.validation.constraints.NotBlank
import javax.validation.constraints.NotNull
import javax.validation.constraints.Pattern
import javax.validation.constraints.Size

data class CreateShipmentRequest(
@field:NotBlank(message = "Order ID cannot be blank")
@field:Size(max = 100, message = "Order ID cannot exceed 100 characters")
val orderId: String,

@field:NotNull(message = "Amount is required")
@field:DecimalMin(value = "0.01", message = "Amount must be positive")
@field:Digits(integer = 10, fraction = 2, message = "Invalid amount format")
val amount: BigDecimal,

@field:NotBlank(message = "Currency is required")
@field:Size(min = 3, max = 3, message = "Currency must be 3 characters (ISO 4217)")
@field:Pattern(regexp = "[A-Z]{3}", message = "Currency must be uppercase ISO 4217 code")
val currency: String,

@field:Size(max = 255, message = "Tracking number cannot exceed 255 characters")
val trackingNumber: String? = null
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.billie.shipment.application.dto

import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL
import java.math.BigDecimal

@JsonInclude(NON_NULL)
data class CreateShipmentResponse(
val success: Boolean,
val shipmentId: String? = null,
val transactionId: String? = null,
val remainingAmount: BigDecimal? = null,
val currency: String? = null,
val errorMessage: String? = null
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.billie.shipment.application.result

import io.billie.shipment.domain.model.valueobjects.Money
import io.billie.shipment.domain.model.valueobjects.ShipmentId
import io.billie.shipment.domain.service.PaymentResult

data class ShipmentResult(
val shipmentId: ShipmentId,
val paymentResult: PaymentResult,
val remainingAmount: Money
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.billie.shipment.domain.events

import java.time.Instant

sealed class DomainEvent {
abstract val occurredAt: Instant
abstract val aggregateId: String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.billie.shipment.domain.events

import io.billie.shipment.domain.model.valueobjects.MerchantId
import io.billie.shipment.domain.model.valueobjects.OrderId
import java.time.Instant

data class OrderFullyShippedEvent(
override val aggregateId: String,
val orderId: OrderId,
val merchantId: MerchantId,
override val occurredAt: Instant = Instant.now()
) : DomainEvent()
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.billie.shipment.domain.events

import io.billie.shipment.domain.model.valueobjects.MerchantId
import io.billie.shipment.domain.model.valueobjects.Money
import io.billie.shipment.domain.model.valueobjects.OrderId
import io.billie.shipment.domain.model.valueobjects.ShipmentId
import java.time.Instant

data class ShipmentCreatedEvent(
override val aggregateId: String,
val shipmentId: ShipmentId,
val orderId: OrderId,
val merchantId: MerchantId,
val amount: Money,
override val occurredAt: Instant = Instant.now()
) : DomainEvent()
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.billie.shipment.domain.exceptions

class InsufficientOrderAmountException(message: String) :
RuntimeException(message)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.billie.shipment.domain.exceptions

import io.billie.shipment.domain.model.valueobjects.OrderId

class OrderNotFoundException(orderId: OrderId) :
RuntimeException("Order not found: ${orderId.value}")
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.billie.shipment.domain.exceptions

import io.billie.shipment.domain.model.valueobjects.MerchantId
import io.billie.shipment.domain.model.valueobjects.OrderId

class UnauthorizedMerchantException(merchantId: MerchantId, orderId: OrderId) :
RuntimeException("Merchant ${merchantId.value} not authorized for order ${orderId.value}")
Loading