Design an E-commerce payment checkout system.
Focus on order cancellation and payment flows.
The checkout system should support creating an order, starting a payment, completing a payment, cancelling an order, and reading the current order details.
A payment can be started only with one of the supported payment methods given during initialization. An order moves through well-defined states based on payment and cancellation operations.
Cancellation behavior depends on the current order state:
- If the order is not yet successfully paid, cancelling it should move it to a cancelled state.
- If the order was already paid, cancelling it should mark it as cancelled and refund required.
Constructor
ECommerceCheckout(List<String> supportedPaymentMethods)
supportedPaymentMethods contains the allowed payment methods such as "CARD", "UPI", "WALLET".
- All payment methods will contain only A-Z and '_'
- Duplicate payment methods, if any, should be treated as one supported method.
Exact Method Signatures
Create Order
String createOrder(String orderId, int totalAmount)
- Creates a new order with initial status
CREATED.
- On creation,
PAYMENT_METHOD is NONE, PAYMENT_REF is NONE, REFUND_REQUIRED is false, and CANCEL_REASON is NONE.
- Return checks must be evaluated in this exact order:
- If
orderId already exists, return "ORDER_ALREADY_EXISTS".
- Else if
totalAmount <= 0, return "INVALID_AMOUNT".
- Else create the order and return
"ORDER_CREATED".
Start Payment
String startPayment(String orderId, String paymentMethod)
- Starts payment for an existing order using the given payment method.
- Allowed only when the current order status is
CREATED or PAYMENT_FAILED.
- At most one active payment flow can exist per order.
- If this method returns
"PAYMENT_STARTED", the order status becomes PAYMENT_IN_PROGRESS, PAYMENT_METHOD becomes the given method, and PAYMENT_REF becomes NONE.
- Return checks must be evaluated in this exact order:
- If the order does not exist, return
"ORDER_NOT_FOUND".
- Else if the method is not supported, return
"UNSUPPORTED_PAYMENT_METHOD".
- Else if payment cannot be started from the current state, return
"ORDER_NOT_PAYABLE".
- Else start payment and return
"PAYMENT_STARTED".
Complete Payment
String completePayment(String orderId, String paymentReference, boolean paymentSucceeded)
- Completes the current active payment flow for the order.
- Allowed only when the current order status is
PAYMENT_IN_PROGRESS.
- Return checks must be evaluated in this exact order:
- If the order does not exist, return
"ORDER_NOT_FOUND".
- Else if there is no active payment flow for that order, return
"PAYMENT_NOT_IN_PROGRESS".
- Else if
paymentSucceeded is true, the order status becomes PAID, PAYMENT_REF becomes paymentReference, and the method returns "PAYMENT_COMPLETED".
- Else the order status becomes
PAYMENT_FAILED, PAYMENT_REF becomes NONE, and the method returns "PAYMENT_FAILED".
- When payment fails,
PAYMENT_METHOD remains the method from the latest startPayment call that returned "PAYMENT_STARTED".
Cancel Order
String cancelOrder(String orderId, String reason)
- Return checks must be evaluated in this exact order:
- If the order does not exist, return
"ORDER_NOT_FOUND".
- Else if the current status is already
CANCELLED or CANCELLED_REFUND_DUE, return "ORDER_ALREADY_CANCELLED".
- Else if the current status is
PAID, the new status becomes CANCELLED_REFUND_DUE, REFUND_REQUIRED becomes true, CANCEL_REASON becomes reason, and the method returns "ORDER_CANCELLED_WITH_REFUND".
- Else if the current status is
CREATED, PAYMENT_IN_PROGRESS, or PAYMENT_FAILED, the new status becomes CANCELLED, REFUND_REQUIRED remains false, CANCEL_REASON becomes reason, and the method returns "ORDER_CANCELLED".
- Cancellation does not change
PAYMENT_METHOD.
- Cancellation does not change
PAYMENT_REF. Therefore it stays NONE if payment was never successfully completed, and it keeps the successful payment reference if payment had already completed successfully.
Get Order Details
List<String> getOrderDetails(String orderId)
- Returns the current order details in the exact format shown below.
- If the order exists, return a
List<String> with exactly these lines in this order:
"ORDER:<orderId>"
"AMOUNT:<totalAmount>"
"STATUS:<status>"
"PAYMENT_METHOD:<paymentMethod or NONE>"
"PAYMENT_REF:<paymentReference or NONE>"
"REFUND_REQUIRED:<true or false>"
"CANCEL_REASON:<reason or NONE>"
- If the order does not exist, return
List<String> containing only "ORDER_NOT_FOUND".
Order States
CREATED — order exists but payment has not started.
PAYMENT_IN_PROGRESS — payment has been started and is awaiting completion.
PAID — payment completed successfully.
PAYMENT_FAILED — the last payment attempt failed.
CANCELLED — order was cancelled before successful payment.
CANCELLED_REFUND_DUE — order was cancelled after successful payment, so refund is required.
Behavior Rules
- All operations must be deterministic and in-memory.
- An order id is unique across the system.
- The latest
startPayment call that returns "PAYMENT_STARTED" decides the active payment method for the next completePayment call.
- After a failed payment, payment may be started again using any supported payment method.
- After an order reaches
CANCELLED or CANCELLED_REFUND_DUE, no further payment operations are allowed.
- Use exact return strings and exact output formatting for
getOrderDetails.
- Actual refund processing is out of scope for this problem. This problem only tracks whether a refund is required.
- All string inputs passed to methods satisfy the stated length constraints.
Constraints
1 ≤ supportedPaymentMethods.size() ≤ 20
1 ≤ orderId.length() ≤ 50
1 ≤ paymentMethod.length() ≤ 30
1 ≤ paymentReference.length() ≤ 50
1 ≤ reason.length() ≤ 100
1 ≤ totalAmount ≤ 10^9
At most 10^4 total method calls will be made.
Examples
Example 1
ECommerceCheckout checkout = new ECommerceCheckout(supportedPaymentMethods = List.of("CARD", "UPI", "WALLET"))
Output: system initialized
checkout.createOrder(orderId = "ORD-100", totalAmount = 2500)
Output: "ORDER_CREATED"
checkout.startPayment(orderId = "ORD-100", paymentMethod = "UPI")
Output: "PAYMENT_STARTED"
checkout.completePayment(orderId = "ORD-100", paymentReference = "PAY-900", paymentSucceeded = true)
Output: "PAYMENT_COMPLETED"
checkout.getOrderDetails(orderId = "ORD-100")
Output: List.of("ORDER:ORD-100", "AMOUNT:2500", "STATUS:PAID", "PAYMENT_METHOD:UPI", "PAYMENT_REF:PAY-900", "REFUND_REQUIRED:false", "CANCEL_REASON:NONE")
Example 2
ECommerceCheckout checkout = new ECommerceCheckout(supportedPaymentMethods = List.of("CARD", "UPI"))
Output: system initialized
checkout.createOrder(orderId = "ORD-200", totalAmount = 900)
Output: "ORDER_CREATED"
checkout.cancelOrder(orderId = "ORD-200", reason = "USER_REQUESTED")
Output: "ORDER_CANCELLED"
checkout.getOrderDetails(orderId = "ORD-200")
Output: List.of("ORDER:ORD-200", "AMOUNT:900", "STATUS:CANCELLED", "PAYMENT_METHOD:NONE", "PAYMENT_REF:NONE", "REFUND_REQUIRED:false", "CANCEL_REASON:USER_REQUESTED")
Example 3
ECommerceCheckout checkout = new ECommerceCheckout(supportedPaymentMethods = List.of("CARD", "WALLET"))
Output: system initialized
checkout.createOrder(orderId = "ORD-300", totalAmount = 1800)
Output: "ORDER_CREATED"
checkout.startPayment(orderId = "ORD-300", paymentMethod = "CARD")
Output: "PAYMENT_STARTED"
checkout.completePayment(orderId = "ORD-300", paymentReference = "PAY-333", paymentSucceeded = true)
Output: "PAYMENT_COMPLETED"
checkout.cancelOrder(orderId = "ORD-300", reason = "CUSTOMER_CHANGED_MIND")
Output: "ORDER_CANCELLED_WITH_REFUND"
checkout.getOrderDetails(orderId = "ORD-300")
Output: List.of("ORDER:ORD-300", "AMOUNT:1800", "STATUS:CANCELLED_REFUND_DUE", "PAYMENT_METHOD:CARD", "PAYMENT_REF:PAY-333", "REFUND_REQUIRED:true", "CANCEL_REASON:CUSTOMER_CHANGED_MIND")
Example 4
ECommerceCheckout checkout = new ECommerceCheckout(supportedPaymentMethods = List.of("CARD", "UPI"))
Output: system initialized
checkout.createOrder(orderId = "ORD-400", totalAmount = 1200)
Output: "ORDER_CREATED"
checkout.startPayment(orderId = "ORD-400", paymentMethod = "CARD")
Output: "PAYMENT_STARTED"
checkout.completePayment(orderId = "ORD-400", paymentReference = "PAY-400-A", paymentSucceeded = false)
Output: "PAYMENT_FAILED"
checkout.getOrderDetails(orderId = "ORD-400")
Output: List.of("ORDER:ORD-400", "AMOUNT:1200", "STATUS:PAYMENT_FAILED", "PAYMENT_METHOD:CARD", "PAYMENT_REF:NONE", "REFUND_REQUIRED:false", "CANCEL_REASON:NONE")
checkout.startPayment(orderId = "ORD-400", paymentMethod = "UPI")
Output: "PAYMENT_STARTED"
checkout.completePayment(orderId = "ORD-400", paymentReference = "PAY-400-B", paymentSucceeded = true)
Output: "PAYMENT_COMPLETED"
checkout.getOrderDetails(orderId = "ORD-400")
Output: List.of("ORDER:ORD-400", "AMOUNT:1200", "STATUS:PAID", "PAYMENT_METHOD:UPI", "PAYMENT_REF:PAY-400-B", "REFUND_REQUIRED:false", "CANCEL_REASON:NONE")
Example 5
ECommerceCheckout checkout = new ECommerceCheckout(supportedPaymentMethods = List.of("CARD", "UPI"))
Output: system initialized
checkout.createOrder(orderId = "ORD-500", totalAmount = 700)
Output: "ORDER_CREATED"
checkout.startPayment(orderId = "ORD-500", paymentMethod = "CARD")
Output: "PAYMENT_STARTED"
checkout.getOrderDetails(orderId = "ORD-500")
Output: List.of("ORDER:ORD-500", "AMOUNT:700", "STATUS:PAYMENT_IN_PROGRESS", "PAYMENT_METHOD:CARD", "PAYMENT_REF:NONE", "REFUND_REQUIRED:false", "CANCEL_REASON:NONE")
checkout.cancelOrder(orderId = "ORD-500", reason = "ADDRESS_NOT_SERVICEABLE")
Output: "ORDER_CANCELLED"
checkout.getOrderDetails(orderId = "ORD-500")
Output: List.of("ORDER:ORD-500", "AMOUNT:700", "STATUS:CANCELLED", "PAYMENT_METHOD:CARD", "PAYMENT_REF:NONE", "REFUND_REQUIRED:false", "CANCEL_REASON:ADDRESS_NOT_SERVICEABLE")
Example 6
ECommerceCheckout checkout = new ECommerceCheckout(supportedPaymentMethods = List.of("CARD"))
Output: system initialized
checkout.createOrder(orderId = "ORD-600", totalAmount = 500)
Output: "ORDER_CREATED"
checkout.createOrder(orderId = "ORD-600", totalAmount = 0)
Output: "ORDER_ALREADY_EXISTS"
checkout.startPayment(orderId = "ORD-999", paymentMethod = "UPI")
Output: "ORDER_NOT_FOUND"
checkout.completePayment(orderId = "ORD-600", paymentReference = "PAY-600", paymentSucceeded = true)
Output: "PAYMENT_NOT_IN_PROGRESS"