Implement Billing Coding Exercises
StripeSoftware Engineer · OnsiteLast updated
Implement a subscription proration and billing reconciliation engine that generates line items across plan changes and computes the outstanding balance against posted payments.
Problem Statement
In recurring billing systems, customers frequently upgrade, downgrade, or cancel plans mid-cycle. You need to implement an invoice generation and reconciliation function that calculates prorated charges for a customer across a billing cycle and reconciles them with any payments made.
Each billing cycle is defined by an inclusive start date and an exclusive end date ([cycle_start, cycle_end)). Each plan has a daily rate expressed in integer cents. Throughout the cycle, the customer can transition between plans or cancel their subscription (plan_id = None).
Implement the function generate_billing_statement:
def generate_billing_statement(
cycle_start: str,
cycle_end: str,
initial_plan_id: str | None,
plan_changes: list[dict],
payments: list[dict],
plans: dict[str, int]
) -> dict:
Input
cycle_start,cycle_end: Strings in'YYYY-MM-DD'format, wherecycle_start < cycle_end.initial_plan_id: The plan ID active at the start ofcycle_start, orNoneif the customer had no active plan.plan_changes: A list of objects with keys"date"(str,'YYYY-MM-DD') and"plan_id"(str orNone), representing the new plan effective from that date. Note: multiple changes can occur on the same day; only the last change for that date takes effect. Changes may arrive out of order.payments: A list of objects with keys"id"(str),"date"(str),"amount_cents"(int), and"status"("succeeded"or"failed").plans: A dictionary mappingplan_idtodaily_rate_cents(int > 0).
Output
Return a dictionary with the following schema:
"line_items": A list of active billing intervals chronologically ordered. Do not include intervals whereplan_idisNoneor duration is 0 days. Each item contains:"plan_id": str"start_date": str ('YYYY-MM-DD')"end_date": str ('YYYY-MM-DD')"days": int"amount_cents": int (days * daily_rate_cents)
"total_billed_cents": int (sum ofamount_centsacross line items)"total_paid_cents": int (sum ofamount_centsfor all"succeeded"payments)"amount_due_cents": int (total_billed_cents - total_paid_cents)
Constraints
- All dates are valid ISO-8601 strings between
"2000-01-01"and"2099-12-31". - All dates in
plan_changesfall within[cycle_start, cycle_end). 0 <= len(plan_changes) <= 10^40 <= len(payments) <= 10^4
Example
cycle_start = "2024-03-01"
cycle_end = "2024-03-31"
initial_plan_id = "starter"
plan_changes = [
{"date": "2024-03-11", "plan_id": "pro"},
{"date": "2024-03-25", "plan_id": None}
]
payments = [
{"id": "pay_1", "date": "2024-03-01", "amount_cents": 1500, "status": "succeeded"},
{"id": "pay_2", "date": "2024-03-20", "amount_cents": 2000, "status": "failed"}
]
plans = {
"starter": 100, # 100 cents/day
"pro": 250 # 250 cents/day
}
# Output:
# {
# "line_items": [
# {"plan_id": "starter", "start_date": "2024-03-01", "end_date": "2024-03-11", "days": 10, "amount_cents": 1000},
# {"plan_id": "pro", "start_date": "2024-03-11", "end_date": "2024-03-25", "days": 14, "amount_cents": 3500}
# ],
# "total_billed_cents": 4500,
# "total_paid_cents": 1500,
# "amount_due_cents": 3000
# }
Hints
Hint 1
Deduplicate same-day plan changes by keeping the last change, and sort the unique transition dates in ascending order.
Hint 2
Form intervals between consecutive event dates, starting at cycle_start and ending at cycle_end, skipping zero-day intervals.
Hint 3
Filter payments strictly by status == 'succeeded' when summing total_paid_cents, and omit line items where the active plan is None.
Follow-up questions
- How would you handle timezone differences if events include UTC timestamps while the billing cycle is defined in the merchant's local timezone?
- How would you adapt the proration logic if plans are billed monthly (e.g., $30/month) rather than with pre-defined daily rates, accounting for months with 28, 30, or 31 days?
- If multiple payments can be applied to specific line items (e.g., restricted credits or refunds), how would you track unpaid balances per line item?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.