Discuss your project

Step-by-Step Guide to Managing Cart Updates with Payment Intents in an E-Commerce Checkout System

/* by Tirth Bodawala - July 4, 2024 */

In this blog post, we’ll walk through the process of managing cart updates with payment intents in an e-commerce checkout system. We’ll break down the process into simple steps and provide code examples for both backend (PHP) and frontend (JavaScript) implementations. Our goal is to ensure that payments are authorized and only captured upon seller approval, even if the cart is updated after creating the payment intent.

Step 1: Understanding the Flow

First, let’s understand the overall flow of managing cart updates with payment intents:

  1. User clicks on “Pay via Card.”
  2. The system checks if the cart has a payment intent:
    • If it does, the payment intent is updated.
    • If it doesn’t, a new payment intent is created.
  3. The system retrieves the PaymentIntentSecret.
  4. The frontend uses the secret to collect card details securely.
  5. The payment is authorized (funds are held) but not captured.
  6. The system waits for the seller’s approval to capture the payment.
  7. If the cart is updated, the system ensures the payment intent is also updated.

Step 2: Frontend Implementation

Collecting Card Details and Authorizing Payment

In the frontend, we need to handle the user’s action to pay via card and securely collect card details. Here’s how you can implement it:

async function handlePayViaCard() {
    const response = await fetch('/api/getPaymentIntentSecret', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ cartId: cart.id }),
    });
    const { paymentIntentSecret } = await response.json();
    await handleCardDetailsSubmission(paymentIntentSecret);
}

async function handleCardDetailsSubmission(paymentIntentSecret) {
    // Collect card details using the payment processor's SDK
    const { cardDetails } = await collectCardDetails(paymentIntentSecret);

    const response = await fetch('/api/authorizePayment', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ paymentIntentId: cardDetails.paymentIntentId }),
    });

    const paymentResult = await response.json();
    if (paymentResult.success) {
        // Payment authorization successful
        // Wait for seller approval
        await waitForSellerApproval(cardDetails.paymentIntentId);
    } else {
        // Handle payment authorization failure
    }
}

async function waitForSellerApproval(paymentIntentId) {
    // Logic to wait for seller approval (e.g., polling or real-time updates)
    const sellerApproved = await checkSellerApproval();

    if (sellerApproved) {
        const response = await fetch('/api/capturePayment', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ paymentIntentId }),
        });
        const paymentResult = await response.json();
        if (paymentResult.success) {
            // Payment captured successfully
        } else {
            // Handle payment capture failure
        }
    } else {
        const response = await fetch('/api/cancelPayment', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ paymentIntentId }),
        });
        const cancelResult = await response.json();
        if (cancelResult.success) {
            // Payment canceled successfully
        } else {
            // Handle payment cancelation failure
        }
    }
}

async function updateCart(updates) {
    const response = await fetch('/api/updateCart', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ cartId: cart.id, updates }),
    });
    const updatedCart = await response.json();
    if (updatedCart.cart.payment_intent_id) {
        // Update the payment intent as well
        await fetch('/api/getPaymentIntentSecret', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ cartId: cart.id }),
        });
    }
}

Step 3: Backend Implementation

Managing Payment Intents and Cart Updates

In the backend, we need to manage the creation and updating of payment intents, as well as handle cart updates appropriately.

// Get PaymentIntentSecret
$app->post('/api/getPaymentIntentSecret', function ($request, $response, $args) {
    $data = $request->getParsedBody();
    $cartId = $data['cartId'];
    
    // Retrieve cart from database
    $cart = getCartById($cartId);
    $paymentIntent = null;

    if ($cart['payment_intent_id']) {
        // Update existing payment intent
        $paymentIntent = updatePaymentIntent($cart['payment_intent_id']);
    } else {
        // Create new payment intent
        $paymentIntent = createPaymentIntent();
        updateCart($cartId, ['payment_intent_id' => $paymentIntent->id]);
    }

    return $response->withJson(['paymentIntentSecret' => $paymentIntent->client_secret]);
});

// Authorize Payment
$app->post('/api/authorizePayment', function ($request, $response, $args) {
    $data = $request->getParsedBody();
    $paymentIntentId = $data['paymentIntentId'];

    // Authorize the payment
    $paymentResult = authorizePayment($paymentIntentId);

    if ($paymentResult->success) {
        return $response->withJson(['success' => true]);
    } else {
        return $response->withJson(['success' => false, 'error' => $paymentResult->error]);
    }
});

// Capture Payment
$app->post('/api/capturePayment', function ($request, $response, $args) {
    $data = $request->getParsedBody();
    $paymentIntentId = $data['paymentIntentId'];

    // Capture the payment
    $paymentResult = capturePayment($paymentIntentId);

    if ($paymentResult->success) {
        return $response->withJson(['success' => true]);
    } else {
        return $response->withJson(['success' => false, 'error' => $paymentResult->error]);
    }
});

// Release Authorization (Cancel Payment)
$app->post('/api/cancelPayment', function ($request, $response, $args) {
    $data = $request->getParsedBody();
    $paymentIntentId = $data['paymentIntentId'];

    // Cancel the payment authorization
    $paymentResult = cancelPayment($paymentIntentId);

    if ($paymentResult->success) {
        return $response->withJson(['success' => true]);
    } else {
        return $response->withJson(['success' => false, 'error' => $paymentResult->error]);
    }
});

// Update Cart
$app->post('/api/updateCart', function ($request, $response, $args) {
    $data = $request->getParsedBody();
    $cartId = $data['cartId'];
    $updates = $data['updates'];

    // Retrieve cart from database
    $cart = getCartById($cartId);

    if ($cart['payment_intent_id']) {
        // Update payment intent if it exists
        updatePaymentIntent($cart['payment_intent_id']);
    }

    // Update the cart
    $updatedCart = updateCart($cartId, $updates);

    return $response->withJson(['cart' => $updatedCart]);
});

Step 4: Handling Cart Updates

When the cart is updated after the payment intent has been created, we need to ensure the payment intent is updated accordingly. This involves checking if the cart has a payment intent and updating it if necessary.

Updating Cart and Payment Intent (PHP Backend):

function updateCart($cartId, $updates) {
    // Retrieve the cart from the database
    $cart = getCartById($cartId);

    if ($cart['payment_intent_id']) {
        // Update the existing payment intent
        updatePaymentIntent($cart['payment_intent_id']);
    }

    // Update the cart details
    $updatedCart = saveCartUpdates($cartId, $updates);

    return $updatedCart;
}

function updatePaymentIntent($paymentIntentId) {
    // Logic to update the payment intent using the payment processor's API
    // ...
    return $updatedPaymentIntent;
}

function saveCartUpdates($cartId, $updates) {
    // Logic to save the cart updates to the database
    // ...
    return $updatedCart;
}

Conclusion

By following these steps, you can effectively manage cart updates and payment intents in your e-commerce checkout system. This ensures that payments are authorized and only captured upon seller approval, even if the cart is updated after creating the payment intent.

By integrating these frontend and backend processes, you can create a seamless and secure checkout experience for your users. This detailed flow ensures that all scenarios, including cart updates, are handled properly, providing a robust solution for managing payments in your e-commerce platform.