mirror of
https://github.com/feeddeck/feeddeck.git
synced 2026-07-18 18:02:18 -05:00
Instead of using an `import_map.json` file to define the versions for dependencies, they are now defined directly within the import. Since the `import_map.json` file should not be used anymore and instead a `deno.json` file per function should be used, we decided to define them directly with the code. The overhead compared to a `deno.json` file per function shouldn't be that large and it makes using functions in a self-hosted setup easier.
189 lines
5.2 KiB
TypeScript
189 lines
5.2 KiB
TypeScript
import Stripe from "https://esm.sh/stripe@14.8.0?target=deno&no-check";
|
|
import { createClient } from "jsr:@supabase/supabase-js@2";
|
|
|
|
import { log } from "../utils/log.ts";
|
|
import {
|
|
FEEDDECK_STRIPE_API_KEY,
|
|
FEEDDECK_STRIPE_PRICE_ID,
|
|
FEEDDECK_SUPABASE_SERVICE_ROLE_KEY,
|
|
FEEDDECK_SUPABASE_SITE_URL,
|
|
FEEDDECK_SUPABASE_URL,
|
|
} from "../utils/constants.ts";
|
|
|
|
/**
|
|
* Create a new Stripe client, based on the `FEEDDECK_STRIPE_API_KEY`
|
|
* environment variable.
|
|
*/
|
|
export const stripe = new Stripe(FEEDDECK_STRIPE_API_KEY, {
|
|
apiVersion: "2022-11-15",
|
|
httpClient: Stripe.createFetchHttpClient(),
|
|
});
|
|
|
|
export const cryptoProvider = Stripe.createSubtleCryptoProvider();
|
|
|
|
/**
|
|
* `createOrRetrieveCustomer` returns the Stripe customer id for the provided
|
|
* `userId`. If the user doesn't have a Stripe customer id yet, a new customer
|
|
* is created for the user and the customer id is stored in the database before
|
|
* it is returned.
|
|
*/
|
|
export const createOrRetrieveCustomer = async (
|
|
userId: string,
|
|
userEmail?: string,
|
|
): Promise<string> => {
|
|
/**
|
|
* Create a new admin client for Supabase, which is used in the following
|
|
* steps to access the database. This client is required because the user
|
|
* client does not have the permissions to access the `profiles` table.
|
|
*/
|
|
const adminSupabaseClient = createClient(
|
|
FEEDDECK_SUPABASE_URL,
|
|
FEEDDECK_SUPABASE_SERVICE_ROLE_KEY,
|
|
{
|
|
auth: {
|
|
autoRefreshToken: false,
|
|
persistSession: false,
|
|
},
|
|
},
|
|
);
|
|
|
|
/**
|
|
* Get the user profile from the database. If there is no profile or more than
|
|
* one profile, we return an error.
|
|
*/
|
|
const { data: profile, error: profileError } = await adminSupabaseClient
|
|
.from("profiles")
|
|
.select("*")
|
|
.eq("id", userId);
|
|
if (profileError || profile?.length !== 1) {
|
|
log("error", "Failed to get user profile", {
|
|
user: userId,
|
|
error: profileError,
|
|
});
|
|
throw new Error("Failed to get user profile");
|
|
}
|
|
|
|
if (profile[0].stripeCustomerId) {
|
|
return profile[0].stripeCustomerId;
|
|
}
|
|
|
|
const customer = await stripe.customers.create({
|
|
email: userEmail,
|
|
metadata: {
|
|
userId: userId,
|
|
},
|
|
});
|
|
|
|
const { error: updateError } = await adminSupabaseClient
|
|
.from("profiles")
|
|
.update({
|
|
stripeCustomerId: customer.id,
|
|
})
|
|
.eq("id", userId);
|
|
if (updateError) {
|
|
log("error", "Failed to update user profile with Stripe customer id", {
|
|
stripeCustomerId: customer.id,
|
|
user: userId,
|
|
error: updateError,
|
|
});
|
|
throw new Error("Failed to update user profile with Stripe customer id");
|
|
}
|
|
|
|
return customer.id;
|
|
};
|
|
|
|
/**
|
|
* `createBillingPortalSession` creates a new session for the billing portal for
|
|
* the provided `stripeCustomerId`. The url which can be used by the user to
|
|
* open the billing portal is returned.
|
|
*/
|
|
export const createBillingPortalSession = async (
|
|
stripeCustomerId: string,
|
|
): Promise<string> => {
|
|
const { url } = await stripe.billingPortal.sessions.create({
|
|
customer: stripeCustomerId,
|
|
return_url: FEEDDECK_SUPABASE_SITE_URL,
|
|
});
|
|
|
|
return url;
|
|
};
|
|
|
|
/**
|
|
* `createCheckoutSession` creates a new checkout session for the provided
|
|
* `stripeCustomerId`. The url which can be used by the user to open the
|
|
* checkout page is returned.
|
|
*/
|
|
export const createCheckoutSession = async (stripeCustomerId: string) => {
|
|
const { url } = await stripe.checkout.sessions.create({
|
|
customer: stripeCustomerId,
|
|
customer_update: {
|
|
address: "auto",
|
|
},
|
|
line_items: [
|
|
{
|
|
price: FEEDDECK_STRIPE_PRICE_ID,
|
|
quantity: 1,
|
|
},
|
|
],
|
|
mode: "subscription",
|
|
allow_promotion_codes: true,
|
|
success_url: FEEDDECK_SUPABASE_SITE_URL,
|
|
cancel_url: FEEDDECK_SUPABASE_SITE_URL,
|
|
});
|
|
|
|
return url;
|
|
};
|
|
|
|
export const manageSubscriptionStatusChange = async (
|
|
stripeCustomerId: string,
|
|
isCreated = false,
|
|
) => {
|
|
/**
|
|
* Create a new admin client for Supabase, which is used in the following
|
|
* steps to access the database. This client is required because the user
|
|
* client does not have the permissions to access the `profiles` table.
|
|
*/
|
|
const adminSupabaseClient = createClient(
|
|
FEEDDECK_SUPABASE_URL,
|
|
FEEDDECK_SUPABASE_SERVICE_ROLE_KEY,
|
|
{
|
|
auth: {
|
|
autoRefreshToken: false,
|
|
persistSession: false,
|
|
},
|
|
},
|
|
);
|
|
|
|
/**
|
|
* Get the user profile from the database. If there is no profile or more than
|
|
* one profile, we return an error.
|
|
*/
|
|
const { data: profile, error: profileError } = await adminSupabaseClient
|
|
.from("profiles")
|
|
.select("*")
|
|
.eq("stripeCustomerId", stripeCustomerId);
|
|
if (profileError || profile?.length !== 1) {
|
|
log("error", "Failed to get user profile", {
|
|
stripeCustomerId: stripeCustomerId,
|
|
error: profileError,
|
|
});
|
|
throw new Error("Failed to get user profile");
|
|
}
|
|
|
|
const { error: updateError } = await adminSupabaseClient
|
|
.from("profiles")
|
|
.update({
|
|
tier: isCreated ? "premium" : "free",
|
|
subscriptionProvider: "stripe",
|
|
})
|
|
.eq("id", profile[0].id);
|
|
if (updateError) {
|
|
log("error", "Failed to update user profile with new tier value", {
|
|
stripeCustomerId: stripeCustomerId,
|
|
user: profile[0].id,
|
|
error: updateError,
|
|
});
|
|
throw new Error("Failed to update user profile with new tier value");
|
|
}
|
|
};
|