// Name: Stripe Payment Links // Description: Fetch payment links from Stripe and retrieve customer emails from successful payments // Author: johnlindquist import "@johnlindquist/kit" import Stripe from "stripe" // Get Stripe API key from environment const STRIPE_API_KEY = await env("STRIPE_API_KEY", { secret: true, hint: "Enter your Stripe secret key (sk_live_...)" }) // Initialize Stripe SDK const stripe = new Stripe(STRIPE_API_KEY) // Fetch all payment links from Stripe const fetchPaymentLinks = async (): Promise<Stripe.PaymentLink[]> => { const links: Stripe.PaymentLink[] = [] for await (const link of stripe.paymentLinks.list()) { links.push(link) } return links } // Get display name for a payment link const getPaymentLinkName = async (link: Stripe.PaymentLink): Promise<string> => { try { const lineItems = await stripe.paymentLinks.listLineItems(link.id, { limit: 1 }) if (lineItems.data.length > 0) { const item = lineItems.data[0] if (item.description) { return item.description } if (item.price?.product) { return `Product ${item.price.product}` } } } catch (error) { console.warn(`Could not fetch line items for ${link.id}:`, error.message) } return link.url || link.id } interface Sale { email: string price: number } // Fetch customer sales for a payment link const fetchSales = async (linkId: string): Promise<Sale[]> => { const sales: Sale[] = [] let hasMore = true let startingAfter: string | undefined while (hasMore) { try { const params: Stripe.Checkout.SessionListParams = { payment_link: linkId, status: 'complete', limit: 100, ...(startingAfter ? { starting_after: startingAfter } : {}) } const response = await stripe.checkout.sessions.list(params) for (const session of response.data) { const email = session.customer_details?.email || (typeof session.customer_email === 'string' ? session.customer_email : undefined) const price = session.amount_total || 0 if (email) { sales.push({ email, price }) } } hasMore = response.has_more if (hasMore && response.data.length > 0) { startingAfter = response.data[response.data.length - 1].id } } catch (error) { throw new Error(`Failed to fetch checkout sessions: ${error.message}`) } } return Array.from(sales) } try { // Show loading dot setLoading(true) // Fetch all payment links const paymentLinks = await fetchPaymentLinks() if (paymentLinks.length === 0) { await div(md("# No Payment Links Found\n\nNo payment links were found in your Stripe account.")) exit() } // Prepare choices with friendly names const choices = await Promise.all( paymentLinks.map(async (link) => { const name = await getPaymentLinkName(link) return { name: `${name} (${link.id})`, description: link.url, value: link } }) ) // Let user select a payment link setLoading(true) const selectedLink = await arg("Select a Payment Link:", choices) // Fetch emails for the selected payment link const sales = await fetchSales(selectedLink.id) if (sales.length === 0) { await div(md(`# No Successful Payments Found\n\nNo successful payments were found for the selected payment link:\n\n**${selectedLink.url}**`)) } else { // Display results const formattedSales = sales.map(email => `- ${email.email} (${email.price})`).join('\n') // Sum up the total cost in cents, then format as dollars and cents (e.g., $12.34) const totalCents = sales.reduce((acc, email) => acc + email.price, 0) const revenue = `$${(totalCents / 100).toFixed(2)}` const emails = sales.map(sale => sale.email) const result = `# Customer Emails\n\n**Payment Link:** ${selectedLink.url}\n\n**Total Customers:** ${sales.length}\n\n**Total Cost:** ${revenue}\n\n## Email Addresses:\n\n${formattedSales}` await editor(result, [ { name: "Copy to Clipboard", shortcut: "cmd+c", onAction: async () => { await copy(emails.join(', ')) await toast("Emails copied to clipboard!") } }, { name: "Copy as List", shortcut: "cmd+l", onAction: async () => { await copy(emails.join('\n')) await toast("Emails copied as list!") } }, { name: "Save to File", shortcut: "cmd+s", onAction: async () => { const timestamp = formatDate(new Date(), 'yyyy-MM-dd-HHmm') const filename = `stripe-emails-${timestamp}.txt` const filePath = home("Downloads", filename) await writeFile(filePath, emails.join('\n')) await toast(`Saved to ${filename}`) await revealFile(filePath) } } ]) } } catch (error) { await div(md(`# Error\n\n${error.message}\n\nPlease check your Stripe API key and try again.`)) }