Skip to content

[Old version updated] Sync Stripe with Database Version 2 #1063

@adryserage

Description

@adryserage

This source code demonstrates how to synchronize data from Stripe, a payment processing platform, with a local database using Prisma, an ORM (Object Relational Mapping) tool. The script performs several key operations, including fetching data from Stripe, mapping Stripe customers to local users, updating user information with Stripe customer IDs, and seeding the local database with Stripe products, prices, and subscriptions. Below is a detailed breakdown of the code, including its structure, functions, and purpose.

Overview

  • Prisma Setup: Initializes the Prisma client to interact with the database.
  • Stripe Setup: Creates a Stripe instance configured with an API version and secret key.
  • Synchronization Process: The sync asynchronous function orchestrates the entire synchronization process.
  • Error Handling: Catches and logs errors that occur during the synchronization process.
  • Data Fetching and Processing: Functions to fetch data from Stripe and process it for database insertion.
  • Database Operations: Functions to clean up, seed, and log statistics about the database after synchronization.

Detailed Documentation

Prisma Client Initialization

import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
  • Imports the PrismaClient from the @prisma/client package.
  • Initializes a new Prisma client instance for database operations.

Stripe Instance Creation

import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: '2023-10-16' });
  • Imports the Stripe module.
  • Creates a Stripe instance using the secret key stored in the environment variable STRIPE_SECRET_KEY and sets the API version to '2023-10-16'.

Synchronization Function (sync)

const sync = async () => {
  // Error handling and synchronization logic.
};
  • An asynchronous function that orchestrates the synchronization of data from Stripe to the local database.
  • Handles errors that might occur during the process and logs the success or failure of the operation.

Helper Functions

  • getStripeInstance: Ensures that the Stripe secret key is set and returns the Stripe instance.
  • fetchStripeData: Fetches active products, prices, subscriptions, and customers from Stripe.
  • mapUsersToCustomers: Maps local user records to Stripe customers based on email.
  • updateUsersWithStripeCustomerId: Updates local users with their corresponding Stripe customer IDs.
  • performDatabaseOperations: Orchestrates database operations including cleaning up old data and seeding with new data from Stripe.
  • cleanup, seedServices, seedPrices, seedSubscriptions: Helper functions to perform specific database seeding operations.
  • printStats: Logs the count of products, prices, and subscriptions synced to the database.

Error Handling

process.on('uncaughtException', (error) => {
  console.error('Uncaught Exception:', error);
  process.exit(1);
});
  • Listens for uncaught exceptions in the Node.js process.
  • Logs the error and exits the process with a status code of 1 to indicate failure.

Complete New Source Code

import { PrismaClient } from '@prisma/client';
import Stripe from 'stripe';

const prisma = new PrismaClient();

const sync = async () => {
  try {
    console.log('Starting sync with Stripe');
    const stripe = getStripeInstance();
    const { products, prices, subscriptions, customers } = await fetchStripeData(stripe);

    let users = await prisma.user.findMany();
    const userMap = mapUsersToCustomers(users, customers.data);

    users = await updateUsersWithStripeCustomerId(users, userMap, prisma);
    await performDatabaseOperations(prices, products, subscriptions, userMap, prisma);

    console.log('Sync completed successfully');
  } catch (error) {
    console.error('Error syncing with Stripe:', error);
    process.exit(1);
  }
};
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: '2023-10-16' });
const getStripeInstance = () => {
  if (!process.env.STRIPE_SECRET_KEY) {
    throw new Error('STRIPE_SECRET_KEY environment variable not set');
  }
  return stripe
};

const fetchStripeData = async (stripe) => {
  const [products, prices, subscriptions, customers] = await Promise.all([
    stripe.products.list({ active: true }),
    stripe.prices.list({ active: true }),
    stripe.subscriptions.list({ status: 'active' }),
    stripe.customers.list(),
  ]);

  return { products, prices, subscriptions, customers };
};

const mapUsersToCustomers = (users, customers) => {
  return users.reduce((map, user) => {
    const customer = customers.find(c => c.email === user.email);
    if (customer) map[customer.id] = user;
    return map;
  }, {});
};

const updateUsersWithStripeCustomerId = async (users, userMap, prisma) => {
  return Promise.all(users.map(user => {
    const customer = userMap[user.email];
    if (customer) {
      return prisma.user.update({
        where: { id: user.id },
        data: { stripeCustomerId: customer.id },
      });
    }
    return user;
  }));
};

const performDatabaseOperations = async (prices, products, subscriptions, userMap, prisma) => {
  await prisma.$transaction(cleanup(prisma));
  const services = await prisma.$transaction(seedServices(products.data, prisma));
  await prisma.$transaction(seedPrices(prices.data, prisma, services));
  await prisma.$transaction(seedSubscriptions(subscriptions.data, prisma, userMap));
  await printStats(prisma);
};

const cleanup = (prisma) => {
  return [
    prisma.price.deleteMany({}),
    prisma.service.deleteMany({}),
    prisma.subscription.deleteMany({}),
  ];
};

const seedServices = (products, prisma) => {
  return products.map(data =>
    prisma.service.create({
      data: {
        id: data.id,
        description: data.description || '',
        features: (data.features || []).map(a => a.name),
        image: data.images.length > 0 ? data.images[0] : '',
        name: data.name,
        created: new Date(data.created * 1000),
      },
    })
  );
};

const seedPrices = (prices, prisma, services) => {
  return prices.map(data => {
    const service = services.find(service => service.id === data.product);
    if (service) {
      return prisma.price.create({
        data: {
          id: data.id,
          billingScheme: data.billing_scheme,
          currency: data.currency,
          serviceId: service.id,
          amount: data.unit_amount ? data.unit_amount / 100 : undefined,
          metadata: data.recurring,
          type: data.type,
          created: new Date(data.created * 1000),
        },
      });
    }
  }).filter(Boolean);
};

const seedSubscriptions = (subscriptions, prisma, userMap) => {
  return subscriptions.map(data => {
    const user = userMap[data.customer];
    if (user) {
      return prisma.subscription.create({
        data: {
          id: data.id,
          customerId: data.customer,
          priceId: data.items.data[0].price.id,
          active: data.status === 'active',
          startDate: new Date(data.start_date * 1000),
          endDate: new Date(data.current_period_end * 1000),
          cancelAt: data.cancel_at ? new Date(data.cancel_at * 1000) : null,
          userId: user.id,
        },
      });
    }
  }).filter(Boolean);
};

const printStats = async (prisma) => {
  const [productCount, priceCount, subscriptionCount] = await Promise.all([
    prisma.service.count(),
    prisma.price.count(),
    prisma.subscription.count(),
  ]);

  console.log('Products synced:', productCount);
  console.log('Prices synced:', priceCount);
  console.log('Subscriptions synced:', subscriptionCount);
};

process.on('uncaughtException', (error) => {
  console.error('Uncaught Exception:', error);
  process.exit(1);
});

export default sync;

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions