Go back Step-by-Step Guide to Managing Cart Updates with Payment Intents in an E-Commerce Checkout System /* by Tirth Bodawala - July 4, 2024 */ Tech Update PaymentsStripe 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: User clicks on “Pay via Card.” 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. The system retrieves the PaymentIntentSecret. The frontend uses the secret to collect card details securely. The payment is authorized (funds are held) but not captured. The system waits for the seller’s approval to capture the payment. 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.