Calculate transaction fees from CSV records
Company: TikTok
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Quick Answer: This question evaluates parsing and processing of structured transaction data, implementing multi-dimension fee computation rules (provider-, transaction-type-, and country-based), maintaining per-merchant state for discounts and volume tracking, and precise numeric handling including rounding.
Part 1: Provider-based fee calculation from CSV
Constraints
- 0 <= number of data rows <= 100000
- 0 <= amount <= 10^9
- All transaction rows have exactly 10 comma-separated fields
- No field contains embedded commas or quoted commas
- Every `payment_provider` appearing in the CSV exists in `provider_fee_rules`
- For `tiered` rules, tiers are ordered by increasing `max_amount` and at least one tier matches every amount
Examples
Input: ('id,reference,amount,currency,date,merchant_id,buyer_country,transaction_type,payment_provider,status\n1,r1,1000,USD,2024-01-01,m1,US,payment,stripe,captured\n2,r2,2500,USD,2024-01-02,m1,US,payment,paypal,captured\n3,r3,700,USD,2024-01-03,m2,CA,refund,bank,payment_failed', {'stripe': {'type': 'percent_fixed', 'percent_bps': 290, 'fixed': 30}, 'paypal': {'type': 'tiered', 'tiers': [(1000, 50), (5000, 120), (None, 250)]}, 'bank': {'type': 'flat', 'fee': 15}}, {'captured', 'settled'})
Expected Output: ['1,payment,stripe,59', '2,payment,paypal,120', '3,refund,bank,0']
Explanation: Stripe uses 2.90% + 30, PayPal uses tiers, and failed transactions always produce fee 0.
Input: ('id,reference,amount,currency,date,merchant_id,buyer_country,transaction_type,payment_provider,status', {'stripe': {'type': 'percent_fixed', 'percent_bps': 290, 'fixed': 30}, 'paypal': {'type': 'tiered', 'tiers': [(1000, 50), (5000, 120), (None, 250)]}, 'bank': {'type': 'flat', 'fee': 15}}, {'captured', 'settled'})
Expected Output: []
Explanation: Header only means there are no transaction rows to process.
Input: ('id,reference,amount,currency,date,merchant_id,buyer_country,transaction_type,payment_provider,status\n1,r1,1001,USD,2024-01-01,m1,US,payment,paypal,captured\n2,r2,333,USD,2024-01-02,m1,US,payment,stripe,settled\n3,r3,0,USD,2024-01-03,m2,CA,payment,stripe,captured', {'stripe': {'type': 'percent_fixed', 'percent_bps': 290, 'fixed': 30}, 'paypal': {'type': 'tiered', 'tiers': [(1000, 50), (5000, 120), (None, 250)]}, 'bank': {'type': 'flat', 'fee': 15}}, {'captured', 'settled'})
Expected Output: ['1,payment,paypal,120', '2,payment,stripe,40', '3,payment,stripe,30']
Explanation: This checks a tier boundary, percentage rounding half up, and a zero-amount successful transaction.
Input: ('', {'stripe': {'type': 'percent_fixed', 'percent_bps': 290, 'fixed': 30}, 'paypal': {'type': 'tiered', 'tiers': [(1000, 50), (5000, 120), (None, 250)]}, 'bank': {'type': 'flat', 'fee': 15}}, {'captured', 'settled'})
Expected Output: []
Explanation: An empty input string should return an empty result.
Hints
- Write one helper that evaluates a single fee rule for a given amount.
- You can solve this in one pass over the CSV rows without storing parsed transactions.
Part 2: Fees by transaction type and payment provider
Constraints
- 0 <= number of data rows <= 100000
- 0 <= amount <= 10^9
- All transaction rows have exactly 10 comma-separated fields
- No field contains embedded commas or quoted commas
- Every `(transaction_type, payment_provider)` pair in the CSV exists in `fee_rules`
- For `tiered` rules, tiers are ordered by increasing `max_amount` and at least one tier matches every amount
Examples
Input: ('id,reference,amount,currency,date,merchant_id,buyer_country,transaction_type,payment_provider,status\n1,r1,1000,USD,2024-01-01,m1,US,payment,stripe,captured\n2,r2,3000,USD,2024-01-02,m1,US,refund,stripe,processed\n3,r3,500,USD,2024-01-03,m2,CA,refund,paypal,processed\n4,r4,2000,USD,2024-01-04,m2,CA,payment,paypal,payment_failed\n5,r5,900,USD,2024-01-05,m3,GB,payout,paypal,settled', {'payment': {'stripe': {'type': 'percent_fixed', 'percent_bps': 250, 'fixed': 20}, 'paypal': {'type': 'flat', 'fee': 70}}, 'refund': {'stripe': {'type': 'flat', 'fee': 15}, 'paypal': {'type': 'tiered', 'tiers': [(1000, 20), (None, 35)]}}, 'payout': {'stripe': {'type': 'flat', 'fee': 40}, 'paypal': {'type': 'percent_fixed', 'percent_bps': 100, 'fixed': 10}}}, {'captured', 'settled', 'processed'})
Expected Output: ['1,payment,stripe,45', '2,refund,stripe,15', '3,refund,paypal,20', '4,payment,paypal,0', '5,payout,paypal,19']
Explanation: The same provider can have different fees for different transaction types.
Input: ('id,reference,amount,currency,date,merchant_id,buyer_country,transaction_type,payment_provider,status', {'payment': {'stripe': {'type': 'percent_fixed', 'percent_bps': 250, 'fixed': 20}, 'paypal': {'type': 'flat', 'fee': 70}}, 'refund': {'stripe': {'type': 'flat', 'fee': 15}, 'paypal': {'type': 'tiered', 'tiers': [(1000, 20), (None, 35)]}}, 'payout': {'stripe': {'type': 'flat', 'fee': 40}, 'paypal': {'type': 'percent_fixed', 'percent_bps': 100, 'fixed': 10}}}, {'captured', 'settled', 'processed'})
Expected Output: []
Explanation: Header only is an important edge case.
Input: ('id,reference,amount,currency,date,merchant_id,buyer_country,transaction_type,payment_provider,status\n1,r1,1000,USD,2024-01-01,m1,US,refund,paypal,processed\n2,r2,1001,USD,2024-01-02,m1,US,refund,paypal,processed\n3,r3,0,USD,2024-01-03,m2,CA,payment,stripe,settled', {'payment': {'stripe': {'type': 'percent_fixed', 'percent_bps': 250, 'fixed': 20}, 'paypal': {'type': 'flat', 'fee': 70}}, 'refund': {'stripe': {'type': 'flat', 'fee': 15}, 'paypal': {'type': 'tiered', 'tiers': [(1000, 20), (None, 35)]}}, 'payout': {'stripe': {'type': 'flat', 'fee': 40}, 'paypal': {'type': 'percent_fixed', 'percent_bps': 100, 'fixed': 10}}}, {'captured', 'settled', 'processed'})
Expected Output: ['1,refund,paypal,20', '2,refund,paypal,35', '3,payment,stripe,20']
Explanation: This checks a tier boundary and a zero-amount payment whose fee still includes the fixed component.
Input: ('', {'payment': {'stripe': {'type': 'percent_fixed', 'percent_bps': 250, 'fixed': 20}, 'paypal': {'type': 'flat', 'fee': 70}}, 'refund': {'stripe': {'type': 'flat', 'fee': 15}, 'paypal': {'type': 'tiered', 'tiers': [(1000, 20), (None, 35)]}}, 'payout': {'stripe': {'type': 'flat', 'fee': 40}, 'paypal': {'type': 'percent_fixed', 'percent_bps': 100, 'fixed': 10}}}, {'captured', 'settled', 'processed'})
Expected Output: []
Explanation: An empty CSV string should produce no output lines.
Hints
- Keep the same rule-evaluation helper from Part 1; only the rule lookup changes.
- Use `fee_rules[transaction_type][payment_provider]` to avoid a long chain of conditionals.
Part 3: Country overrides and merchant volume discounts
Constraints
- 0 <= number of data rows <= 100000
- 0 <= amount <= 10^9
- 0 <= threshold <= 10^9
- 1 <= multiplier_num <= multiplier_den <= 10^6
- All transaction rows have exactly 10 comma-separated fields
- No field contains embedded commas or quoted commas
- Every `(transaction_type, payment_provider)` pair in the CSV exists in `base_fee_rules`
- For every `tiered` rule, tiers are ordered by increasing `max_amount` and at least one tier matches every amount
Examples
Input: ('id,reference,amount,currency,date,merchant_id,buyer_country,transaction_type,payment_provider,status\n1,r1,1000,USD,2024-01-01,m1,US,payment,stripe,captured\n2,r2,2000,USD,2024-01-02,m1,BR,payment,stripe,captured\n3,r3,500,USD,2024-01-03,m1,DE,refund,paypal,processed\n4,r4,3000,USD,2024-01-04,m2,BR,payment,paypal,payment_failed\n5,r5,1500,USD,2024-01-05,m2,JP,payment,paypal,settled', {'payment': {'stripe': {'type': 'percent_fixed', 'percent_bps': 300, 'fixed': 30}, 'paypal': {'type': 'flat', 'fee': 60}}, 'refund': {'stripe': {'type': 'flat', 'fee': 20}, 'paypal': {'type': 'tiered', 'tiers': [(1000, 25), (None, 40)]}}}, {'DE': {'mode': 'override', 'rule': {'type': 'flat', 'fee': 10}}, 'BR': {'mode': 'add', 'rule': {'type': 'percent_fixed', 'percent_bps': 100, 'fixed': 0}}, 'JP': {'mode': 'add', 'rule': {'type': 'flat', 'fee': 5}}}, 2, {'multiplier_num': 1, 'multiplier_den': 2}, {'captured', 'settled', 'processed'})
Expected Output: ['1,payment,stripe,60', '2,payment,stripe,110', '3,refund,paypal,5', '4,payment,paypal,0', '5,payment,paypal,65']
Explanation: This case combines base rules, country `add` and `override`, per-merchant counting, and a discount that starts on the 3rd successful transaction for merchant `m1`.
Input: ('id,reference,amount,currency,date,merchant_id,buyer_country,transaction_type,payment_provider,status\n1,r1,1000,USD,2024-01-01,m9,US,payment,stripe,payment_failed\n2,r2,1000,USD,2024-01-02,m9,US,payment,stripe,captured\n3,r3,100,USD,2024-01-03,m9,JP,payment,paypal,settled\n4,r4,500,USD,2024-01-04,m9,DE,refund,paypal,processed', {'payment': {'stripe': {'type': 'percent_fixed', 'percent_bps': 300, 'fixed': 30}, 'paypal': {'type': 'flat', 'fee': 60}}, 'refund': {'stripe': {'type': 'flat', 'fee': 20}, 'paypal': {'type': 'tiered', 'tiers': [(1000, 25), (None, 40)]}}}, {'DE': {'mode': 'override', 'rule': {'type': 'flat', 'fee': 10}}, 'BR': {'mode': 'add', 'rule': {'type': 'percent_fixed', 'percent_bps': 100, 'fixed': 0}}, 'JP': {'mode': 'add', 'rule': {'type': 'flat', 'fee': 5}}}, 1, {'multiplier_num': 1, 'multiplier_den': 2}, {'captured', 'settled', 'processed'})
Expected Output: ['1,payment,stripe,0', '2,payment,stripe,60', '3,payment,paypal,33', '4,refund,paypal,5']
Explanation: The failed transaction does not count toward the threshold. The third row becomes 65 before discount, then rounds half up to 33 after a 1/2 multiplier.
Input: ('id,reference,amount,currency,date,merchant_id,buyer_country,transaction_type,payment_provider,status\n1,r1,800,USD,2024-01-01,m1,DE,refund,paypal,processed', {'payment': {'stripe': {'type': 'percent_fixed', 'percent_bps': 300, 'fixed': 30}, 'paypal': {'type': 'flat', 'fee': 60}}, 'refund': {'stripe': {'type': 'flat', 'fee': 20}, 'paypal': {'type': 'tiered', 'tiers': [(1000, 25), (None, 40)]}}}, {'DE': {'mode': 'override', 'rule': {'type': 'flat', 'fee': 10}}, 'BR': {'mode': 'add', 'rule': {'type': 'percent_fixed', 'percent_bps': 100, 'fixed': 0}}, 'JP': {'mode': 'add', 'rule': {'type': 'flat', 'fee': 5}}}, 0, {'multiplier_num': 1, 'multiplier_den': 2}, {'captured', 'settled', 'processed'})
Expected Output: ['1,refund,paypal,5']
Explanation: With threshold 0, every successful transaction is discounted immediately.
Input: ('id,reference,amount,currency,date,merchant_id,buyer_country,transaction_type,payment_provider,status', {'payment': {'stripe': {'type': 'percent_fixed', 'percent_bps': 300, 'fixed': 30}, 'paypal': {'type': 'flat', 'fee': 60}}, 'refund': {'stripe': {'type': 'flat', 'fee': 20}, 'paypal': {'type': 'tiered', 'tiers': [(1000, 25), (None, 40)]}}}, {'DE': {'mode': 'override', 'rule': {'type': 'flat', 'fee': 10}}, 'BR': {'mode': 'add', 'rule': {'type': 'percent_fixed', 'percent_bps': 100, 'fixed': 0}}, 'JP': {'mode': 'add', 'rule': {'type': 'flat', 'fee': 5}}}, 2, {'multiplier_num': 1, 'multiplier_den': 2}, {'captured', 'settled', 'processed'})
Expected Output: []
Explanation: Header only means there are no rows to price.
Hints
- Keep a dictionary `merchant_id -> successful_count` while scanning rows once from top to bottom.
- Apply the country adjustment before the discount, and remember that failed rows do not increment the merchant count.