# Dynamically update payment amounts with Elements

Learn how to modify total amounts when customers change their selections during checkout.

Update the amount of a [Checkout Session](https://docs.stripe.com/api/checkout/sessions.md) in your Elements integration when customers change what they’re buying or how much they pay. Recalculate the totals on your server, and then update the Checkout Session.

#### Common use cases

- Add or remove add-ons (such as gift wrap or a warranty).
- Select a different shipping method or delivery speed.
- Add additional services or charges.
- Apply or remove a discount code or pre-tax store credit.

#### Security best practices

- Recalculate amounts on your server. Don’t trust client-provided prices or totals.
- Authorize the update based on your business rules (for example, enforce max quantities).
- Only update Sessions that are active and not completed or expired.

#### Constraints and behavior

- You can update the amount while the Checkout Session is awaiting payment.
- After confirmation, you generally can’t increase the amount.

## Update the client SDK [Client-side]

When using Elements with the Checkout Sessions API, wrap client calls to your server in `runServerUpdate` so the checkout state and totals refresh.

Wrap `runServerUpdate` calls in `try`/`catch` blocks to handle errors from your server and from `runServerUpdate` itself (for example, timeouts). `response.type === 'error'` covers failures in Stripe’s internal retrieval of the updated session. Errors returned by your own server (such as 4xx or 5xx responses) aren’t reflected in `response` because the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) resolves for any completed HTTP response regardless of status code. Check `response.ok` inside your update function and throw on failure so that the `catch` block is reached.

#### HTML + JS

```javascript
import {loadStripe} from '@stripe/stripe-js';

const stripe = await loadStripe('<<YOUR_PUBLISHABLE_KEY>>');

const checkout = stripe.initCheckoutElementsSdk({
  clientSecret,
  elementsOptions: {/* ... */},
});

// Example: Add additional service using price_data
const loadActionsResult = await checkout.loadActions();
if (loadActionsResult.type === 'success') {
  const actions = loadActionsResult.actions;
  const session = actions.getSession();
  document
  .getElementById('add-service')
  .addEventListener('click', async () => {
    const updateOnServer = async () => {
      const response = await fetch('/update-custom-amount', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({
          checkout_session_id: session.id,
          product_id: 'gift_wrap', // Server looks up actual price
        }),
      });
      if (!response.ok) {
        const body = await response.json();
        throw new Error(body.message);
      }
    };

    try {
      const response = await actions.runServerUpdate(updateOnServer);
      if (response.type === 'error') {
        // Handle Stripe API errors (for example, session retrieval failure)
      }
    } catch (error) {
      // Handle promise rejection from your server (4xx/5xx errors) or
      // from runServerUpdate itself (for example, timeouts).
      // error.message contains the message thrown from your update function.
    }
  });
}
```

#### React

```jsx
import React from 'react';
import {useCheckoutElements} from '@stripe/react-stripe-js';

export const AddServiceButton = () => {
  const checkoutState = useCheckoutElements();
  if (checkoutState.type === 'loading') {
    return (
      <div>Loading...</div>
    );
  } else if (checkoutState.type === 'error') {
    return (
      <div>Error: {checkoutState.error.message}</div>
    );
  }
  const {runServerUpdate, id} = checkoutState;

  const addService = async () => {
    const updateOnServer = async () => {
      const response = await fetch('/update-custom-amount', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({
          checkout_session_id: id,
          product_id: 'gift_wrap', // Server looks up actual price
        }),
      });
      if (!response.ok) {
        const body = await response.json();
        throw new Error(body.message);
      }
    };

    try {
      const res = await runServerUpdate(updateOnServer);
      if (res.type === 'error') {
        // Handle Stripe API errors (for example, session retrieval failure)
      }
    } catch (error) {
      // Handle promise rejection from your server (4xx/5xx errors) or
      // from runServerUpdate itself (for example, timeouts).
      // error.message contains the message thrown from your update function.
    }
  };

  return (
    <button onClick={addService}>
      Add Gift Wrap
    </button>
  );
};
```

## Create server endpoints [Server-side]

Calculate amounts and validate inputs on your server. Then, you can update `line_items` with [price_data](https://docs.stripe.com/api/checkout/sessions/create.md#create_checkout_session-line_items-price_data) to add ad-hoc charges.

> Updating the `line_items` or `price_data` recalculates the Session total and taxes.

#### Node

```node
import express from 'express';
import Stripe from 'stripe';

const app = express();
app.use(express.json());

// Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
const stripe = new Stripe('<<YOUR_SECRET_KEY>>');


// Product catalog with prices - store this securely server-side
const PRODUCTS = {
  gift_wrap: { name: 'Gift Wrap', price: 500 }, // $5.00
  express_shipping: { name: 'Express Shipping', price: 1500 }, // $15.00
  warranty: { name: 'Extended Warranty', price: 2000 }, // $20.00
};

app.post('/update-custom-amount', async (req, res) => {
  try {
    const {checkout_session_id, product_id} = req.body;

    const session = await stripe.checkout.sessions.retrieve(checkout_session_id);
    if (session.status === 'complete' || session.expires_at * 1000 < Date.now()) {
      return res.status(400).json({error: 'Session is no longer updatable.'});
    }

    // Look up product price server-side
    const product = PRODUCTS[product_id];
    if (!product) {
      return res.status(400).json({error: 'Invalid product ID'});
    }

    // Add the additional product via price_data
    const updated = await stripe.checkout.sessions.update(checkout_session_id, {
      line_items: [
        {
          price_data: {
            currency: 'usd',
            product_data: {name: product.name},
            unit_amount: product.price,
          },
          quantity: 1,
        },
      ],
    });

    return res.json({id: updated.id, amount_total: updated.amount_total});
  } catch (err) {
    return res.status(400).json({error: err.message});
  }
});

app.listen(4242, () => console.log('Server running on port 4242'));
```
