Created
April 1, 2025 15:20
-
-
Save jgdovin/be19a5bc1f95ce1632a21bbe826c81e8 to your computer and use it in GitHub Desktop.
duplicate search
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// TZ='UTC' npx tsx ./src/scratch.ts | |
import Stripe from 'stripe'; | |
import { subDays, differenceInDays } from 'date-fns'; | |
import groupBy from 'lodash/groupBy'; | |
import * as dotenv from 'dotenv'; | |
dotenv.config(); | |
dotenv.config({ path: '.env.local' }); | |
if (!process.env.STRIPE_SECRET_KEY) { | |
console.error('STRIPE_SECRET_KEY env var needs to be defined'); | |
process.exit(1); | |
} | |
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, { apiVersion: '2025-02-24.acacia' }); | |
interface DuplicateCharge { | |
customerId: string; | |
customerEmail: string | null; | |
amount: number; | |
charges: Array<{ | |
id: string; | |
created: number; | |
description: string | null; | |
}>; | |
daysBetween: number; | |
} | |
async function findPotentialDuplicateCharges(daysToLookBack: number = 30, maxDaysBetween: number = 2) { | |
const endDate = new Date(); | |
const startDate = subDays(endDate, daysToLookBack); | |
console.log(`Searching for potential duplicates between ${startDate.toISOString()} and ${endDate.toISOString()}`); | |
// Get all charges in the date range | |
const charges = await stripe.charges.list({ | |
created: { | |
gte: Math.floor(startDate.getTime() / 1000), | |
lte: Math.floor(endDate.getTime() / 1000) | |
}, | |
limit: 100, | |
expand: ['data.customer'] | |
}); | |
console.log(charges) | |
// Group charges by customer | |
const chargesByCustomer = groupBy(charges.data, 'customer.id'); | |
const potentialDuplicates: DuplicateCharge[] = []; | |
// For each customer, look for charges with the same amount within maxDaysBetween days | |
for (const [customerId, customerCharges] of Object.entries(chargesByCustomer)) { | |
// Skip if customer has only one charge | |
if (customerCharges.length <= 1) continue; | |
// Group charges by amount | |
const chargesByAmount = groupBy(customerCharges, 'amount'); | |
for (const [, chargesWithSameAmount] of Object.entries(chargesByAmount)) { | |
// Skip if there's only one charge with this amount | |
if (chargesWithSameAmount.length <= 1) continue; | |
// Sort charges by creation date | |
const sortedCharges = chargesWithSameAmount.sort((a, b) => a.created - b.created); | |
// Check each pair of charges | |
for (let i = 0; i < sortedCharges.length - 1; i++) { | |
const charge1 = sortedCharges[i]; | |
const charge2 = sortedCharges[i + 1]; | |
const date1 = new Date(charge1.created * 1000); | |
const date2 = new Date(charge2.created * 1000); | |
const daysBetween = differenceInDays(date2, date1); | |
if (daysBetween <= maxDaysBetween) { | |
const customer = charge1.customer as Stripe.Customer; | |
potentialDuplicates.push({ | |
customerId: customer.id, | |
customerEmail: customer.email, | |
amount: charge1.amount / 100, // Convert from cents to dollars | |
charges: [ | |
{ | |
id: charge1.id, | |
created: charge1.created, | |
description: charge1.description | |
}, | |
{ | |
id: charge2.id, | |
created: charge2.created, | |
description: charge2.description | |
} | |
], | |
daysBetween | |
}); | |
} | |
} | |
} | |
} | |
return potentialDuplicates; | |
} | |
// Run the script | |
async function main() { | |
try { | |
const duplicates = await findPotentialDuplicateCharges(); | |
if (duplicates.length === 0) { | |
console.log('No potential duplicates found.'); | |
return; | |
} | |
console.log(`Found ${duplicates.length} potential duplicate charges:\n`); | |
duplicates.forEach((dup, index) => { | |
console.log(`${index + 1}. Customer: ${dup.customerEmail || dup.customerId}`); | |
console.log(` Amount: $${dup.amount}`); | |
console.log(` Days between charges: ${dup.daysBetween}`); | |
console.log(' Charges:'); | |
dup.charges.forEach(charge => { | |
console.log(` - ID: ${charge.id}`); | |
console.log(` Date: ${new Date(charge.created * 1000).toISOString()}`); | |
if (charge.description) { | |
console.log(` Description: ${charge.description}`); | |
} | |
}); | |
console.log(''); | |
}); | |
} catch (error) { | |
console.error('Error:', error); | |
} | |
} | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment