> ## Documentation Index
> Fetch the complete documentation index at: https://www.commercengine.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Checkout

> This guide walks you through the process of converting a customer's shopping cart into a placed order, including address validation, shipping checks, payment processing, and handling payment retries. It utilizes endpoints primarily from the Carts, Orders, Payments, Shipping, and Common modules.

## Checkout Flow

This guide walks you through the process of converting a customer's shopping cart into a placed order, including address validation, shipping checks, payment processing, and handling payment retries. It utilizes endpoints primarily from the Carts, Orders, Payments, Shipping, and Common modules.

<Tip>
  **Recommended: Use Hosted Checkout**

  For most storefronts, we recommend the [Hosted Checkout](/hosted-checkout/overview) — a pre-built, embeddable checkout that handles cart, authentication, address collection, payments, and order confirmation. The custom flow below is for developers who need full UI control.
</Tip>

<Info>
  **Prerequisites**

  * A valid `cart_id` representing the customer's cart with items added.
  * A **logged-in** user session token (`access_token`). Anonymous users cannot create orders — the user must authenticate before checkout.
</Info>

**High-Level Steps:**

1. **Review Cart:** Display the final cart contents and totals.
2. **Authenticate User:** Ensure the user is logged in (anonymous users cannot create orders). Use OTP login with `register_if_not_exists: true` for a seamless experience.
3. **Collect Addresses:** Gather or confirm billing and shipping addresses.
4. **Validate Pincode Serviceability:** Check if shipping is available to the destination.
5. **(Optional) Apply Discounts/Credits:** Allow final application of coupons, loyalty points, or credit balance.
6. **Create Order & Initiate Payment:** Send the cart details to create an order and get payment instructions.
7. **Process Payment:** Redirect user or use SDKs to complete payment via the chosen gateway (Juspay recommended).
8. **Handle Payment Callback/Redirect:** User returns to your site from the payment gateway.
9. **Verify Payment Status:** Poll the Commerce Engine API to confirm the final payment status.
10. **Display Confirmation/Failure:** Show the appropriate order confirmation or payment failure/retry screen.

<Steps titleSize="h3">
  <Step title="Review Cart">
    Before proceeding, ensure the customer can review their cart. Fetch the latest cart state:

    * `GET /carts/{cart_id}` or `GET /carts/users/{user_id}`
    * Display `cart_items`, pricing breakdown (`subtotal`, `tax`, `shipping`, `grand_total`), applied discounts (`coupon_code`, `loyalty_points_redeemed`, etc.), and the final `to_be_paid` amount.
  </Step>

  <Step title="Collect & Update Addresses">
    Accurate addresses are required for tax calculation, shipping cost determination, and delivery.

    * **Logged-in Users:**
      * Fetch saved addresses using `GET /customers/{user_id}/addresses`.
      * Allow the user to select existing billing and shipping addresses or add a new one (`POST /customers/{user_id}/addresses`).
      * Update the cart with the selected address IDs using `POST /carts/{id}/address`:

        ```json Update Cart with Saved Address IDs theme={"theme":{"light":"github-light","dark":"github-dark"}}
        {
          "billing_address_id": "SAVED_ADDRESS_ID_1",
          "shipping_address_id": "SAVED_ADDRESS_ID_2"
        }
        ```

    * **Anonymous / Guest Users:**
      * Provide forms to collect billing and shipping address details.
      * Use `GET /common/countries`, `GET /common/countries/{country_iso_code}/states`, and `GET /common/countries/{country_iso_code}/pincodes` (with query `pincode=...&limit=1`) to potentially provide dropdowns or validate inputs for Country, State, and City based on Pincode.
      * Update the cart with the collected address details using `POST /carts/{id}/address`:

        ```json Update Cart with New Address Objects theme={"theme":{"light":"github-light","dark":"github-dark"}}
        {
          "billing_address": { /* CustomerAddress object */ },
          "shipping_address": { /* CustomerAddress object */ }
        }
        ```

    <Info>
      **Cart Update**

      The `POST /carts/{id}/address` call returns the updated `Cart` object, which will now include potentially recalculated `shipping_amount_including_tax` and `grand_total` based on the destination. Re-render the cart summary if these values changed.
    </Info>
  </Step>

  <Step title="Validate Pincode Serviceability (Optional)">
    <Info>
      **Provider Dependency**

      This step requires an integration with a shipping provider that supports pincode serviceability checks through Commerce Engine.
    </Info>

    Before allowing the user to proceed to payment, verify if delivery is possible to their chosen shipping pincode.

    * `GET /shipment/pincode-serviceability/{pincode}`
    * **Path Parameter:** `pincode` (from the shipping address).
    * **Response:**
      * `200 OK` with `success: true`: Delivery is available.
      * `4xx` Error (e.g., 404 or specific error code if defined): Delivery is not available. Display an appropriate message to the user, preventing them from proceeding with this address.

    <Frame caption="Pincode Serviceability Check Flow">
      <iframe title="Pincode Serviceability Check Flow" allowfullscreen width="100%" height="700" src="https://mermaid.tarkai.dev/embed?definition=sequenceDiagram%0A%20%20%20%20participant%20ClientApp%20as%20Client%20Application%0A%20%20%20%20participant%20User%0A%20%20%20%20participant%20CE_API%20as%20CE%20API%0A%0A%20%20%20%20User-%3E%3EClientApp%3A%20Enters%20Shipping%20Address%20(Pincode%3A%20110011)%0A%20%20%20%20ClientApp-%3E%3ECE_API%3A%20POST%20%2Fcarts%2F%7BcartId%7D%2Faddress%0A%20%20%20%20activate%20CE_API%0A%20%20%20%20CE_API--%3E%3EClientApp%3A%20200%20OK%20(Updated%20Cart%20Object)%0A%20%20%20%20deactivate%20CE_API%0A%20%20%20%20ClientApp-%3E%3EClientApp%3A%20Render%20updated%20Cart%20Summary%20(potential%20shipping%20cost%20change)%0A%0A%20%20%20%20ClientApp-%3E%3ECE_API%3A%20GET%20%2Fshipment%2Fpincode-serviceability%2F110011%0A%20%20%20%20activate%20CE_API%0A%20%20%20%20alt%20Serviceable%0A%20%20%20%20%20%20%20%20CE_API--%3E%3EClientApp%3A%20200%20OK%20(success%3A%20true)%0A%20%20%20%20%20%20%20%20ClientApp-%3E%3EClientApp%3A%20Allow%20User%20to%20proceed%20to%20Payment%0A%20%20%20%20else%20Not%20Serviceable%0A%20%20%20%20%20%20%20%20CE_API--%3E%3EClientApp%3A%20Error%20Response%20(e.g.%2C%20404)%0A%20%20%20%20%20%20%20%20ClientApp-%3E%3EUser%3A%20Display%20%22Sorry%2C%20delivery%20not%20available%20to%20this%20pincode.%22%0A%20%20%20%20end%0A%20%20%20%20deactivate%20CE_API" />
    </Frame>
  </Step>

  <Step title="Apply Final Discounts/Credits (Optional)">
    Provide options for the user to apply any last-minute coupons, loyalty points (logged-in only), or credit balance (logged-in only) using the respective `POST /carts/{id}/...` endpoints described in the Carts module documentation. Remember to handle potential errors (e.g., coupon requires login). Update the cart summary after each successful application.
  </Step>

  <Step title="Create Order & Initiate Payment">
    This is the core transition from cart to order.

    * `POST /orders`

    * **Authentication:** Bearer Token (must be a **logged-in** user, not anonymous)

    * **Request Body:**

      * `cart_id`: **Required**. The ID of the finalized cart.
      * `payment_method`: **Required**. An object containing the payment provider and integration details.

      **Example for Juspay (Hyper-Checkout - Recommended):**

      ```json Create Order Request (Juspay Hyper-Checkout) theme={"theme":{"light":"github-light","dark":"github-dark"}}
      {
        "cart_id": "YOUR_CART_ID",
        "payment_method": {
          "payment_provider_slug": "JUSPAY",
          "action": "paymentPage",
          "integration_type": "hyper-checkout",
          "return_url": "https://yourstore.com/checkout/payment-return",
          "gateway_reference_id": "OPTIONAL_PG_ID_IF_NEEDED"
        }
      }
      ```

      **Example for PayU:**

      ```json Create Order Request (PayU) theme={"theme":{"light":"github-light","dark":"github-dark"}}
      {
        "cart_id": "YOUR_CART_ID",
        "payment_method": {
          "payment_provider_slug": "PAYU",
          "furl": "https://yourstore.com/checkout/payment-failed",
          "surl": "https://yourstore.com/checkout/payment-success"
        }
      }
      ```

          <Tip>
            **Advanced Use Cases**

            For Juspay Express Checkout or other complex integrations, consult the specific `JuspayPaymentGatewayParams` and `PayuPaymentGatewayParams` schemas and potentially use helper endpoints like `POST /payments/juspay/create-order` or `POST /payments/generate-hash` (PayU) *before* this step if required by your chosen integration pattern. **Hyper-Checkout simplifies this significantly.**
          </Tip>

    * **Response (Success - `200 OK`):**

      * `message`, `success`
      * `content`:
        * `order`: The newly created `OrderDetail` object (initial status likely 'draft' or similar, `payment_status: 'pending'`). Contains the `order_number`.
        * `payment_required`: Boolean (usually `true`).
        * `payment_info`: An object containing instructions for the payment gateway. The structure depends on the chosen gateway (`PayuPaymentInfo` or `JuspayPaymentInfo`).

      **Example `payment_info` for Juspay Hyper-Checkout:**

      ```json Create Order Response (Juspay Hyper-Checkout) theme={"theme":{"light":"github-light","dark":"github-dark"}}
      // ... within response content
      "payment_info": {
          "payment_gateway": "JUSPAY",
          "id": "...", // Juspay Payment Session ID
          "status": "NEW",
          "order_id": "...", // Juspay Order ID
          "payment_links": {
              "web": "https://api.juspay.in/...", // **URL to redirect the user to**
              "mobile": "...",
              "iframe": "..."
          },
          "sdk_payload": { /* Payload if using Juspay Mobile SDKs */ }
      }
      ```

      **Example `payment_info` for PayU:**

      ```json Create Order Response (PayU) theme={"theme":{"light":"github-light","dark":"github-dark"}}
       // ... within response content
      "payment_info": {
          "payment_gateway": "PAYU",
          "key": "YOUR_PAYU_KEY",
          "txnid": "CE_GENERATED_TXN_ID",
          "amount": 100.00,
          "productinfo": "Order Description",
          "firstname": "Test",
          "email": "test@example.com",
          "phone": "9999999999",
          "surl": "https://yourstore.com/checkout/payment-success",
          "furl": "https://yourstore.com/checkout/payment-failed",
          "hash": "CALCULATED_PAYU_HASH"
          // ... other PayU specific fields
          // This data needs to be submitted to PayU via form post typically.
      }
      ```
  </Step>

  <Step title="Process Payment">
    Based on the `payment_info` received:

    * **Juspay Hyper-Checkout:** Redirect the user's browser to the `payment_info.payment_links.web` URL. Juspay handles the payment method selection and processing.
    * **PayU:** Typically requires submitting the fields from `payment_info` as a form POST to the PayU payment URL.
    * **Other Integrations (Juspay Express, SDKs):** Follow the specific integration guide for the chosen method, using data from `payment_info` (e.g., `sdk_payload` for mobile SDKs).

    The user interacts with the payment gateway interface to complete the payment.
  </Step>

  <Step title="Handle Payment Callback/Redirect">
    Once the user completes (or cancels) the payment on the gateway's page/interface, the gateway redirects them back to the `return_url` (for Juspay Hyper-Checkout) or `surl`/`furl` (for PayU) that you provided during order creation.

    * This callback URL is on **your storefront application**.
    * The URL might contain query parameters added by the gateway (e.g., transaction status, IDs).

    <Warning>
      **Verify Status via API**

      **DO NOT rely solely on callback/redirect parameters** to determine the final order status. They are informational and can sometimes be unreliable or manipulated. You **must** verify the definitive status with the Commerce Engine API in the next step.
    </Warning>
  </Step>

  <Step title="Verify Payment Status">
    On your callback/return page (`return_url`), the crucial step is to poll the Commerce Engine API to get the authoritative payment status for the order.

    * **Endpoint:** `GET /orders/{order_number}/payment-status`
    * **Authentication:** Bearer Token
    * **Path Parameter:** `order_number` (obtained in Step 5 response).
    * **Polling Logic:**
      1. Make the first call immediately when the user lands on the `return_url`.
      2. Check the `content.status` in the response.
      3. **If `status` is `success`:** Payment is confirmed. Proceed to Step 9 (Confirmation).
      4. **If `status` is `failed`:** Payment failed. Check `content.is_retry_available`. Proceed to Step 9 (Failure/Retry).
      5. **If `status` is `pending` or `partially_paid`:** The payment outcome is not yet final. Wait for a short interval (e.g., **3 seconds**) and repeat the API call (poll again). Continue polling until the status becomes `success` or `failed`, or until a reasonable timeout (e.g., 1-2 minutes) is reached.
    * **Response (`200 OK`):**

      ```json Payment Status Response theme={"theme":{"light":"github-light","dark":"github-dark"}}
      {
        "message": "Payment status retrieved.",
        "success": true,
        "content": {
          "status": "success", // "pending", "failed", "partially_paid"
          "amount_paid": 100.00,
          "amount_unpaid": 0.00,
          "is_retry_available": false // boolean
        }
      }
      ```

    <Info>
      **Why Polling is Necessary**

      Payment gateway notifications can be delayed or asynchronous. Polling `GET /orders/{order_number}/payment-status` ensures you get the confirmed status directly from Commerce Engine, which manages gateway webhooks and status updates reliably on the backend.
    </Info>
  </Step>

  <Step title="Display Confirmation / Failure / Retry">
    Based on the final status obtained from polling:

    * **If `status` == `success`:**
      * Display an Order Confirmation page showing order details (you can fetch full details again using `GET /orders/{order_number}`).
      * Thank the customer!
    * **If `status` == `failed`:**
      * Check `is_retry_available`:
        * **If `true`:** Display a "Payment Failed" message and offer a "Retry Payment" button. See "Handling Payment Retries" below.
        * **If `false`:** Display an "Order Failed" message, explaining that payment could not be processed and potentially advising the customer to try again later or contact support. Do *not* offer an immediate retry.
    * **If Polling Times Out (Status remains `pending`):** Display a message indicating the payment is still processing and advise the customer to check their order history later or contact support. Avoid confirming the order prematurely.

    #### Handling Payment Retries

    If payment failed but `is_retry_available` was true:

    1. When the user clicks "Retry Payment":

    2. Call `POST /orders/{order_number}/retry-payment`.

    3. **Authentication:** Bearer Token

    4. **Path Parameter:** `order_number`.

    5. **Request Body:** Requires `payment_method` to re-initiate the gateway flow (e.g., providing `return_url` again for Juspay).

       ```json Payment Retry Request Body (Juspay Example) theme={"theme":{"light":"github-light","dark":"github-dark"}}
        {
          "payment_method": {
            "payment_provider_slug": "JUSPAY",
            "action": "paymentPage",
            "integration_type": "hyper-checkout",
            "return_url": "https://yourstore.com/checkout/payment-return"
          }
        }
       ```

    6. **Response:** Similar to `POST /orders`, provides new `payment_info` (e.g., a new Juspay `payment_links.web` URL).

    7. Redirect the user to the new payment URL (Step 6).

    8. The process continues from Step 7 (Handling Callback) and Step 8 (Verify Payment Status).
  </Step>
