/stripe-guides

How to store metadata on Stripe charges?

Learn how to store, update, and use metadata on Stripe charges to enhance payment tracking, reporting, and integration with your systems. Step-by-step guide.

Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Book a free consultation

How to store metadata on Stripe charges?

How to Store Metadata on Stripe Charges: A Comprehensive Tutorial

 

Storing metadata on Stripe charges can significantly enhance your payment tracking capabilities by attaching custom information to transactions. This guide will walk you through the process in detail.

 

Step 1: Set Up Your Stripe Account and API Keys

 

Before you can store metadata on charges, you need to set up your Stripe account and obtain your API keys:

  1. Sign up or log in to your Stripe account at https://dashboard.stripe.com
  2. Navigate to Developers → API keys
  3. Note your publishable key and secret key (keep your secret key secure)

// Example of initializing Stripe with your secret key
const stripe = require('stripe')('sk_test_your_secret_key');

 

Step 2: Understand Metadata in Stripe

 

Metadata in Stripe is a key-value store that allows you to attach custom data to various Stripe objects, including charges. Important characteristics:

  • You can store up to 50 keys
  • Keys can be up to 40 characters long
  • Values can be up to 500 characters long
  • Metadata is visible in the Stripe Dashboard
  • Metadata is included in API responses
  • Metadata can be used for searching and filtering

 

Step 3: Adding Metadata When Creating a Charge

 

Here's how to include metadata when creating a new charge:


const stripe = require('stripe')('sk_test_your_secret_key');

async function createChargeWithMetadata() {
  try {
    const charge = await stripe.charges.create({
      amount: 2000, // Amount in cents
      currency: 'usd',
      source: 'tok\_visa', // Or a customer ID or payment method ID
      description: 'My first charge with metadata',
      metadata: {
        order\_id: '6735',
        customer\_name: 'John Doe',
        product_id: 'prod_123456',
        shipment\_date: '2023-05-15',
        custom\_field: 'Any additional information'
      }
    });
    
    console.log('Charge created with ID:', charge.id);
    console.log('Metadata:', charge.metadata);
    return charge;
  } catch (error) {
    console.error('Error creating charge:', error);
    throw error;
  }
}

createChargeWithMetadata();

 

Step 4: Adding Metadata When Creating a PaymentIntent

 

For modern Stripe integrations using Payment Intents (recommended):


const stripe = require('stripe')('sk_test_your_secret_key');

async function createPaymentIntentWithMetadata() {
  try {
    const paymentIntent = await stripe.paymentIntents.create({
      amount: 2000,
      currency: 'usd',
      payment_method_types: ['card'],
      description: 'Payment intent with metadata',
      metadata: {
        order\_id: '6735',
        customer\_name: 'John Doe',
        product_id: 'prod_123456',
        shipping\_method: 'express',
        promotion\_code: 'SUMMER20'
      }
    });
    
    console.log('PaymentIntent created with ID:', paymentIntent.id);
    console.log('Metadata:', paymentIntent.metadata);
    return paymentIntent;
  } catch (error) {
    console.error('Error creating payment intent:', error);
    throw error;
  }
}

createPaymentIntentWithMetadata();

 

Step 5: Updating Metadata on an Existing Charge

 

If you need to update metadata after a charge has been created:


const stripe = require('stripe')('sk_test_your_secret_key');

async function updateChargeMetadata(chargeId) {
  try {
    const charge = await stripe.charges.update(
      chargeId,
      {
        metadata: {
          status: 'shipped',
          tracking\_number: '1Z999AA10123456784',
          updated\_at: new Date().toISOString()
        }
      }
    );
    
    console.log('Charge updated with ID:', charge.id);
    console.log('Updated metadata:', charge.metadata);
    return charge;
  } catch (error) {
    console.error('Error updating charge:', error);
    throw error;
  }
}

updateChargeMetadata('ch\_123456789');

 

Step 6: Using Metadata with Checkout Sessions

 

If you're using Stripe Checkout, you can add metadata to the session:


const stripe = require('stripe')('sk_test_your_secret_key');

