Rocky_Mountain_Vending/lib/stripe/products.ts

323 lines
8.9 KiB
TypeScript

import type { Product, StripePrice } from "@/lib/products/types"
import Stripe from "stripe"
import { getStripeClient } from "./client"
import { filterProducts } from "@/lib/products/filters"
/**
* Fetch all active products from Stripe
*/
export async function fetchAllProducts(): Promise<Product[]> {
const stripe = getStripeClient()
try {
// Fetch all products with their prices
const products = await stripe.products.list({
active: true,
expand: ["data.default_price"],
limit: 100,
})
// Map Stripe products to our Product format
const mappedProducts: Product[] = []
for (const product of products.data) {
// Get the default price
let price: StripePrice | null = null
let priceId: string | undefined
if (product.default_price) {
if (typeof product.default_price === "string") {
priceId = product.default_price
const priceData = await stripe.prices.retrieve(priceId)
price = {
id: priceData.id,
unit_amount: priceData.unit_amount,
currency: priceData.currency,
type: priceData.type,
active: priceData.active,
}
} else {
price = {
id: product.default_price.id,
unit_amount: product.default_price.unit_amount,
currency: product.default_price.currency,
type: product.default_price.type,
active: product.default_price.active,
}
priceId = price.id
}
}
// Only include products with valid prices
if (price && price.unit_amount !== null && price.active) {
mappedProducts.push({
id: product.id,
stripeId: product.id,
name: product.name,
description: product.description,
price: price.unit_amount / 100, // Convert from cents to dollars
currency: price.currency,
images: product.images || [],
metadata: product.metadata,
priceId: priceId,
})
}
}
// Apply filters to exclude service charges
return filterProducts(mappedProducts)
} catch (error) {
console.error("Error fetching products from Stripe:", error)
// Return empty array instead of throwing to prevent page crashes
if (error instanceof Error && error.message.includes("STRIPE_SECRET_KEY")) {
throw error // Re-throw configuration errors
}
return [] // Return empty array for other errors
}
}
/**
* Fetch a single product by Stripe ID
*/
export async function fetchProductById(id: string): Promise<Product | null> {
const stripe = getStripeClient()
try {
const product = await stripe.products.retrieve(id, {
expand: ["default_price"],
})
if (!product.active) {
return null
}
// Get the default price
let price: StripePrice | null = null
let priceId: string | undefined
if (product.default_price) {
if (typeof product.default_price === "string") {
priceId = product.default_price
const priceData = await stripe.prices.retrieve(priceId)
price = {
id: priceData.id,
unit_amount: priceData.unit_amount,
currency: priceData.currency,
type: priceData.type,
active: priceData.active,
}
} else {
price = {
id: product.default_price.id,
unit_amount: product.default_price.unit_amount,
currency: product.default_price.currency,
type: product.default_price.type,
active: product.default_price.active,
}
priceId = price.id
}
}
if (!price || price.unit_amount === null || !price.active) {
return null
}
const mappedProduct: Product = {
id: product.id,
stripeId: product.id,
name: product.name,
description: product.description,
price: (price.unit_amount ?? 0) / 100,
currency: price.currency,
images: product.images || [],
metadata: product.metadata,
priceId: priceId,
}
// Check if product should be excluded
const { shouldExcludeProduct } = await import("@/lib/products/config")
if (shouldExcludeProduct(mappedProduct.name)) {
return null
}
return mappedProduct
} catch (error) {
console.error(`Error fetching product ${id} from Stripe:`, error)
return null
}
}
/**
* Create a new product in Stripe
*/
export async function createProductInStripe(productData: {
name: string
description?: string
price: number
currency?: string
images?: string[]
metadata?: Record<string, string>
}): Promise<{ product: Product; priceId: string } | null> {
const stripe = getStripeClient()
try {
const currency = productData.currency || "usd"
// Create the product first
const stripeProduct = await stripe.products.create({
name: productData.name,
description: productData.description || "",
images: productData.images || [],
metadata: productData.metadata || {},
active: true,
})
// Create the price
const price = await stripe.prices.create({
product: stripeProduct.id,
unit_amount: Math.round(productData.price * 100), // Convert to cents
currency,
metadata: {
...productData.metadata,
created_by: "admin",
},
})
// Update product with default price
await stripe.products.update(stripeProduct.id, {
default_price: price.id,
})
const mappedProduct: Product = {
id: stripeProduct.id,
stripeId: stripeProduct.id,
name: stripeProduct.name,
description: stripeProduct.description,
price: (price.unit_amount ?? 0) / 100,
currency: price.currency,
images: stripeProduct.images || [],
metadata: stripeProduct.metadata,
priceId: price.id,
}
return { product: mappedProduct, priceId: price.id }
} catch (error) {
console.error("Error creating product in Stripe:", error)
return null
}
}
/**
* Update an existing product in Stripe
*/
export async function updateProductInStripe(
productId: string,
updates: {
name?: string
description?: string
price?: number
images?: string[]
metadata?: Record<string, string>
}
): Promise<Product | null> {
const stripe = getStripeClient()
try {
// Update product fields
const updateData: Stripe.ProductUpdateParams = {}
if (updates.name !== undefined) updateData.name = updates.name
if (updates.description !== undefined)
updateData.description = updates.description
if (updates.images !== undefined) updateData.images = updates.images
if (updates.metadata !== undefined) updateData.metadata = updates.metadata
const updatedProduct = await stripe.products.update(productId, updateData)
// Update price if provided
let priceId =
typeof updatedProduct.default_price === "string"
? updatedProduct.default_price
: updatedProduct.default_price?.id
if (updates.price !== undefined && priceId) {
const existingPrice = await stripe.prices.retrieve(priceId)
const updatedPrice = await stripe.prices.create({
currency: existingPrice.currency,
metadata: {
...existingPrice.metadata,
...updates.metadata,
updated_at: new Date().toISOString(),
},
product: updatedProduct.id,
unit_amount: Math.round(updates.price * 100),
})
await stripe.products.update(productId, {
default_price: updatedPrice.id,
})
priceId = updatedPrice.id
}
const mappedProduct: Product = {
id: updatedProduct.id,
stripeId: updatedProduct.id,
name: updatedProduct.name,
description: updatedProduct.description,
price: updates.price || 0,
currency: "usd", // You may need to fetch the actual currency
images: updatedProduct.images || [],
metadata: updatedProduct.metadata,
priceId: priceId,
}
return mappedProduct
} catch (error) {
console.error(`Error updating product ${productId} in Stripe:`, error)
return null
}
}
/**
* Deactivate a product in Stripe (soft delete)
*/
export async function deactivateProductInStripe(
productId: string
): Promise<boolean> {
const stripe = getStripeClient()
try {
await stripe.products.update(productId, { active: false })
return true
} catch (error) {
console.error(`Error deactivating product ${productId} in Stripe:`, error)
return false
}
}
/**
* Fetch prices for a specific product
*/
export async function fetchProductPrices(
productId: string
): Promise<StripePrice[]> {
const stripe = getStripeClient()
try {
const prices = await stripe.prices.list({
product: productId,
active: true,
})
return prices.data.map((price) => ({
id: price.id,
unit_amount: price.unit_amount,
currency: price.currency,
type: price.type,
active: price.active,
}))
} catch (error) {
console.error(`Error fetching prices for product ${productId}:`, error)
return []
}
}