</Steps>

**Checkout Flow Diagram (Juspay Hyper-Checkout):**

<Frame caption="Checkout & Retry Flow (Juspay Hyper-Checkout)">
  <iframe title="Checkout & Retry Flow (Juspay Hyper-Checkout)" allowfullscreen width="100%" height="1000" src="https://mermaid.tarkai.dev/embed?definition=sequenceDiagram%0A%20%20%20%20participant%20ClientApp%20as%20Client%20Application%0A%20%20%20%20participant%20User%0A%20%20%20%20participant%20CE_API%20as%20CE%20API%0A%20%20%20%20participant%20Juspay%0A%0A%20%20%20%20ClientApp-%3E%3ECE_API%3A%20POST%20%2Fcarts%2F%7Bid%7D%2Faddress%20(Select%2FEnter%20Address)%0A%20%20%20%20activate%20CE_API%0A%20%20%20%20CE_API--%3E%3EClientApp%3A%20200%20OK%20(Updated%20Cart)%0A%20%20%20%20deactivate%20CE_API%0A%20%20%20%20ClientApp-%3E%3ECE_API%3A%20GET%20%2Fshipment%2Fpincode-serviceability%2F%7Bpincode%7D%0A%20%20%20%20activate%20CE_API%0A%20%20%20%20CE_API--%3E%3EClientApp%3A%20200%20OK%20(Serviceable)%0A%20%20%20%20deactivate%20CE_API%0A%0A%20%20%20%20User-%3E%3EClientApp%3A%20Clicks%20%22Proceed%20to%20Payment%22%0A%20%20%20%20ClientApp-%3E%3ECE_API%3A%20POST%20%2Forders%20(Req%3A%20%7Bcart_id%2C%20pg%3AJUSPAY%2C%20params%3A%7Bintegration%3Ahyper%2C...%7D%7D)%0A%20%20%20%20activate%20CE_API%0A%20%20%20%20CE_API--%3E%3EClientApp%3A%20200%20OK%0A%20%20%20%20deactivate%20CE_API%0A%0A%20%20%20%20ClientApp-%3E%3EUser%3A%20Redirect%20to%20payment%20link%20URL%0A%20%20%20%20User-%3E%3EJuspay%3A%20Interacts%20with%20Juspay%20Checkout%20(Enters%20Card%2FUPI%20etc.)%0A%20%20%20%20Juspay--%3E%3EUser%3A%20Payment%20Complete%2FFailed%2C%20Redirect%20back%20to%20ClientApp's%20return_url%0A%0A%20%20%20%20ClientApp-%3E%3ECE_API%3A%20GET%20%2Forders%2F%7Border_number%7D%2Fpayment-status%20(Polling%20Start)%0A%20%20%20%20activate%20CE_API%0A%20%20%20%20loop%20Until%20Status%20is%20Success%20or%20Failed%20or%20Timeout%0A%20%20%20%20%20%20%20%20CE_API--%3E%3EClientApp%3A%20200%20OK%20(Res%3A%20%7Bstatus%3A%20'pending'%7D)%0A%20%20%20%20%20%20%20%20ClientApp-%3E%3EClientApp%3A%20Wait%203%20seconds%0A%20%20%20%20%20%20%20%20ClientApp-%3E%3ECE_API%3A%20GET%20%2Forders%2F%7Border_number%7D%2Fpayment-status%0A%20%20%20%20end%0A%20%20%20%20CE_API--%3E%3EClientApp%3A%20200%20OK%20(Res%3A%20%7Bstatus%3A%20'success'%7D)%20%2F%2F%20Or%20'failed'%20with%20is_retry_available%0A%20%20%20%20deactivate%20CE_API%0A%0A%20%20%20%20alt%20Payment%20Success%0A%20%20%20%20%20%20%20%20%20ClientApp-%3E%3EUser%3A%20Display%20Order%20Confirmation%20Page%0A%20%20%20%20else%20Payment%20Failed%0A%20%20%20%20%20%20%20%20alt%20Retry%20Available%20(is_retry_available%20%3D%3D%20true)%0A%20%20%20%20%20%20%20%20%20%20%20%20ClientApp-%3E%3EUser%3A%20Display%20Payment%20Failed%20%2B%20Retry%20Button%0A%20%20%20%20%20%20%20%20%20%20%20%20User-%3E%3EClientApp%3A%20Clicks%20Retry%0A%20%20%20%20%20%20%20%20%20%20%20%20ClientApp-%3E%3ECE_API%3A%20POST%20%2Forders%2F%7Border_number%7D%2Fretry-payment%0A%20%20%20%20%20%20%20%20%20%20%20%20activate%20CE_API%0A%20%20%20%20%20%20%20%20%20%20%20%20CE_API--%3E%3EClientApp%3A%20200%20OK%20(Res%3A%20%7Bnew%20payment_info%7D)%0A%20%20%20%20%20%20%20%20%20%20%20%20deactivate%20CE_API%0A%20%20%20%20%20%20%20%20%20%20%20%20ClientApp-%3E%3EUser%3A%20Redirect%20to%20NEW%20payment%20link%0A%20%20%20%20%20%20%20%20else%20No%20Retry%20Available%20(is_retry_available%20%3D%3D%20false)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20ClientApp-%3E%3EUser%3A%20Display%20Order%20Failed%0A%20%20%20%20%20%20%20%20end%0A%20%20%20%20end" />
</Frame>