async function createCheckoutSessionWithMetadata() {
  try {
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ['card'],
      line\_items: [
        {
          price\_data: {
            currency: 'usd',
            product\_data: {
              name: 'T-shirt',
            },
            unit\_amount: 2000,
          },
          quantity: 1,
        },
      ],
      mode: 'payment',
      success\_url: 'https://example.com/success',
      cancel\_url: 'https://example.com/cancel',
      metadata: {
        order\_id: '6735',
        customer_id: 'cust_123456',
        source: 'summer\_campaign',
        referral\_code: 'REF789'
      }
    });
    
    console.log('Checkout Session created with ID:', session.id);
    console.log('Metadata:', session.metadata);
    return session;
  } catch (error) {
    console.error('Error creating checkout session:', error);
    throw error;
  }
}

createCheckoutSessionWithMetadata();

 

Step 7: Retrieving and Accessing Metadata

 

To retrieve a charge and access its metadata:


const stripe = require('stripe')('sk_test_your_secret_key');

async function getChargeMetadata(chargeId) {
  try {
    const charge = await stripe.charges.retrieve(chargeId);
    
    console.log('Charge ID:', charge.id);
    console.log('All metadata:', charge.metadata);
    
    // Access specific metadata fields
    const orderId = charge.metadata.order\_id;
    const customerName = charge.metadata.customer\_name;
    
    console.log('Order ID:', orderId);
    console.log('Customer Name:', customerName);
    
    return charge.metadata;
  } catch (error) {
    console.error('Error retrieving charge:', error);
    throw error;
  }
}

getChargeMetadata('ch\_123456789');

 

Step 8: Searching Charges Using Metadata

 

You can search for charges based on their metadata:


const stripe = require('stripe')('sk_test_your_secret_key');

async function searchChargesByMetadata() {
  try {
    const charges = await stripe.charges.list({
      limit: 10,
      // Search for charges with a specific order\_id in metadata
      search: {
        query: 'metadata["order\_id"]:"6735"'
      }
    });
    
    console.log('Found charges:', charges.data.length);
    
    charges.data.forEach(charge => {
      console.log('Charge ID:', charge.id);
      console.log('Amount:', charge.amount / 100); // Convert cents to dollars
      console.log('Metadata:', charge.metadata);
      console.log('------------------------');
    });
    
    return charges.data;
  } catch (error) {
    console.error('Error searching charges:', error);
    throw error;
  }
}

searchChargesByMetadata();

 

Step 9: Handling Metadata in Webhooks

 

When receiving webhook events from Stripe, you can access the metadata to process the event:


const express = require('express');
const stripe = require('stripe')('sk_test_your_secret_key');
const app = express();

// Use JSON parser for all non-webhook routes
app.use((req, res, next) => {
  if (req.path === '/webhook') {
    next();
  } else {
    express.json()(req, res, next);
  }
});

