Files
stats/packages/payments/scripts/assign-product-to-org.ts
2026-01-21 21:56:20 +01:00

222 lines
5.5 KiB
TypeScript

import { db } from '@openpanel/db';
import { Polar } from '@polar-sh/sdk';
import inquirer from 'inquirer';
import inquirerAutocomplete from 'inquirer-autocomplete-prompt';
import { getSuccessUrl } from '..';
// Register the autocomplete prompt
inquirer.registerPrompt('autocomplete', inquirerAutocomplete);
interface Answers {
isProduction: boolean;
polarApiKey: string;
productId: string;
organizationId: string;
}
async function promptForInput() {
// Get all organizations first
const organizations = await db.organization.findMany({
select: {
id: true,
name: true,
},
});
// Step 1: Collect Polar credentials first
const polarCredentials = await inquirer.prompt<{
isProduction: boolean;
polarApiKey: string;
polarOrganizationId: string;
}>([
{
type: 'list',
name: 'isProduction',
message: 'Is this for production?',
choices: [
{ name: 'Yes', value: true },
{ name: 'No', value: false },
],
default: true,
},
{
type: 'string',
name: 'polarApiKey',
message: 'Enter your Polar API key:',
validate: (input: string) => {
if (!input) return 'API key is required';
return true;
},
},
]);
// Step 2: Initialize Polar client and fetch products
const polar = new Polar({
accessToken: polarCredentials.polarApiKey,
server: polarCredentials.isProduction ? 'production' : 'sandbox',
});
console.log('Fetching products from Polar...');
const productsResponse = await polar.products.list({
limit: 100,
isArchived: false,
sorting: ['price_amount'],
});
const products = productsResponse.result.items;
if (products.length === 0) {
throw new Error('No products found in Polar');
}
// Step 3: Continue with product selection and organization selection
const restOfAnswers = await inquirer.prompt<{
productId: string;
organizationId: string;
}>([
{
type: 'autocomplete',
name: 'productId',
message: 'Select product:',
source: (answersSoFar: any, input = '') => {
return products
.filter(
(product) =>
product.name.toLowerCase().includes(input.toLowerCase()) ||
product.id.toLowerCase().includes(input.toLowerCase()),
)
.map((product) => {
const price = product.prices?.[0];
const priceStr =
price && 'priceAmount' in price && price.priceAmount
? `$${(price.priceAmount / 100).toFixed(2)}/${price.recurringInterval || 'month'}`
: 'No price';
return {
name: `${product.name} (${priceStr})`,
value: product.id,
};
});
},
},
{
type: 'autocomplete',
name: 'organizationId',
message: 'Select organization:',
source: (answersSoFar: any, input = '') => {
return organizations
.filter(
(org) =>
org.name.toLowerCase().includes(input.toLowerCase()) ||
org.id.toLowerCase().includes(input.toLowerCase()),
)
.map((org) => ({
name: `${org.name} (${org.id})`,
value: org.id,
}));
},
},
]);
return {
...polarCredentials,
...restOfAnswers,
};
}
async function main() {
console.log('Assigning existing product to organization...');
const input = await promptForInput();
const polar = new Polar({
accessToken: input.polarApiKey,
server: input.isProduction ? 'production' : 'sandbox',
});
const organization = await db.organization.findUniqueOrThrow({
where: {
id: input.organizationId,
},
select: {
id: true,
name: true,
createdBy: {
select: {
id: true,
email: true,
firstName: true,
lastName: true,
},
},
projects: {
select: {
id: true,
},
},
},
});
if (!organization.createdBy) {
throw new Error(
`Organization ${organization.name} does not have a creator. Cannot proceed.`,
);
}
const user = organization.createdBy;
// Fetch product details for review
const product = await polar.products.get({ id: input.productId });
const price = product.prices?.[0];
const priceStr =
price && 'priceAmount' in price && price.priceAmount
? `$${(price.priceAmount / 100).toFixed(2)}/${price.recurringInterval || 'month'}`
: 'No price';
console.log('\nReview the following settings:');
console.table({
product: product.name,
price: priceStr,
organization: organization.name,
email: user.email,
name:
[user.firstName, user.lastName].filter(Boolean).join(' ') || 'No name',
});
const { confirmed } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirmed',
message: 'Do you want to proceed?',
default: false,
},
]);
if (!confirmed) {
console.log('Operation canceled');
return;
}
const checkoutLink = await polar.checkoutLinks.create({
paymentProcessor: 'stripe',
productId: input.productId,
allowDiscountCodes: false,
metadata: {
organizationId: organization.id,
userId: user.id,
},
successUrl: getSuccessUrl(
input.isProduction
? 'https://dashboard.openpanel.dev'
: 'http://localhost:3000',
organization.id,
),
});
console.log('\nCheckout link created:');
console.table(checkoutLink);
console.log('\nProduct assigned successfully!');
}
main()
.catch(console.error)
.finally(() => db.$disconnect());