Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/database/postgres/stripe/sync-customer.ts
Views: 687
/*1Stripe Synchronization23ALMOST DEPRECATED -- only used/matters for users with legacy upgrade subscriptions4AND is used to change the email address/name of a user in stripe, when they change5it in cocalc... which is kind of weird to be here.67Get all info about the given account from stripe and put it in our own local8database. Also, call it right after the user does some action that will change9their account info status. Additionally, it checks the email address Stripe10knows about the customer and updates it if it changes.1112This will not touch stripe if the user doesn't have a stripe_customer_id13set in the accounts table and customer_id is not given as an input.14*/1516import getPool from "@cocalc/database/pool";17import getLogger from "@cocalc/backend/logger";18import { getStripeCustomerId } from "./customer-id";19import { is_valid_email_address } from "@cocalc/util/misc";20import stripeName from "@cocalc/util/stripe/name";2122const log = getLogger("database:stripe:sync");2324interface Options {25account_id: string;26// The following two are for efficiency purposes:27stripe; // connection to stripe28customer_id?: string; // gets looked up if not given29}3031// returns customer object or undefined3233export default async function syncCustomer({34account_id,35stripe,36customer_id,37}: Options) {38log.debug("account_id = ", account_id);39if (!customer_id) {40customer_id = await getStripeCustomerId(account_id);41log.debug("customer_id = ", customer_id);42if (!customer_id) {43// done -- nothing to do -- not a customer44return;45}46}4748// get customer data from stripe49let customer = await stripe.customers.retrieve(customer_id, {50expand: ["sources", "subscriptions"],51});5253const pool = getPool();5455if (customer.deleted) {56// we don't delete customers -- this would be a weird situation.57log.debug(58"customer exists in stripe but is deleted there, so we delete link to stripe.",59);60await pool.query(61"UPDATE accounts SET stripe_customer_id=NULL, stripe_customer=NULL WHERE account_id=$1",62[account_id],63);64return;65}6667// update email, name or description in stripe if different from database.68const { rows } = await pool.query(69"SELECT email_address, first_name, last_name FROM accounts WHERE account_id = $1::UUID",70[account_id],71);72if (rows.length == 0) {73throw Error(`no account ${account_id}`);74}75const { email_address, first_name, last_name } = rows[0];7677const update: any = {};78if (79email_address != customer.email &&80is_valid_email_address(email_address)81) {82// update email address83update.email = email_address;84}8586const name = stripeName(first_name, last_name);87if (name != customer.name) {88update.name = name;89}90if (name != customer.description) {91update.description = name;92}93if (Object.keys(update).length > 0) {94// something changed95customer = await stripe.customers.update(customer_id, update);96}9798// if there is a non-canceled subscription, save in our database the stripe data about this account99// Otherwise, clear that so we don't consider user again.100await pool.query(101"UPDATE accounts SET stripe_customer=$1::JSONB WHERE account_id=$2::UUID",102[103hasNonCanceledLegacySubscription(customer.subscriptions)104? customer105: null,106account_id,107],108);109110return customer;111}112113function hasNonCanceledLegacySubscription(subscriptions): boolean {114for (const sub of subscriptions?.data ?? []) {115if (sub.status != "canceled") {116// this is a crappy test, I guess, but the one new subscription we have using117// stripe checkout sets metadata.service to 'credit', but we didn't touch118// metadata.service on the old legacy upgrade plans. (We didn't set metadata at all.)119if (sub.metadata?.service == null) {120return true;121}122}123}124return false;125}126127128