// Special handling for webhook endpoint
app.post('/webhook', express.raw({type: 'application/json'}), async (req, res) => {
  const sig = req.headers['stripe-signature'];
  let event;

  try {
    // Construct the event from the raw request and signature
    event = stripe.webhooks.constructEvent(
      req.body,
      sig,
      'whsec_your_webhook_signing_secret'
    );
  } catch (err) {
    console.log(`Webhook Error: ${err.message}`);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Handle the event
  if (event.type === 'charge.succeeded') {
    const charge = event.data.object;
    const metadata = charge.metadata;
    
    console.log('Charge succeeded with ID:', charge.id);
    console.log('Charge metadata:', metadata);
    
    // Use metadata to process your business logic
    if (metadata.order\_id) {
      // Update order status in your database
      console.log(`Updating order ${metadata.order_id} to paid status`);
      // await updateOrderStatus(metadata.order\_id, 'paid');
    }
    
    if (metadata.customer\_id) {
      // Update customer payment records
      console.log(`Recording payment for customer ${metadata.customer_id}`);
      // await recordCustomerPayment(metadata.customer\_id, charge.amount);
    }
  }

  // Return a 200 response to acknowledge receipt of the event
  res.send({received: true});
});

app.listen(3000, () => console.log('Running on port 3000'));

 

Step 10: Best Practices for Using Metadata

 

To make the most of Stripe metadata:

  • Use consistent naming conventions for your metadata keys
  • Store only non-sensitive data in metadata (no PII like full credit card numbers)
  • Keep metadata values concise to stay within the 500-character limit
  • Consider storing complex data structures as JSON strings
  • Document your metadata schema for your team
  • Use metadata to link Stripe objects to your internal systems

Example of storing a more complex data structure:


const orderDetails = {
  items: [
    { id: 'SKU123', quantity: 2, price: 10.99 },
    { id: 'SKU456', quantity: 1, price: 24.99 }
  ],
  shipping\_address: {
    city: 'San Francisco',
    state: 'CA',
    country: 'US'
  },
  discount\_applied: true
};

// Store as JSON string in metadata
const charge = await stripe.charges.create({
  amount: 4697, // 46.97 in cents
  currency: 'usd',
  source: 'tok\_visa',
  metadata: {
    order\_id: '6735',
    order\_details: JSON.stringify(orderDetails)
  }
});

// Later, parse it back to an object
const retrievedCharge = await stripe.charges.retrieve('ch\_123456789');
const parsedOrderDetails = JSON.parse(retrievedCharge.metadata.order\_details);
console.log('Items ordered:', parsedOrderDetails.items.length);

 

Step 11: Implementing Metadata in Different Programming Languages

 

Here are examples of implementing metadata in different programming languages:

Python:


import stripe
stripe.api_key = 'sk_test_your_secret\_key'

# Create a charge with metadata
charge = stripe.Charge.create(
  amount=2000,
  currency='usd',
  source='tok\_visa',
  description='Charge with metadata in Python',
  metadata={
    'order\_id': '6735',
    'customer\_name': 'John Doe',
    'product_id': 'prod_123456'
  }
)

print(f"Charge created with ID: {charge.id}")
print(f"Metadata: {charge.metadata}")

# Update metadata
updated\_charge = stripe.Charge.modify(
  charge.id,
  metadata={
    'status': 'shipped',
    'tracking\_number': '1Z999AA10123456784'
  }
)

print(f"Updated metadata: {updated\_charge.metadata}")

PHP:


 2000,
    'currency' => 'usd',
    'source' => 'tok\_visa',
    'description' => 'Charge with metadata in PHP',
    'metadata' => [
      'order\_id' => '6735',
      'customer\_name' => 'John Doe',
      'product_id' => 'prod_123456'
    ]
  ]);

  echo "Charge created with ID: " . $charge->id . "\n";
  echo "Metadata: ";
  print\_r($charge->metadata);

  // Update metadata
  $updated\_charge = \Stripe\Charge::update(
    $charge->id,
    [
      'metadata' => [
        'status' => 'shipped',
        'tracking\_number' => '1Z999AA10123456784'
      ]
    ]
  );

  echo "Updated metadata: ";
  print_r($updated_charge->metadata);
} catch (\Stripe\Exception\ApiErrorException $e) {
  echo "Error: " . $e->getMessage();
}
?>

Ruby:


require 'stripe'
Stripe.api_key = 'sk_test_your_secret\_key'

begin
  # Create a charge with metadata
  charge = Stripe::Charge.create({
    amount: 2000,
    currency: 'usd',
    source: 'tok\_visa',
    description: 'Charge with metadata in Ruby',
    metadata: {
      order\_id: '6735',
      customer\_name: 'John Doe',
      product_id: 'prod_123456'
    }
  })

  puts "Charge created with ID: #{charge.id}"
  puts "Metadata: #{charge.metadata}"

  # Update metadata
  updated\_charge = Stripe::Charge.update(
    charge.id,
    metadata: {
      status: 'shipped',
      tracking\_number: '1Z999AA10123456784'
    }
  )

  puts "Updated metadata: #{updated\_charge.metadata}"
rescue Stripe::StripeError => e
  puts "Error: #{e.message}"
end

 

Step 12: Integrating Metadata with Your Database

 

To maintain a connection between Stripe charges and your internal database:


const stripe = require('stripe')('sk_test_your_secret_key');
const { Pool } = require('pg'); // Using PostgreSQL for this example

// Create a database connection pool
const pool = new Pool({
  user: 'db\_user',
  host: 'localhost',
  database: 'my\_app',
  password: 'password',
  port: 5432,
});

