# How to collect billing details in Stripe Checkout

- Tool: Stripe
- Difficulty: Beginner
- Time required: 10 minutes
- Compatibility: Stripe API v2024-12+, Node.js 18+
- Last updated: March 2026

## TL;DR

Collect billing details in Stripe Checkout using billing_address_collection: 'required' and custom_fields for additional inputs like company name or PO number. Stripe displays address fields on the checkout page and includes the data in the Checkout Session object for retrieval via webhook or API.

## Collecting Billing and Shipping Details in Stripe Checkout

By default, Stripe Checkout only collects payment details and email. You can require a full billing address with billing_address_collection: 'required', add custom text or dropdown fields with custom_fields, and collect shipping addresses with shipping_address_collection. All collected data is stored on the Checkout Session and available via the API or webhooks after payment.

## Before you start

- A working Stripe Checkout Session endpoint
- Node.js 18+ with the stripe npm package
- Your Stripe secret key (sk_test_) in environment variables

## Step-by-step guide

### 1. Enable billing address collection

Set billing_address_collection to 'required' when creating the Checkout Session. Stripe adds address fields (street, city, state, ZIP, country) to the checkout page.

```
const session = await stripe.checkout.sessions.create({
  mode: 'payment',
  billing_address_collection: 'required',
  line_items: [
    {
      price_data: {
        currency: 'usd',
        product_data: { name: 'Widget' },
        unit_amount: 2000,
      },
      quantity: 1,
    },
  ],
  success_url: 'https://yoursite.com/success?session_id={CHECKOUT_SESSION_ID}',
  cancel_url: 'https://yoursite.com/cancel',
});
```

**Expected result:** The Checkout page shows billing address fields that the customer must fill in before paying.

### 2. Add custom fields

Use custom_fields to add up to 3 additional text or dropdown inputs to the Checkout page. These are useful for collecting PO numbers, company names, or order notes.

```
const session = await stripe.checkout.sessions.create({
  mode: 'payment',
  billing_address_collection: 'required',
  custom_fields: [
    {
      key: 'company_name',
      label: { type: 'custom', custom: 'Company Name' },
      type: 'text',
      optional: true,
    },
    {
      key: 'po_number',
      label: { type: 'custom', custom: 'PO Number' },
      type: 'text',
      optional: true,
    },
  ],
  line_items: [{ price: 'price_xxx', quantity: 1 }],
  success_url: 'https://yoursite.com/success?session_id={CHECKOUT_SESSION_ID}',
  cancel_url: 'https://yoursite.com/cancel',
});
```

**Expected result:** The Checkout page shows Company Name and PO Number fields above the payment details.

### 3. Enable shipping address collection

To collect a shipping address separately from the billing address, use shipping_address_collection with allowed_countries.

```
const session = await stripe.checkout.sessions.create({
  mode: 'payment',
  billing_address_collection: 'required',
  shipping_address_collection: {
    allowed_countries: ['US', 'CA', 'GB', 'AU'],
  },
  line_items: [{ price: 'price_xxx', quantity: 1 }],
  success_url: 'https://yoursite.com/success?session_id={CHECKOUT_SESSION_ID}',
  cancel_url: 'https://yoursite.com/cancel',
});
```

**Expected result:** The Checkout page shows both billing and shipping address sections. Shipping is limited to the specified countries.

### 4. Retrieve collected details after payment

After the customer pays, retrieve the Checkout Session to access billing details, shipping details, and custom field values.

```
app.get('/api/order-details', async (req, res) => {
  const session = await stripe.checkout.sessions.retrieve(
    req.query.session_id
  );

  res.json({
    email: session.customer_details?.email,
    name: session.customer_details?.name,
    billing_address: session.customer_details?.address,
    shipping: session.shipping_details,
    custom_fields: session.custom_fields, // Array of { key, text/dropdown value }
    payment_status: session.payment_status,
  });
});
```

**Expected result:** The API returns all collected customer details including billing address, shipping address, and custom field values.

