CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/database/postgres/stripe/sync-customer.ts
Views: 687
1
/*
2
Stripe Synchronization
3
4
ALMOST DEPRECATED -- only used/matters for users with legacy upgrade subscriptions
5
AND is used to change the email address/name of a user in stripe, when they change
6
it in cocalc... which is kind of weird to be here.
7
8
Get all info about the given account from stripe and put it in our own local
9
database. Also, call it right after the user does some action that will change
10
their account info status. Additionally, it checks the email address Stripe
11
knows about the customer and updates it if it changes.
12
13
This will not touch stripe if the user doesn't have a stripe_customer_id
14
set in the accounts table and customer_id is not given as an input.
15
*/
16
17
import getPool from "@cocalc/database/pool";
18
import getLogger from "@cocalc/backend/logger";
19
import { getStripeCustomerId } from "./customer-id";
20
import { is_valid_email_address } from "@cocalc/util/misc";
21
import stripeName from "@cocalc/util/stripe/name";
22
23
const log = getLogger("database:stripe:sync");
24
25
interface Options {
26
account_id: string;
27
// The following two are for efficiency purposes:
28
stripe; // connection to stripe
29
customer_id?: string; // gets looked up if not given
30
}
31
32
// returns customer object or undefined
33
34
export default async function syncCustomer({
35
account_id,
36
stripe,
37
customer_id,
38
}: Options) {
39
log.debug("account_id = ", account_id);
40
if (!customer_id) {
41
customer_id = await getStripeCustomerId(account_id);
42
log.debug("customer_id = ", customer_id);
43
if (!customer_id) {
44
// done -- nothing to do -- not a customer
45
return;
46
}
47
}
48
49
// get customer data from stripe
50
let customer = await stripe.customers.retrieve(customer_id, {
51
expand: ["sources", "subscriptions"],
52
});
53
54
const pool = getPool();
55
56
if (customer.deleted) {
57
// we don't delete customers -- this would be a weird situation.
58
log.debug(
59
"customer exists in stripe but is deleted there, so we delete link to stripe.",
60
);
61
await pool.query(
62
"UPDATE accounts SET stripe_customer_id=NULL, stripe_customer=NULL WHERE account_id=$1",
63
[account_id],
64
);
65
return;
66
}
67
68
// update email, name or description in stripe if different from database.
69
const { rows } = await pool.query(
70
"SELECT email_address, first_name, last_name FROM accounts WHERE account_id = $1::UUID",
71
[account_id],
72
);
73
if (rows.length == 0) {
74
throw Error(`no account ${account_id}`);
75
}
76
const { email_address, first_name, last_name } = rows[0];
77
78
const update: any = {};
79
if (
80
email_address != customer.email &&
81
is_valid_email_address(email_address)
82
) {
83
// update email address
84
update.email = email_address;
85
}
86
87
const name = stripeName(first_name, last_name);
88
if (name != customer.name) {
89
update.name = name;
90
}
91
if (name != customer.description) {
92
update.description = name;
93
}
94
if (Object.keys(update).length > 0) {
95
// something changed
96
customer = await stripe.customers.update(customer_id, update);
97
}
98
99
// if there is a non-canceled subscription, save in our database the stripe data about this account
100
// Otherwise, clear that so we don't consider user again.
101
await pool.query(
102
"UPDATE accounts SET stripe_customer=$1::JSONB WHERE account_id=$2::UUID",
103
[
104
hasNonCanceledLegacySubscription(customer.subscriptions)
105
? customer
106
: null,
107
account_id,
108
],
109
);
110
111
return customer;
112
}
113
114
function hasNonCanceledLegacySubscription(subscriptions): boolean {
115
for (const sub of subscriptions?.data ?? []) {
116
if (sub.status != "canceled") {
117
// this is a crappy test, I guess, but the one new subscription we have using
118
// stripe checkout sets metadata.service to 'credit', but we didn't touch
119
// metadata.service on the old legacy upgrade plans. (We didn't set metadata at all.)
120
if (sub.metadata?.service == null) {
121
return true;
122
}
123
}
124
}
125
return false;
126
}
127
128