diff --git a/backend/individual-assignment/challenge/.gitignore b/backend/individual-assignment/challenge/.gitignore
new file mode 100644
index 00000000..b63da455
--- /dev/null
+++ b/backend/individual-assignment/challenge/.gitignore
@@ -0,0 +1,42 @@
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+out/
+!**/src/main/**/out/
+!**/src/test/**/out/
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+bin/
+!**/src/main/**/bin/
+!**/src/test/**/bin/
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file
diff --git a/backend/individual-assignment/challenge/build.gradle.kts b/backend/individual-assignment/challenge/build.gradle.kts
new file mode 100644
index 00000000..47087801
--- /dev/null
+++ b/backend/individual-assignment/challenge/build.gradle.kts
@@ -0,0 +1,18 @@
+plugins {
+ kotlin("jvm") version "2.0.0"
+}
+
+group = "org.example"
+version = "1.0-SNAPSHOT"
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation(kotlin("test"))
+}
+
+tasks.test {
+ useJUnitPlatform()
+}
\ No newline at end of file
diff --git a/backend/individual-assignment/challenge/explain.md b/backend/individual-assignment/challenge/explain.md
new file mode 100644
index 00000000..1f64a1e3
--- /dev/null
+++ b/backend/individual-assignment/challenge/explain.md
@@ -0,0 +1,39 @@
+# Hello, My Dear (and Maybe Future Leader and Co-worker)
+
+Even though the entire process so far has been in Portuguese, I am providing this document in English to encourage a broader mindset. I hope you enjoy the code! :3
+
+## Problem
+
+You are reviewing the design decisions of software that processes online orders. For these orders, payments are handled based on specific situations:
+
+- If the payment is for a physical item, a shipping label must be generated and placed in the shipping box.
+- If the payment is for a service subscription, the subscription must be activated, and the user notified via email.
+- If the payment is for an ordinary book, a shipping label must be generated with a notification that it is a tax-exempt item as provided in the Constitution Art. 150, VI, d.
+- If the payment is for any digital media (music, video), in addition to sending a purchase confirmation email to the buyer, a $10 discount voucher must be granted to the buyer associated with the payment.
+
+## Solution
+
+A new method `processProduct` was added to the `Order` class, which calls auxiliary functions that are executed based on the provided `Enum` type, similar to a map. You can check these functions in `kotlin/extension/ProductTypeProcess.kt`.
+
+After the payment is set, we must proceed with the shipping or provisioning process. Although it was not implemented, it is noted that a database transactional approach would be ideal to cancel the payment in case of an internal error during the shipping process.
+
+Regarding the process functions, an expanding approach was attempted. They route to classes that process the order information and utilize their specific data:
+
+- **physicalProcess**: Address validation was set to demonstrate exception test cases. The shipping method is the same and can extend the possible taxes by using a list of an enum, placing them in order to make them exempt from the given tax.
+- **bookProcess**: This method uses the same function as `physicalProcess`, but passes `Taxes.SHIP_ART_150` to make it exempt.
+- **digitalProcess**: Calls `DigitalPurchaseImpl`, which has the `sendMailWithVoucher` function, receiving a `SendEmailImpl`. `SendEmailImpl` is a black box that creates the email body, with or without a voucher. `DigitalProcess` has a companion object with a default voucher, which can be changed according to the situation, making it easier to provide random vouchers or larger vouchers based on the number of purchases.
+- **membershipProcess**: Also calls `SendEmailImpl`, and uses `MembershipImpl`, which validates the customer, checks if they are already a member, and proceeds accordingly before sending the email. If the business decides that the member should now receive a voucher, it can be easily changed.
+
+SOLID principles were applied, ensuring that functions have single responsibilities, adhere to Open/Closed principles, etc.
+
+The project structure follows Kotlin patterns.
+
+The test class has a 'Fixture' folder, which I prefer to create for objects, as they can become more complex, only copying certain values and changing them.
+
+**Note:**
+The `Order` class would be better as a Data Class, with an implementation that receives an `OrderDto` and deals with its data, avoiding a larger fixture class for tests as we could use the `.copy()` method.
+
+Whether we proceed with this process or not, let’s connect and maybe share knowledge! ^~^
+[LinkedIn](https://www.linkedin.com/in/gabrielteixeirasoares/)
+
+See ya!
diff --git a/backend/individual-assignment/challenge/gradle.properties b/backend/individual-assignment/challenge/gradle.properties
new file mode 100644
index 00000000..7fc6f1ff
--- /dev/null
+++ b/backend/individual-assignment/challenge/gradle.properties
@@ -0,0 +1 @@
+kotlin.code.style=official
diff --git a/backend/individual-assignment/challenge/gradle/wrapper/gradle-wrapper.jar b/backend/individual-assignment/challenge/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 00000000..249e5832
Binary files /dev/null and b/backend/individual-assignment/challenge/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/backend/individual-assignment/challenge/gradle/wrapper/gradle-wrapper.properties b/backend/individual-assignment/challenge/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 00000000..b55f55f1
--- /dev/null
+++ b/backend/individual-assignment/challenge/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Aug 13 20:31:16 BRT 2024
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/backend/individual-assignment/challenge/gradlew b/backend/individual-assignment/challenge/gradlew
new file mode 100644
index 00000000..1b6c7873
--- /dev/null
+++ b/backend/individual-assignment/challenge/gradlew
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/backend/individual-assignment/challenge/gradlew.bat b/backend/individual-assignment/challenge/gradlew.bat
new file mode 100644
index 00000000..107acd32
--- /dev/null
+++ b/backend/individual-assignment/challenge/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/backend/individual-assignment/challenge/settings.gradle.kts b/backend/individual-assignment/challenge/settings.gradle.kts
new file mode 100644
index 00000000..b4e2e9d6
--- /dev/null
+++ b/backend/individual-assignment/challenge/settings.gradle.kts
@@ -0,0 +1,5 @@
+plugins {
+ id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
+}
+rootProject.name = "challenge"
+
diff --git a/backend/individual-assignment/challenge/src/main/kotlin/Main.kt b/backend/individual-assignment/challenge/src/main/kotlin/Main.kt
new file mode 100644
index 00000000..496d2331
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/main/kotlin/Main.kt
@@ -0,0 +1,39 @@
+package org.example
+
+import org.example.dtos.Address
+import org.example.dtos.CreditCard
+import org.example.dtos.Customer
+import org.example.dtos.Product
+import org.example.enums.ProductType
+import org.example.models.impl.Order
+
+//TIP To Run code, press or
+// click the icon in the gutter.
+fun main(args : Array) {
+ val shirt = Product("Flowered t-shirt", ProductType.PHYSICAL, 35.00)
+ val netflix = Product("Familiar plan", ProductType.MEMBERSHIP, 29.90)
+ val book = Product("The Hitchhiker's Guide to the Galaxy", ProductType.BOOK, 120.00)
+ val music = Product("Stairway to Heaven", ProductType.DIGITAL, 5.00)
+
+ val order = Order(
+ Customer(
+ isMember = false,
+ email = "batatoncio.solanum-tuberosum@wilsomclaudio.com"
+ ),
+ Address(
+ street = "Here"
+ )
+ )
+
+ order.addProduct(shirt, 2)
+ order.addProduct(netflix, 1)
+ order.addProduct(book, 1)
+ order.addProduct(music, 1)
+
+ // In this scenario, we may use database instructions so that if the processProduct fail, the payment will rollback
+ order.pay(CreditCard("43567890-987654367"))
+
+ // Depending on how long does it take to some of these methods to be run, queue processors may be handy
+ // The carefulness needed is if process method throws something, so that a payment rollback will be needed
+ order.processProduct()
+}
\ No newline at end of file
diff --git a/backend/individual-assignment/challenge/src/main/kotlin/dtos/Address.kt b/backend/individual-assignment/challenge/src/main/kotlin/dtos/Address.kt
new file mode 100644
index 00000000..666d323a
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/main/kotlin/dtos/Address.kt
@@ -0,0 +1,5 @@
+package org.example.dtos
+
+data class Address(
+ val street: String
+)
\ No newline at end of file
diff --git a/backend/individual-assignment/challenge/src/main/kotlin/dtos/CreditCard.kt b/backend/individual-assignment/challenge/src/main/kotlin/dtos/CreditCard.kt
new file mode 100644
index 00000000..36dafa41
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/main/kotlin/dtos/CreditCard.kt
@@ -0,0 +1,7 @@
+package org.example.dtos
+
+import org.example.models.PaymentMethod
+
+data class CreditCard(
+ val number: String
+) : PaymentMethod
diff --git a/backend/individual-assignment/challenge/src/main/kotlin/dtos/Customer.kt b/backend/individual-assignment/challenge/src/main/kotlin/dtos/Customer.kt
new file mode 100644
index 00000000..fc1ff3bf
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/main/kotlin/dtos/Customer.kt
@@ -0,0 +1,6 @@
+package org.example.dtos
+
+data class Customer (
+ val isMember: Boolean,
+ val email: String
+)
\ No newline at end of file
diff --git a/backend/individual-assignment/challenge/src/main/kotlin/dtos/Invoice.kt b/backend/individual-assignment/challenge/src/main/kotlin/dtos/Invoice.kt
new file mode 100644
index 00000000..28211a33
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/main/kotlin/dtos/Invoice.kt
@@ -0,0 +1,8 @@
+package org.example.dtos
+
+import org.example.models.impl.Order
+
+data class Invoice(val order: Order) {
+ val billingAddress: Address = order.address
+ val shippingAddress: Address = order.address
+}
diff --git a/backend/individual-assignment/challenge/src/main/kotlin/dtos/OrderItem.kt b/backend/individual-assignment/challenge/src/main/kotlin/dtos/OrderItem.kt
new file mode 100644
index 00000000..1de15e21
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/main/kotlin/dtos/OrderItem.kt
@@ -0,0 +1,8 @@
+package org.example.dtos
+
+data class OrderItem(
+ val product: Product,
+ val quantity: Int
+) {
+ val total get() = product.price * quantity
+}
diff --git a/backend/individual-assignment/challenge/src/main/kotlin/dtos/Payment.kt b/backend/individual-assignment/challenge/src/main/kotlin/dtos/Payment.kt
new file mode 100644
index 00000000..079a323e
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/main/kotlin/dtos/Payment.kt
@@ -0,0 +1,15 @@
+package org.example.dtos
+
+import org.example.models.PaymentMethod
+import org.example.models.impl.Order
+import java.util.*
+
+data class Payment(
+ val order: Order,
+ val paymentMethod: PaymentMethod
+) {
+ val paidAt = Date()
+ val authorizationNumber = paidAt.time
+ val amount = order.totalAmount
+ val invoice = Invoice(order)
+}
diff --git a/backend/individual-assignment/challenge/src/main/kotlin/dtos/Product.kt b/backend/individual-assignment/challenge/src/main/kotlin/dtos/Product.kt
new file mode 100644
index 00000000..230cce2f
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/main/kotlin/dtos/Product.kt
@@ -0,0 +1,9 @@
+package org.example.dtos
+
+import org.example.enums.ProductType
+
+data class Product(
+ val name: String,
+ val type: ProductType,
+ val price: Double
+)
diff --git a/backend/individual-assignment/challenge/src/main/kotlin/dtos/Voucher.kt b/backend/individual-assignment/challenge/src/main/kotlin/dtos/Voucher.kt
new file mode 100644
index 00000000..63d6ec46
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/main/kotlin/dtos/Voucher.kt
@@ -0,0 +1,5 @@
+package org.example.dtos
+
+data class Voucher(
+ val discountE4: Long
+)
diff --git a/backend/individual-assignment/challenge/src/main/kotlin/enums/ProductType.kt b/backend/individual-assignment/challenge/src/main/kotlin/enums/ProductType.kt
new file mode 100644
index 00000000..22ec9656
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/main/kotlin/enums/ProductType.kt
@@ -0,0 +1,8 @@
+package org.example.enums
+
+enum class ProductType {
+ PHYSICAL,
+ BOOK,
+ DIGITAL,
+ MEMBERSHIP
+}
\ No newline at end of file
diff --git a/backend/individual-assignment/challenge/src/main/kotlin/enums/Taxes.kt b/backend/individual-assignment/challenge/src/main/kotlin/enums/Taxes.kt
new file mode 100644
index 00000000..7a3ea5d8
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/main/kotlin/enums/Taxes.kt
@@ -0,0 +1,5 @@
+package org.example.enums
+
+enum class Taxes {
+ SHIP_ART_150
+}
\ No newline at end of file
diff --git a/backend/individual-assignment/challenge/src/main/kotlin/extension/AddressExtensions.kt b/backend/individual-assignment/challenge/src/main/kotlin/extension/AddressExtensions.kt
new file mode 100644
index 00000000..95f4ba30
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/main/kotlin/extension/AddressExtensions.kt
@@ -0,0 +1,10 @@
+package org.example.extension
+
+import org.example.dtos.Address
+
+fun Address.isValid(): Boolean {
+ if(this.street == "nowhere"){
+ return false
+ }
+ return true
+}
\ No newline at end of file
diff --git a/backend/individual-assignment/challenge/src/main/kotlin/extension/ProductTypeProcess.kt b/backend/individual-assignment/challenge/src/main/kotlin/extension/ProductTypeProcess.kt
new file mode 100644
index 00000000..2fee1e4b
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/main/kotlin/extension/ProductTypeProcess.kt
@@ -0,0 +1,47 @@
+package org.example.extension
+
+import org.example.enums.Taxes
+import org.example.models.impl.*
+
+fun physicalProcess(
+ order: Order
+): String {
+ if(!order.address.isValid()){
+ throw Exception("Address not valid")
+ }
+ return ShippingLabelImpl().ship(
+ address = order.address,
+ customer = order.customer,
+ exemptTaxes = emptyList()
+ )
+}
+
+fun bookProcess(
+ order: Order
+): String {
+ return ShippingLabelImpl().ship(
+ address = order.address,
+ customer = order.customer,
+ exemptTaxes = listOf(Taxes.SHIP_ART_150)
+ )
+}
+
+fun digitalProcess(
+ order: Order
+): String {
+ return DigitalPurchaseImpl(
+ SendEmailImpl()
+ ).sendMailWithVoucher(
+ order.customer
+ )
+}
+
+fun membershipProcess(
+ order: Order
+): String {
+ return MembershipImpl(
+ SendEmailImpl()
+ ).activateMembership(
+ order.customer
+ )
+}
\ No newline at end of file
diff --git a/backend/individual-assignment/challenge/src/main/kotlin/models/DigitalPurchase.kt b/backend/individual-assignment/challenge/src/main/kotlin/models/DigitalPurchase.kt
new file mode 100644
index 00000000..4c687967
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/main/kotlin/models/DigitalPurchase.kt
@@ -0,0 +1,7 @@
+package org.example.models
+
+import org.example.dtos.Customer
+
+interface DigitalPurchase{
+ fun sendMailWithVoucher(customer: Customer): String
+}
\ No newline at end of file
diff --git a/backend/individual-assignment/challenge/src/main/kotlin/models/Membership.kt b/backend/individual-assignment/challenge/src/main/kotlin/models/Membership.kt
new file mode 100644
index 00000000..6a03a577
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/main/kotlin/models/Membership.kt
@@ -0,0 +1,7 @@
+package org.example.models
+
+import org.example.dtos.Customer
+
+interface Membership {
+ fun activateMembership(customer: Customer): String
+}
\ No newline at end of file
diff --git a/backend/individual-assignment/challenge/src/main/kotlin/models/PaymentMethod.kt b/backend/individual-assignment/challenge/src/main/kotlin/models/PaymentMethod.kt
new file mode 100644
index 00000000..607017aa
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/main/kotlin/models/PaymentMethod.kt
@@ -0,0 +1,4 @@
+package org.example.models
+
+interface PaymentMethod {
+}
\ No newline at end of file
diff --git a/backend/individual-assignment/challenge/src/main/kotlin/models/SendEmail.kt b/backend/individual-assignment/challenge/src/main/kotlin/models/SendEmail.kt
new file mode 100644
index 00000000..7e3d16e1
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/main/kotlin/models/SendEmail.kt
@@ -0,0 +1,10 @@
+package org.example.models
+
+import org.example.dtos.Voucher
+
+interface SendEmail {
+ fun sendEmail(
+ email: String,
+ voucher: Voucher? = null
+ ): String
+}
\ No newline at end of file
diff --git a/backend/individual-assignment/challenge/src/main/kotlin/models/ShippingLabel.kt b/backend/individual-assignment/challenge/src/main/kotlin/models/ShippingLabel.kt
new file mode 100644
index 00000000..507364a4
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/main/kotlin/models/ShippingLabel.kt
@@ -0,0 +1,14 @@
+package org.example.models
+
+import org.example.dtos.Address
+import org.example.dtos.Customer
+import org.example.enums.Taxes
+
+interface ShippingLabel {
+ fun ship(
+ customer: Customer,
+ address: Address,
+ exemptTaxes: List
+ ): String
+}
+
diff --git a/backend/individual-assignment/challenge/src/main/kotlin/models/impl/DigitalPurchaseImpl.kt b/backend/individual-assignment/challenge/src/main/kotlin/models/impl/DigitalPurchaseImpl.kt
new file mode 100644
index 00000000..db4d054f
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/main/kotlin/models/impl/DigitalPurchaseImpl.kt
@@ -0,0 +1,25 @@
+package org.example.models.impl
+
+import org.example.dtos.Voucher
+import org.example.dtos.Customer
+import org.example.models.DigitalPurchase
+import org.example.models.SendEmail
+
+class DigitalPurchaseImpl(
+ val emailSender : SendEmail
+): DigitalPurchase {
+ override fun sendMailWithVoucher(customer: Customer): String{
+ emailSender.sendEmail(
+ customer.email,
+ defaultVaucher
+ )
+
+ return "The customer received a email with voucher"
+ }
+
+ companion object{
+ private val defaultVaucher = Voucher(
+ discountE4 = 1000
+ )
+ }
+}
\ No newline at end of file
diff --git a/backend/individual-assignment/challenge/src/main/kotlin/models/impl/MembershipImpl.kt b/backend/individual-assignment/challenge/src/main/kotlin/models/impl/MembershipImpl.kt
new file mode 100644
index 00000000..d59525da
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/main/kotlin/models/impl/MembershipImpl.kt
@@ -0,0 +1,25 @@
+package org.example.models.impl
+
+import org.example.dtos.Voucher
+import org.example.dtos.Customer
+import org.example.models.Membership
+import org.example.models.SendEmail
+
+class MembershipImpl(
+ val emailSender : SendEmail
+): Membership {
+
+ override fun activateMembership(customer: Customer): String {
+ if(customer.isMember) {
+ println("The member is already a member, the expirationDate will be increased")
+ return "The member is already a member, the expirationDate will be increased"
+ } else {
+ println("The client will have his membership activated")
+ emailSender.sendEmail(
+ email = customer.email
+ )
+
+ return "The Customer is now a member"
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/individual-assignment/challenge/src/main/kotlin/models/impl/Order.kt b/backend/individual-assignment/challenge/src/main/kotlin/models/impl/Order.kt
new file mode 100644
index 00000000..ffbadff9
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/main/kotlin/models/impl/Order.kt
@@ -0,0 +1,72 @@
+package org.example.models.impl
+
+import org.example.dtos.*
+import org.example.enums.*
+import org.example.extension.bookProcess
+import org.example.extension.digitalProcess
+import org.example.extension.membershipProcess
+import org.example.extension.physicalProcess
+import org.example.models.PaymentMethod
+import java.util.*
+
+class Order(
+ val customer: Customer,
+ val address: Address
+) {
+ private val items = mutableListOf()
+ var closedAt: Date? = null
+ private set
+ var payment: Payment? = null
+ private set
+ val totalAmount
+ get() = items.sumOf { it.total }
+
+ fun addProduct(
+ product: Product,
+ quantity: Int
+ ) {
+ val productAlreadyAdded = items.any { it.product == product }
+ if (productAlreadyAdded)
+ throw Exception("The product have already been added. Change the amount if you want more.")
+
+ items.add(OrderItem(product, quantity))
+ }
+
+ fun pay(method: PaymentMethod) {
+ if (payment != null) {
+ throw Exception("The order has already been paid!")
+ }
+
+ if (items.isEmpty()) {
+ throw Exception("Empty order can not be paid!")
+ }
+
+ payment = Payment(this, method)
+
+ close()
+ }
+
+ fun processProduct() {
+ items.forEach{
+ item ->
+ when(item.product.type) {
+ ProductType.BOOK -> bookProcess(
+ order = this
+ )
+ ProductType.PHYSICAL -> physicalProcess(
+ order = this
+ )
+ ProductType.DIGITAL -> digitalProcess(
+ order = this
+ )
+ ProductType.MEMBERSHIP -> membershipProcess(
+ order = this
+ )
+ }
+ }
+ }
+
+ private fun close() {
+ closedAt = Date()
+ }
+}
\ No newline at end of file
diff --git a/backend/individual-assignment/challenge/src/main/kotlin/models/impl/SendEmailImpl.kt b/backend/individual-assignment/challenge/src/main/kotlin/models/impl/SendEmailImpl.kt
new file mode 100644
index 00000000..2af8109c
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/main/kotlin/models/impl/SendEmailImpl.kt
@@ -0,0 +1,14 @@
+package org.example.models.impl
+
+import org.example.dtos.Voucher
+import org.example.models.SendEmail
+
+class SendEmailImpl: SendEmail {
+ override fun sendEmail(
+ email: String,
+ voucher: Voucher?
+ ): String {
+ println("Method that will make email body, with whatever tool")
+ return "Method that will make email body, with whatever tool"
+ }
+}
\ No newline at end of file
diff --git a/backend/individual-assignment/challenge/src/main/kotlin/models/impl/ShippingLabelImpl.kt b/backend/individual-assignment/challenge/src/main/kotlin/models/impl/ShippingLabelImpl.kt
new file mode 100644
index 00000000..080e0d17
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/main/kotlin/models/impl/ShippingLabelImpl.kt
@@ -0,0 +1,22 @@
+package org.example.models.impl
+
+import org.example.enums.Taxes
+import org.example.dtos.Address
+import org.example.dtos.Customer
+import org.example.models.ShippingLabel
+
+class ShippingLabelImpl: ShippingLabel {
+ override fun ship(
+ customer: Customer,
+ address: Address,
+ exemptTaxes: List
+ ): String {
+ if(exemptTaxes.isEmpty()){
+ println("The object was shipped with taxes")
+ return "The object was shipped with taxes"
+ } else {
+ println("The object was shipped with NO taxes")
+ return "The object was shipped with NO taxes"
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/individual-assignment/challenge/src/test/kotlin/DigitalPurchaseTest.kt b/backend/individual-assignment/challenge/src/test/kotlin/DigitalPurchaseTest.kt
new file mode 100644
index 00000000..008b4ea4
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/test/kotlin/DigitalPurchaseTest.kt
@@ -0,0 +1,54 @@
+import fixtures.CustomerFixture
+import org.example.models.impl.DigitalPurchaseImpl
+import org.example.models.impl.SendEmailImpl
+import org.junit.jupiter.api.Test
+import kotlin.test.assertEquals
+
+class DigitalPurchaseTest {
+ //Other classes will not be tested due to time limit...
+ val emailSenderMockk = SendEmailImpl() // This was supposed to be mockk()
+ val digitalImpl = DigitalPurchaseImpl(emailSenderMockk)
+
+ @Test
+ fun `(sendEmail) Shall send email with voucher`(){
+ val customer = CustomerFixture().customer
+
+ /*
+ every {
+ emailSenderMockk.sendEmail()
+ } returns "success"
+ */
+
+ val response = digitalImpl.sendMailWithVoucher(
+ customer = customer
+ )
+
+ assertEquals("The customer received a email with voucher",response)
+ }
+
+ @Test
+ fun `(sendEmail) Rethrow error if emailSender throws`(){
+ val customer = CustomerFixture().customer
+
+
+ /*
+ every {
+ emailSenderMockk.sendEmail()
+ } throws Exception("Fon")
+ */
+
+ /*
+ assertFail when calling sendMailWithVoucher
+ */
+
+ val response = digitalImpl.sendMailWithVoucher(
+ customer = customer
+ )
+
+ /*
+ Would Assert message and exception type
+ */
+
+ assertEquals(true, true)
+ }
+}
\ No newline at end of file
diff --git a/backend/individual-assignment/challenge/src/test/kotlin/ProductTypeProcessTest.kt b/backend/individual-assignment/challenge/src/test/kotlin/ProductTypeProcessTest.kt
new file mode 100644
index 00000000..f9d61531
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/test/kotlin/ProductTypeProcessTest.kt
@@ -0,0 +1,71 @@
+import fixtures.OrderFixture
+import org.example.extension.bookProcess
+import org.example.extension.digitalProcess
+import org.example.extension.membershipProcess
+import org.example.extension.physicalProcess
+import org.junit.jupiter.api.Order
+import org.junit.jupiter.api.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertFails
+import kotlin.test.assertFailsWith
+import kotlin.test.assertTrue
+
+class ProductTypeProcessTest {
+ private val order = OrderFixture().order
+
+ @Test
+ fun `(physicalProcess) Shall ship the object with the taxes`(){
+ val response = physicalProcess(order)
+
+ assertEquals("The object was shipped with taxes", response)
+ }
+
+ @Test
+ fun `(physicalProcess) Shall throw exception if Address is not valid`(){
+ val order = OrderFixture().invalidAddressOrder
+ val response = assertFailsWith{
+ physicalProcess(order)
+ }
+
+ assertEquals("Address not valid", response.message)
+ }
+
+ @Test
+ fun `(bookProcess) Shall ship the object without taxes`(){
+ val response = bookProcess(order)
+
+ assertEquals("The object was shipped with NO taxes", response)
+ }
+
+ @Test
+ fun `(digitalProcess) Shall send email with voucher`(){
+ val response = digitalProcess(order)
+
+ assertEquals("The customer received a email with voucher",response)
+ }
+
+ @Test
+ fun `(membershipProcess) Shall make the Customer a member`(){
+ val response = membershipProcess(order)
+
+ assertEquals("The Customer is now a member", response)
+ }
+
+ @Test
+ fun `(membershipProcess) Shall extend the Customer membership if hes already a member`(){
+ val order = OrderFixture().memberOrder
+
+ val response = membershipProcess(order)
+
+ assertEquals("The member is already a member, the expirationDate will be increased", response)
+ }
+
+ @Test
+ fun `(membershipProcess) rethrow error if MembershipImpl has any error`(){
+ //This case would mockkStatic the Membership, and make every activateMembership throws error
+ // Need to import it, but will not
+ // All methods that may be mocked would have a method like this, so that it will make its own test
+ // an UNIT test...
+ assertTrue(true)
+ }
+}
\ No newline at end of file
diff --git a/backend/individual-assignment/challenge/src/test/kotlin/fixtures/AddressFixture.kt b/backend/individual-assignment/challenge/src/test/kotlin/fixtures/AddressFixture.kt
new file mode 100644
index 00000000..68b9ff70
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/test/kotlin/fixtures/AddressFixture.kt
@@ -0,0 +1,13 @@
+package fixtures
+
+import org.example.dtos.Address
+
+class AddressFixture {
+ val address = Address(
+ street = "somewhere"
+ )
+
+ val invalidAddress = Address(
+ street = "nowhere"
+ )
+}
\ No newline at end of file
diff --git a/backend/individual-assignment/challenge/src/test/kotlin/fixtures/CustomerFixture.kt b/backend/individual-assignment/challenge/src/test/kotlin/fixtures/CustomerFixture.kt
new file mode 100644
index 00000000..eb845eec
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/test/kotlin/fixtures/CustomerFixture.kt
@@ -0,0 +1,15 @@
+package fixtures
+
+import org.example.dtos.Customer
+
+class CustomerFixture{
+ val customer = Customer(
+ isMember = false,
+ email = "batata@gmail.com"
+ )
+
+ val nonMemberCustomer = Customer(
+ isMember = true,
+ email = "batata@gmail.com"
+ )
+}
\ No newline at end of file
diff --git a/backend/individual-assignment/challenge/src/test/kotlin/fixtures/OrderFixture.kt b/backend/individual-assignment/challenge/src/test/kotlin/fixtures/OrderFixture.kt
new file mode 100644
index 00000000..6788bae6
--- /dev/null
+++ b/backend/individual-assignment/challenge/src/test/kotlin/fixtures/OrderFixture.kt
@@ -0,0 +1,20 @@
+package fixtures
+
+import org.example.models.impl.Order
+
+class OrderFixture {
+ val order = Order(
+ customer = CustomerFixture().customer,
+ address = AddressFixture().address
+ )
+
+ val memberOrder = Order(
+ customer = CustomerFixture().nonMemberCustomer,
+ address = AddressFixture().address
+ )
+
+ val invalidAddressOrder = Order(
+ customer = CustomerFixture().nonMemberCustomer,
+ address = AddressFixture().invalidAddress
+ )
+}
\ No newline at end of file