Bugnik
← All problems

01Returns Eligibility Service

C++MediumBug fixSuggested time: 60 min

Return-policy rule engine + JSON parser

Sign in to start

Before you start

You'll get more out of this problem if you're comfortable with:

Language

  • Splitting a C++ project into headers (.h) and sources (.cpp), and how declarations must match definitions
  • C++ value vs reference semantics

Libraries and frameworks

  • std::map / std::vector lookups and case-insensitive string comparison

Build and test tools

  • Reading g++ error messages and test-failure output to find the first real problem

Reasoning

  • How a small hand-written parser (here, for JSON) walks through its input
  • Day arithmetic on dates and inclusive date windows
  • Money in integer cents and rounding half up

Background

The Retail Returns team owns the service that decides whether a customer may return items from an order and how much they get back. Category managers configure return rules as a JSON policy file, and the service evaluates every return request against it. After a recent refactor, the service and its test suite are in bad shape.

How the system works

Policy file

{
  "default": {"returnable": true, "windowDays": 30, "requiresUnopened": false, "restockingFeePct": 0},
  "rules": [
    {"category": "Electronics", "windowDays": 15, "restockingFeePct": 15},
    {"category": "Appliances",  "windowDays": 45, "restockingFeePct": 12.5}
  ]
}
  • Every category rule must have a category. Any other field it omits is taken from default.
  • Category matching is case-insensitive and ignores surrounding whitespace. Categories with no matching rule use the default rule.
  • restockingFeePct may be fractional (e.g. 12.5).
  • The service has its own small JSON parser (json.cpp). It supports objects, arrays, strings (escapes \" \\ \/ \n \t), true/false/null, and numbers that may be negative and/or fractional (-3, 12.5). Malformed input throws json::ParseError.

Evaluating a return request

The service checks a request in this order. The first failing check decides the outcome:

#CheckOutcome
1order id existsORDER_NOT_FOUND
2order belongs to the requesting customerNOT_OWNER
3the SKU is on the orderITEM_NOT_FOUND
40 < quantity <= purchased − already returnedINVALID_QUANTITY
5request date is not before the purchase dateINVALID_DATE
6the category is returnableNON_RETURNABLE
7days since purchase <= windowDays (the last day of the window is allowed; a same-day return is day 0)WINDOW_EXPIRED
8if the item was opened, the rule must not require it to be unopenedMUST_BE_UNOPENED

If every check passes, the outcome is APPROVED and:

  • gross = paid unit price × quantity. The paid price is what the customer actually paid, after discounts. It isn't the catalog list price.
  • If the item was opened: restockingFee = restockingFeePct % of gross, rounded half up to the nearest cent. Otherwise the fee is 0.
  • refund = gross − restockingFee.

All money is in integer cents.

State

ReturnService keeps the orders in memory. An approved return adds to that line's returned quantity and to the customer's refund total. A rejected request changes nothing. Customers may return a line in several requests, but never more units in total than they bought.

Project structure

include/
  json.h            tiny JSON value + parser
  date_util.h       ISO date parsing, day arithmetic
  money.h           cents helpers (percentages, formatting)
  order.h           Order, OrderLine, ReturnRequest, Decision, Outcome
  policy.h          PolicyRule, PolicyBook (loaded from JSON)
  return_engine.h   evaluateReturn: pure eligibility + refund calculation
  return_service.h  ReturnService: order store, ownership, state updates
src/                implementations of the above
tests/              unit tests (testlib.h is the test framework, runner.cpp the entry point)

Your task

The QA team and customers have reported problems since the last release:

  • The project no longer builds.
  • Customers returning electronics on the last day of the return window are turned away.
  • Refunds for opened appliances are a few cents to a few dollars off.
  • Customers who bought discounted items say their restocking fees are too high.
  • Some customers have returned more units than they bought by splitting a return into several requests.

Find and fix the defects so that all tests pass.

  • Do not modify files in tests/.
  • Keep your changes minimal. Fix the defects; don't rewrite modules.
  • Additional hidden tests check the behaviour described in this README.