safely run database <--> stripe operations.
npm install -g TheProfs/bp-syncThis is a script runner that runs mapping files, which contain some custom logic to sync data between your database and Stripe.
It abstracts away authentication, pagination, error-handling, and logging so you don't have to worry about that.
The first time you run it, you'll be prompted for:
- Stripe API Key
- Database URL
These are stored in your macOS keychain.
Then, to perform a mapping:
- Run
bp-sync init, which createsmapping.js - Replace the loop in
mapping.jswith your own custom logic. - or use an LLM to generate it, see LLM Prompt.
- Run it using
bp-sync exec mapping.js
bp-sync initwhich creates:
// mapping.js
// this is a sample, edit me!
for await (const user of users()) {
await user.set({
stripe_subscription_id: user.subscription?.id || null
}).save()
}// mapping.js
for await (const user of users()) {
await user.set({
stripe_subsription_status: user.subscription?.status || null
}).save()
}bp-sync exec mapping.jsthe end.
Usage:
bp-sync Show this help
bp-sync -h, --help Show this help
bp-sync init Create mapping.js
bp-sync exec <file> Execute script
Examples:
bp-sync init
bp-sync exec mapping.jsBulk update:
Activate all inactive users and update timestamps
for await (const user of users('active = false')) {
await user.set({ active: true, updated_at: new Date() }).save()
}Backfill from Stripe:
Copy customer email from Stripe to NULL database fields
for await (const user of users('field1 IS NULL', [], { fetchCustomer: true })) {
if (!user.customer) continue
await user.set({ field1: user.customer.email }).save()
}Process subscriptions:
Set DB plan_id from all Stripe active subscriptions
for await (const sub of subscriptions({ status: 'active' })) {
await query(
'UPDATE users SET plan_id = $1 WHERE stripe_id = $2',
[sub.items.data[0].price.id, sub.customer]
)
}Iterate customers:
Find and log customers by email
for await (const customer of customers({ email: 'test@example.com' })) {
log.info('Customer: $1', [customer.id])
}Cancel subscriptions:
Cancel all active subscriptions for specific plan
for await (const sub of subscriptions({
status: 'active',
price: 'price_premium_monthly'
})) {
await stripe.subscriptions.cancel(sub.id)
log.success('Cancelled subscription $1', [sub.id])
}Sync subscription status:
Update database with current Stripe subscription status
for await (const user of users('stripe_id IS NOT NULL')) {
const subs = await stripe.subscriptions.list({ customer: user.stripe_id })
const active = subs.data.find(s => s.status === 'active')
await user.set({
subscription_status: active?.status || 'none',
plan_id: active?.items.data[0]?.price.id || null
}).save()
}Instead of writing mapping.js scripts manually, you can use an LLM to generate them from natural language.
Copy the following prompt and run it:
Code Generator Prompt
# Generate bp-sync mapping.js
You are a code generator for bp-sync mapping scripts.
Generate valid JavaScript code for `mapping.js` based on the
user's query.
## User Query
The user wants to: **[YOUR QUERY HERE]**
## API Reference
<API>
**Database:**
- `users(where, params, options)` - Generator yielding User instances
- `where`: SQL WHERE clause (without WHERE keyword)
- `params`: Array of parameterized values for $1, $2, etc.
- `options.fetchCustomer`: Boolean, auto-fetches Stripe customer if true
- Returns User with `.set(data)` and `.save()` methods
- `query(sql, params)` - Execute raw SQL (INSERT/UPDATE/DELETE)
- Returns query result directly
**Stripe:**
- `customers(filters)` - Generator for Stripe customers
- `subscriptions(filters)` - Generator for Stripe subscriptions
- `invoices(filters)` - Generator for Stripe invoices
- `charges(filters)` - Generator for Stripe charges
- All resources auto-discovered, accept Stripe API filter objects
**Logging:**
- `log.info(msg, args)` - General messages
- `log.warning(msg, args)` - Non-fatal issues
- `log.error(msg, args)` - Errors
- `log.success(msg, args)` - Success messages
- Placeholders: $1, $2, $3 replaced with args array values
</API>
## Code Patterns
<PATTERNS>
**User updates with Stripe data:**
```js
for await (const user of users('field IS NULL', [], { fetchCustomer: true })) {
if (!user.customer) continue
await user.set({ field: user.customer.email }).save()
}Bulk user updates:
for await (const user of users('active = false')) {
await user.set({ active: true, updated_at: new Date() }).save()
}Stripe to DB sync:
for await (const sub of subscriptions({ status: 'active' })) {
await query(
'UPDATE users SET plan_id = $1 WHERE stripe_id = $2',
[sub.items.data[0].price.id, sub.customer]
)
}Iterate and log:
for await (const customer of customers({ email: 'test@example.com' })) {
log.info('Customer: $1', [customer.id])
}Generate the mapping.js code now:
**Usage with codex:**
```bash
cat prompt.md | codex exec
Stripe:
customers(filters),subscriptions(filters),invoices(filters), etc.stripe- raw client
Database:
users(where, params, options)- generator with.set()and.save()query(sql, params)- direct queriesdb- raw pg client
Logging:
log.error(),log.success(),log.warning(),log.info()
All output to stderr. Placeholders: $1, $2.
npm ci
npm testauthor: TheProfs