This service provides REST endpoints to manage Coupons
and evaluate a cart against all possible coupons at runtime.
- This service assumes the cart and product entities are managed by their own separate domain services. This domain of this service is limited to managing the coupon domain entity, product and cart are treated as foreign entities.
- This service assumes that the AutN and AuthZ are done at the gateway layer.
- This service assumes that you can only apply 1 Coupon. For example there coupons for product A and B exist and the cart contains both A and B, then only one of them can be selected.
- [Implemented] Creation of a new Coupon.
- [Implemented] Creation of multiple new Coupons.
- [Implemented] Getting all the coupons.
- [Implemented] Getting a coupon with id.
- [Implemented] Deleting a coupon with id.
- [Implemented] Getting all applicable coupons for a cart.
- [Implemented] Applying a specific coupon to a cart.
- [Not Implemented] PUT method (deprioritized in favour of POST)
- [Not Implemented] User details (first time?, loyal?, milestone?) based discounts.
- [Not Implemented] Payment Type based discounts.
- [Not Implemented] Seasonal discounts.
- [Not Implemented] Usage Limit or Expiration on coupons.
- [Not Implemented] Analytics.
- [Not Implemented] Notifications on coupons.
- [Not Implemented] Concurrency Handling for usage count.
- [Not Implemented] Decoupling objects internally with the request objects.
- [Not Implemented] Free product discount.
- [Not Implemented] Pagination to APIs.
Response:
[
{
"id": 4254458416752905633,
"type": "PRODUCT",
"discount": 0.5,
"productId": 1
},
{
"id": 8989409639447348072,
"type": "CART_VALUE",
"discount": 0.5,
"minCartValue": 1000.0
},
{
"id": 3058698387227953112,
"type": "BUY_X_GET_Y",
"requiredProductIds": [
1,
2
],
"requiredProducts": 2,
"freeProductIds": [
3,
4
],
"freeProducts": 1
}
]
Response (Coupon Exists):
{
"id": 4254458416752905633,
"type": "PRODUCT",
"discount": 0.5,
"productId": 1
}
Response (Coupon Doesn't Exist):
400: Bad Request
Request:
{
"type": "PRODUCT",
"discount": 0.5,
"productId": 12
}
Response (Coupon Id):
3695691894844706074
Request (invalid schema):
{
"type": "PRODUCT",
"discount": -1.0,
"productId": 12
}
Response:
{
"timestamp": "2024-09-21T09:31:20.404+00:00",
"status": 400,
"error": "Bad Request",
"path": "/v1/coupon"
}
Request:
[
{
"type": "PRODUCT",
"discount": 0.5,
"productId": 12
},
{
"type": "PRODUCT",
"discount": 0.2,
"productId": 1
}
]
Response:
[
1261786151684425124,
5223562988408424163
]
Response:
{
"id": 5223562988408424163,
"type": "PRODUCT",
"discount": 0.2,
"productId": 1
}
Response (Coupon doesn't exist):
400: Bad Request
Request:
{
"cart": {
"items": [
{"id": 1, "value": 10.0, "quantity": 10},
{"id": 2, "value": 100.0, "quantity": 20}
]
}
}
Response:
{
"1926902968298981064": 1050.0,
"6649925179862827746": 50.0
}
Request: 0.0.0.0:8080/v1/apply/1926902968298981064
{
"cart": {
"items": [
{"id": 1, "value": 10.0, "quantity": 10},
{"id": 2, "value": 100.0, "quantity": 20}
]
}
}
Response:
1050.0
Request (Coupon doesn't exist): 0.0.0.0:8080/v1/apply/1926
{
"cart": {
"items": [
{"id": 1, "value": 10.0, "quantity": 10},
{"id": 2, "value": 100.0, "quantity": 20}
]
}
}
Response:
400: Bad Request
The Coupon can be further enhanced by storing an expiration date and a count limit. This can be done by adding to the entity 2 new fields:
DateTime expirationDate;
Long usageCount;
Then at the time of applying a coupon, we just have to check:
if (!(usageCount < 0) || (DateTime.now() > expirationDate)) {
throw new ExpiredCouponException("Coupon is no longer valid");
}
Due to time constraints, I have not implemented a persistence layer yet. I'm storing the data in memory using a hashmap. The Dao is written against an interface and can be easily implemented to use an actual DB. My DB of choice would be a NoSql DB as the coupon structure is not same. Given Monk is already using AWS, I'd implement the persistence layer with DynamoDb with coupon id as partition key.
Due to time constraints, I am always returning a Bad Request in case of any errors. This can be enhanced by returning contextual error messages with proper error codes.
Due to time constraints, I have not written integration tests for the APIs.
Currently, I'm just returning the raw discount value due to time constraints. This can be made more contextual by returning the discount applied on each product. I feel the current implementation is a good starting point and meets the functional requirement of computing discount and saving customers money.
This service can be broken into 2 services: one to manage the coupon entity (responsible for providing CRUD APIs, analytics into coupon usage patterns and notifications) and another to evaluate a transaction for the discount.