134. Design Order Checkout and Payment for E-commerce Website

Asked in

Design Order Checkout and Payment for E-commerce Website
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"




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