This is just a pet project of mine to play with Kotlin stdlib
For this, I'm simulating an eCommerce platform as close as I can, model-wise. I took inspiration based on my understanding of how Amazon handles Physical, Digital and Subscription Orders
- Kotlin 2.x (target: JVM 21)
- JUnit 5 (test runner)
- Kotest Assertions (assertion framework)
- GitHub CI
fun main() {
val cart = ShoppingCart()
.add(Product("PS4 Slim 1TB", ProductType.PHYSICAL, 1899.00), 1)
.add(Product("PDP Chair", ProductType.PHYSICAL, 399.00), 2)
.add(Product("Cracking the Code Interview", ProductType.PHYSICAL_TAX_FREE, 219.57), 2)
.add(Product("The Hitchhiker's Guide to the Galaxy", ProductType.PHYSICAL_TAX_FREE, 120.00), 1)
.add(Product("Stairway to Heaven", ProductType.DIGITAL, 5.00), 1)
.add(Product("Nier:Automata", ProductType.DIGITAL, 129.90), 4)
.add(Product("Netflix Familiar Plan", ProductType.SUBSCRIPTION, 29.90), 1)
.add(Product("Spotify Premium", ProductType.SUBSCRIPTION, 14.90), 1)
.add(Product("Amazon Prime", ProductType.SUBSCRIPTION, 12.90), 1)
val account = Account("john doe", "[email protected]", "passwd")
val orders = cart.checkout(account)
// Pick one or Iterate through the orders
val physicalOrder = orders.first { it.type == OrderType.PHYSICAL } as PhysicalOrder
val digitalOrder = orders.first { it.type == OrderType.DIGITAL } as DigitalOrder
val subscriptionOrder = orders.first { it.type == OrderType.SUBSCRIPTION } as SubscriptionOrder
val address = Address.builder
.country("Brazil")
.city("Sao Paulo")
.state("SP")
.zipCode("01000-000")
.streetAddress("Av Paulista, 1000")
.build()
val myCreditCard = CreditCard(
nameOnCard = "John Doe",
number = "123.456.789-00",
securityCode = 123,
expiresAt = YearMonth.of(2027, 7),
billingAddress = address
)
val physicalOrderInvoice = physicalOrder
.withShippingAddress(address)
.withPaymentMethod(myCreditCard)
.place()
.pay()
.invoice()
val digitalOrderInvoice = digitalOrder
.withPaymentMethod(myCreditCard)
.place()
.pay()
.invoice()
val membershipOrderInvoice = subscriptionOrder
.withPaymentMethod(myCreditCard)
.place()
.pay()
.invoice()
}
These are pretty self explanatory, right ? :) ProductType has an enum parameter, mapping to what kind of Order that product would be in
Represents a given amount of Product in the Shopping Cart
or in an Order
Represents an interface defining the implementations for Physical, Digital and Membership Orders,
each with its own set of rules for place()
, pay()
, fulfill()
and complete()
May only contain products which the ProductType
is 'Physical' or 'PHYSICAL_TAX_FREE'
-
place()
:- Setups the Packaging for Shipping
- Includes a
Shipping and Handling
cost of extra $10 per package - Ready the order to the
PENDING
status - Note: Provided that there are items that fall under the
PHYSICAL_BOOK
category, they're grouped together into another shipment with the labelTAX_FREE
-
pay()
:- Provided that the Order has been placed, and not yet payed:
//TODO:
Process the Payment- Updates the OrderStatus to
UNSHIPPED
-
fulfill()
:- Provided that the order has been payed and not yet shipped:
//TODO:
Notifies the seller to fulfill/process the Order on its end- Updates the OrderStatus to
SHIPPED
-
complete()
:// TODO:
: Track the packages/shipment they're all delivered- Updates the OrderStatus to
DELIVERED
May only contain products which the ProductType
is 'Digital'
-
place()
:- Includes a
Voucher
discount of $10 for the Order - Ready the order to the
PENDING
status
- Includes a
-
pay()
:- Provided that the Order has been placed, and not yet payed:
//TODO:
Process the Payment- Updates the OrderStatus to
UNSENT
-
fulfill()
:- Provided that the order has been payed and not yet sent:
//TODO:
Notifies the seller to fulfill/process the Order on its end- Updates the OrderStatus to
SENT
-
complete()
:// TODO:
: Track when the the Buyer clicks on the emailed link to redeem the item- Updates the OrderStatus to
REDEEMED
May only contain products which the ProductType
is 'Subscription'
-
place()
:- Ensures there's only one Subscription per Order
- Ready the order to the
PENDING
status - Note: In a scenario with multiple
Membership
Items in the Shopping Cart, each will spawn a different Order
-
pay()
:- Provided that the Order has been placed, and not yet payed:
//TODO:
Process the Payment- Updates the OrderStatus to
PENDING_ACTIVATION
-
fulfill()
:- Provided that the order has been payed and not yet activated:
//TODO:
Activates the Subscription Service- Updates the OrderStatus to
ACTIVATED
To better simulate the user experience in an e-commerce platform, and also to wrap all the complexity of creating an Order, this Shopping Cart entity was created
-
add(product: Product, n: Int)
:- Adds the N-amounts of the given
product
to the shopping Cart - Note: if the product is already in the Cart, adds up in the quantity
- Adds the N-amounts of the given
-
updateQuantity(product: Product, n: Int)
:- Overrides whatever amount there is of the given
product
wih the specifiedn
amount. If(n == 0)
, the product is deleted from the cart
- Overrides whatever amount there is of the given
-
delete(product: Product)
Deletes the product from the cart regardless of the quantity -
subtotal()
Computes the sum of (unittest price of each product in the Cart * quantity) -
checkout(account: Account)
:- All items that fall under
Physical
(regardless if their product category will be tax-free on Shipment or not) are grouped together to create aPhysicalOrder
- All items that fall under
Digital
are grouped together to create aDigitalOrder
- Each item that falls under
Membership
will create a different Membership Order (due to the complexity of activating each subscription individually, and probably through 3rd-party APIs)
- All items that fall under
- Build Automation with CI (Travis CI)
- Code Coverage check
- Code Inspection for maintainability
- GitHub CI