async function processOrder(orderId, customerId, amount, productIds) {
  const client = await pool.connect();
  
  try {
    // Start a transaction
    await client.query('BEGIN');
    
    // Create the charge in Stripe with metadata
    const charge = await stripe.charges.create({
      amount: amount \* 100, // Convert to cents
      currency: 'usd',
      source: 'tok\_visa', // In real scenario, you would use a saved payment method
      description: `Order #${orderId}`,
      metadata: {
        order\_id: orderId.toString(),
        customer\_id: customerId.toString(),
        products: productIds.join(',')
      }
    });
    
    // Save the charge information in your database
    const saveChargeResult = await client.query(
      'INSERT INTO payments (order_id, customer_id, stripe_charge_id, amount, status) VALUES ($1, $2, $3, $4, $5) RETURNING id',
      [orderId, customerId, charge.id, amount, 'successful']
    );
    
    const paymentId = saveChargeResult.rows[0].id;
    
    // Update order status
    await client.query(
      'UPDATE orders SET status = $1, payment_id = $2, updated_at = NOW() WHERE id = $3',
      ['paid', paymentId, orderId]
    );
    
    // Commit the transaction
    await client.query('COMMIT');
    
    console.log(`Order ${orderId} processed successfully with Stripe charge ${charge.id}`);
    return { success: true, chargeId: charge.id, paymentId };
    
  } catch (error) {
    // If anything fails, roll back the transaction
    await client.query('ROLLBACK');
    console.error('Error processing order:', error);
    return { success: false, error: error.message };
  } finally {
    // Release the client back to the pool
    client.release();
  }
}

// Example usage
processOrder(6735, 12345, 49.99, ['prod_123', 'prod_456'])
  .then(result => console.log(result))
  .catch(error => console.error(error));

 

Step 13: Building a Reporting System Using Metadata

 

Use metadata to build a custom reporting system:


const stripe = require('stripe')('sk_test_your_secret_key');
const express = require('express');
const app = express();

app.use(express.json());

// API endpoint to generate a report based on metadata
app.get('/api/reports/sales-by-product', async (req, res) => {
  try {
    const startDate = req.query.start\_date || '2023-01-01';
    const endDate = req.query.end\_date || new Date().toISOString().split('T')[0];
    
    // Convert dates to timestamps for Stripe API
    const startTimestamp = Math.floor(new Date(startDate).getTime() / 1000);
    const endTimestamp = Math.floor(new Date(endDate).getTime() / 1000);
    
    // Get all charges in the date range
    const charges = await stripe.charges.list({
      limit: 100,
      created: {
        gte: startTimestamp,
        lte: endTimestamp
      }
    });
    
    // Group and count by product\_id in metadata
    const productSales = {};
    let totalSales = 0;
    
    charges.data.forEach(charge => {
      if (charge.status === 'succeeded' && charge.metadata && charge.metadata.product\_id) {
        const productId = charge.metadata.product\_id;
        const amount = charge.amount / 100; // Convert cents to dollars
        
        if (!productSales[productId]) {
          productSales[productId] = {
            count: 0,
            revenue: 0
          };
        }
        
        productSales[productId].count += 1;
        productSales[productId].revenue += amount;
        totalSales += amount;
      }
    });
    
    // Format the results
    const results = {
      start\_date: startDate,
      end\_date: endDate,
      total\_sales: totalSales.toFixed(2),
      products: Object.keys(productSales).map(productId => ({
        product\_id: productId,
        sales\_count: productSales[productId].count,
        revenue: productSales[productId].revenue.toFixed(2)
      }))
    };
    
    res.json(results);
  } catch (error) {
    console.error('Error generating report:', error);
    res.status(500).json({ error: 'Failed to generate report' });
  }
});

app.listen(3000, () => console.log('Reporting server running on port 3000'));

 

Conclusion

 

Stripe's metadata feature provides a powerful way to associate custom data with your charges and payment intents. By following this comprehensive tutorial, you can implement a robust system for tracking, querying, and managing payments with your application-specific data. Remember to use metadata strategically, keeping your keys and values consistent and meaningful to maximize the benefits of this flexible system.

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Book a Free Consultation

Client trust and success are our top priorities

When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.

Rapid Dev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with. They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

CPO, Praction - Arkady Sokolov

May 2, 2023

Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost. He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Co-Founder, Arc - Donald Muir

Dec 27, 2022

Rapid Dev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space. They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Co-CEO, Grantify - Mat Westergreen-Thorne

Oct 15, 2022

Rapid Dev is an excellent developer for no-code and low-code solutions.
We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Co-Founder, Church Real Estate Marketplace - Emmanuel Brown

May 1, 2024 

Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 
This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Production Manager, Media Production Company - Samantha Fekete

Sep 23, 2022