## Complete code example

File: `server.js`

```javascript
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

const app = express();
app.use(express.static('public'));
app.use(express.json());

app.post('/create-checkout-session', async (req, res) => {
  try {
    const session = await stripe.checkout.sessions.create({
      mode: 'payment',
      billing_address_collection: 'required',
      shipping_address_collection: {
        allowed_countries: ['US', 'CA', 'GB', 'AU', 'DE', 'FR'],
      },
      custom_fields: [
        {
          key: 'company_name',
          label: { type: 'custom', custom: 'Company Name' },
          type: 'text',
          optional: true,
        },
        {
          key: 'order_notes',
          label: { type: 'custom', custom: 'Order Notes' },
          type: 'text',
          optional: true,
        },
      ],
      line_items: [
        {
          price_data: {
            currency: 'usd',
            product_data: { name: 'Premium Widget' },
            unit_amount: 5000,
          },
          quantity: 1,
        },
      ],
      success_url: `${req.headers.origin}/success?session_id={CHECKOUT_SESSION_ID}`,
      cancel_url: `${req.headers.origin}/cancel`,
    });

    res.json({ url: session.url });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.get('/api/order-details', async (req, res) => {
  try {
    const session = await stripe.checkout.sessions.retrieve(req.query.session_id);
    res.json({
      email: session.customer_details?.email,
      name: session.customer_details?.name,
      billing_address: session.customer_details?.address,
      shipping: session.shipping_details,
      custom_fields: session.custom_fields,
      amount_total: session.amount_total,
      payment_status: session.payment_status,
    });
  } catch (err) {
    res.status(400).json({ error: 'Invalid session' });
  }
});

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

## Common mistakes

- **Assuming billing address is collected by default** — undefined Fix: By default, billing_address_collection is 'auto', meaning Stripe only asks for the address when the payment method requires it. Set it to 'required' to always collect it.
- **Adding more than 3 custom fields** — undefined Fix: Stripe Checkout supports a maximum of 3 custom fields per session. Keep additional data collection on your own pages before or after checkout.
- **Not specifying allowed_countries for shipping** — undefined Fix: shipping_address_collection requires an allowed_countries array. List only the countries you actually ship to.
- **Looking for custom field values in the wrong place** — undefined Fix: Custom field values are in session.custom_fields, not session.metadata. Each entry has a key and a text (or dropdown) object with the value.

## Best practices

- Set billing_address_collection: 'required' when you need the address for invoicing or tax calculation
- Use custom_fields for essential business data (PO number, company name) — max 3 fields
- Restrict shipping_address_collection to only the countries you serve
- Retrieve billing details via webhook (checkout.session.completed) for reliable order processing
- Use the phone_number_collection: { enabled: true } option if you need the customer's phone number
- Test the full flow with card 4242 4242 4242 4242 and verify collected details in the Dashboard

## Frequently asked questions

### Can I pre-fill the billing address?

Yes. If you create or pass a Stripe Customer with a known address, Checkout can pre-fill those fields. Pass the customer ID when creating the session.

### Is phone number collection available?

Yes. Add phone_number_collection: { enabled: true } to the session creation params. The phone number is stored on session.customer_details.phone.

### Can I make custom fields required?

Yes. Omit the optional: true property (or set optional: false). The customer must fill in the field before they can pay.

### Do billing details work with subscription mode?

Yes. billing_address_collection, shipping_address_collection, and custom_fields all work with mode: 'subscription' the same way as mode: 'payment'.

### What if I need to collect more complex data during checkout?

Stripe Checkout supports up to 3 custom fields. For more complex forms — multi-step checkout, conditional fields, or file uploads — RapidDev can help you build a custom checkout flow using Stripe Elements with your own form design.

---

Source: https://www.rapidevelopers.com/stripe-guide/how-to-collect-billing-details-in-stripe-checkout
© RapidDev — https://www.rapidevelopers.com/stripe-guide/how-to-collect-billing-details-in-stripe-checkout
