73. Design a Billing and discounts System for an ecommerce app

Design Billing and discounts System for an ecommerce app
Implement a fully executable, in-memory billing and discounts system for an ecommerce app. You must design and implement a bill creation flow, a discount application flow, and a point/level calculation system.

Core Requirements

  • Bill: Create a bill for a customer using a list of cart items, compute a subtotal, and track bill state (open/paid).
  • Discount: Apply one or more discount codes to an open bill and compute the payable amount deterministically.
  • Point/Level calculation system: When a bill is paid successfully, award loyalty points and update the customer’s level.
  • In-memory: No database; store customers, bills, applied discounts, and points in memory.
  • Deterministic IDs: Bill IDs must be generated sequentially as B1, B2, B3, ... in creation order.

Data Format

Cart Item Encoding

Each cart item is provided as a single string. The format is: "itemName|unitPrice|quantity"
  • itemName is a non-empty string without the | character.
  • unitPrice is an integer representing price per unit (in dollars).
  • quantity is an integer.
  • Subtotal contribution = unitPrice * quantity.

Supported Discount Codes

  • P10: 10% off subtotal.
  • P20: 20% off subtotal.
  • FLAT100: Flat 100 off, applicable only if subtotal >= 500.
  • REDEEM: Redeem customer points for additional discount (see rules below).

Discount Rules

  • Percentage discount: At most one percentage code can be effective. If both P10 and P20 are applied, only the highest percentage is used.
  • Flat discount: FLAT100 can be applied at most once and only if subtotal >= 500.
  • Redemption discount: REDEEM can be applied at most once.
  • Computation order:
    • Start with subtotal.
    • Apply the effective percentage discount (if any).
    • Apply FLAT100 (if applicable).
    • Apply REDEEM (if applied).
  • Rounding: All calculations must use integer math. Percentage discount uses floor: percentDiscount = (subtotal * percent) / 100.
  • Non-negative payable: Payable amount must not go below 0.
  • Idempotency: Applying the same discount code multiple times must not stack. It should have no additional effect after the first time.

Point/Level Calculation System

Point Earning

  • On successful payment, points earned = floor(payableAmount / 100).
  • Points are awarded only if the bill transitions to paid.

Point Redemption

  • When REDEEM is applied, the bill can use customer’s currently available points for discount at the rate 1 point = 1 dollar.
  • Redemption amount is capped to 20% of the current payable amount (after percentage and flat discounts):
    redeemCap = floor(currentPayable * 20 / 100)
  • Redemption amount = min(customerPoints, redeemCap).
  • Points are deducted only if payment succeeds. If payment fails then no points are deducted.

Customer Levels

  • BRONZE: 0 - 99 points
  • SILVER: 100 - 499 points
  • GOLD: 500 - 1999 points
  • PLATINUM: 2000+ points
  • Level is derived from current total points after any redemption deduction and newly earned points.

Error Handling

  • If an operation cannot be completed due to invalid input or invalid state, return an error output as defined by the method contract.
  • A bill can be paid at most once. Discounts can be applied only while the bill is open.

Method Signatures

1) Create bill

String createBill(String customerId, List<String> cartItems)
  • customerId is a non-empty string.
  • cartItems contains 1 or more strings, each in format "itemName|unitPrice|quantity".
  • unitPrice >= 0 and quantity > 0 for every item.
  • Returns the new bill ID ("B1", "B2", ...).
  • If input is invalid, return "ERROR".

2) Apply discount

long applyDiscount(String billId, String discountCode)
  • billId must refer to an existing, open bill.
  • discountCode must be one of: P10, P20, FLAT100, REDEEM.
  • Discount application is idempotent per code.
  • Returns the current payable amount after applying the code (and re-evaluating all applied codes).
  • If billId is invalid or bill is already paid, return -1.
  • If discountCode is unknown, ignore it and return the current payable amount (no change).

3) Pay bill (also updates points/level)

String payBill(String billId, long amountPaid)
  • billId must refer to an existing, open bill.
  • amountPaid must be exactly equal to the current payable amount.
  • On success, the bill becomes paid, points are redeemed (if REDEEM was applied), then new points are earned, and level is updated.
  • Return receipt string in format:
    "PAID|final=<finalAmount>|pointsEarned=<x>|totalPoints=<y>|level=<LEVEL>"
  • If billId is invalid, bill is already paid, or amountPaid mismatches, return "ERROR".

Constraints

  • 1 <= cartItems.size() <= 10^5
  • 0 <= unitPrice <= 10^9
  • 1 <= quantity <= 10^6
  • Total subtotal fits in 64-bit signed integer (long).
  • At most 4 distinct discount codes can be applied to a bill (from the supported set).

Examples

Example 1: Basic bill + percentage discount + payment earns points

  • createBill(customerId = "C1", cartItems = List.of("book|200|1", "pen|10|5")) returns "B1"
    Explanation: Subtotal = 200*1 + 10*5 = 250; bill ID starts from B1.
  • applyDiscount(billId = "B1", discountCode = "P10") returns 225
    Explanation: 10% of 250 is 25; payable becomes 250 - 25 = 225.
  • applyDiscount(billId = "B1", discountCode = "FLAT100") returns 225
    Explanation: FLAT100 requires subtotal >= 500; subtotal is 250 so it is not applicable.
  • payBill(billId = "B1", amountPaid = 225) returns "PAID|final=225|pointsEarned=2|totalPoints=2|level=BRONZE"
    Explanation: Points earned = floor(225/100)=2; total points become 2; level stays BRONZE.

Example 2: Multiple discounts including REDEEM (points used + points earned)

  • createBill(customerId = "C1", cartItems = List.of("shoes|600|1", "tshirt|200|2")) returns "B2"
    Explanation: Subtotal = 600 + 200*2 = 1000.
  • applyDiscount(billId = "B2", discountCode = "P20") returns 800
    Explanation: 20% of 1000 is 200; payable becomes 1000 - 200 = 800.
  • applyDiscount(billId = "B2", discountCode = "FLAT100") returns 700
    Explanation: Subtotal >= 500, so flat 100 applies; payable becomes 800 - 100 = 700.
  • applyDiscount(billId = "B2", discountCode = "REDEEM") returns 698
    Explanation: Customer C1 currently has 2 points from Example 1. Redeem cap = floor(700*20/100)=140. Redeem amount = min(2,140)=2. Payable becomes 700 - 2 = 698.
  • payBill(billId = "B2", amountPaid = 698) returns "PAID|final=698|pointsEarned=6|totalPoints=6|level=BRONZE"
    Explanation: First deduct redeemed 2 points (2 - 2 = 0), then earn floor(698/100)=6. Total points become 6; level remains BRONZE.

Example 3: Invalid payment amount does not change state or points

  • createBill(customerId = "C2", cartItems = List.of("mouse|499|1")) returns "B3"
    Explanation: Subtotal = 499.
  • applyDiscount(billId = "B3", discountCode = "P10") returns 450
    Explanation: floor(499*10/100)=49; payable becomes 499 - 49 = 450.
  • payBill(billId = "B3", amountPaid = 449) returns "ERROR"
    Explanation: Amount paid must match payable exactly; bill remains open and no points/level changes occur.




Please use Laptop/Desktop or any other large screen to add/edit code.