Skip to content

Commit

Permalink
fix: add customer portal button
Browse files Browse the repository at this point in the history
The customer portal allows people to optionally update their billing info and to update their payment methods. It also allows to manage the user subscriptions if the user prefers to do it through there.
  • Loading branch information
Torwent committed Jan 18, 2024
1 parent 51714c7 commit 0eefa22
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 42 deletions.
62 changes: 56 additions & 6 deletions src/lib/backend/data.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,58 @@ export async function createStripeCustomer(
return customer.id
}

export async function createCheckoutSession(
id: string,
customer: string,
stripeUser: string | null,
price: string,
origin: string
) {
let session: Stripe.Checkout.Session

try {
session = await stripe.checkout.sessions.create({
line_items: [{ price: price, quantity: 1 }],
customer: customer,
customer_update: { address: "auto", shipping: "auto" },
mode: "subscription",
billing_address_collection: "auto",
automatic_tax: { enabled: stripeUser == null },
payment_method_collection: "always",
allow_promotion_codes: true,
subscription_data: {
on_behalf_of: stripeUser ?? undefined,
application_fee_percent: stripeUser ? 20 : undefined,
transfer_data: stripeUser ? { destination: stripeUser } : undefined,
metadata: { user_id: id }
},
success_url: origin + "/api/stripe/checkout/success?session_id={CHECKOUT_SESSION_ID}",
cancel_url: origin + "/api/stripe/checkout/cancel?session_id={CHECKOUT_SESSION_ID}"
})
} catch (err: any) {
console.error(err)
return null
}

return session.url
}

export async function createCustomerPortal(customer: string, origin: string) {
let portal: Stripe.BillingPortal.Session

try {
portal = await stripe.billingPortal.sessions.create({
customer: customer,
return_url: origin + "/subscriptions"
})
} catch (error) {
console.error(error)
return null
}

return portal.url
}

export async function createStripeConnectAccount(
supabase: SupabaseClient,
baseURL: string,
Expand Down Expand Up @@ -350,7 +402,7 @@ export async function createStripeConnectAccount(
return accountLink.url
}

export async function finishStripeAccountSetup(baseURL: string, account: string) {
export async function finishStripeConnectAccountSetup(baseURL: string, account: string) {
let accountLink: Stripe.Response<Stripe.AccountLink>

try {
Expand All @@ -368,7 +420,7 @@ export async function finishStripeAccountSetup(baseURL: string, account: string)
return accountLink.url
}

export async function getStripeAccount(id: string) {
export async function getStripeConnectAccount(id: string) {
let stripeAccount: Stripe.Response<Stripe.Account> | null = null

try {
Expand All @@ -383,11 +435,9 @@ export async function getStripeAccount(id: string) {
return stripeAccount
}

export async function updateStripeAccount(id: string, dba: string) {
let stripeAccount: Stripe.Response<Stripe.Account> | null = null

export async function updateStripeConnectAccount(id: string, dba: string) {
try {
stripeAccount = await stripe.accounts.update(id, { business_profile: { name: dba } })
await stripe.accounts.update(id, { business_profile: { name: dba } })
} catch (error) {
console.error(
"An error occurred when calling the Stripe API to create an account session",
Expand Down
12 changes: 6 additions & 6 deletions src/routes/dashboard/[slug]/+page.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import {
createStripePrice,
createStripeScriptProduct,
doLogin,
finishStripeAccountSetup,
finishStripeConnectAccountSetup,
updateStripePrice,
updateStripeProduct,
getStripeSession,
getStripeAccount,
updateStripeAccount
getStripeConnectAccount,
updateStripeConnectAccount
} from "$lib/backend/data.server"
import {
bundleArraySchema,
Expand Down Expand Up @@ -50,7 +50,7 @@ export const load = async (event) => {

const scripter = promises[6]

const stripeAccount = scripter.stripe ? await getStripeAccount(scripter.stripe) : null
const stripeAccount = scripter.stripe ? await getStripeConnectAccount(scripter.stripe) : null
const stripeSession = scripter.stripe ? await getStripeSession(scripter.stripe) : null

return {
Expand Down Expand Up @@ -112,7 +112,7 @@ export const actions = {
const scripter = await getScripterDashboard(supabaseServer, slug)
if (!scripter.stripe) throw error(403, "You need a linked stripe account to edit it.")

const link = await finishStripeAccountSetup(origin, scripter.stripe)
const link = await finishStripeConnectAccountSetup(origin, scripter.stripe)

if (link) throw redirect(303, link)
return
Expand Down Expand Up @@ -141,7 +141,7 @@ export const actions = {
if (!form.valid) return setError(form, "", "The name you set is not valid!")
if (!scripter.stripe) return setError(form, "", "The user is missing a stripe profile!")

const success = await updateStripeAccount(scripter.stripe, form.data.dba)
const success = await updateStripeConnectAccount(scripter.stripe, form.data.dba)
if (!success) return setError(form, "", "Failed to update stripe's business name")
return
},
Expand Down
78 changes: 48 additions & 30 deletions src/routes/subscriptions/+page.server.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { PUBLIC_SUPER_USER_ID } from "$env/static/public"
import { doLogin } from "$lib/backend/data.server"
import { createCheckoutSession, createCustomerPortal, doLogin } from "$lib/backend/data.server"
import { subscriptionsSchema, checkoutSchema } from "$lib/backend/schemas"
import { stripe, updateCustomerID } from "$lib/backend/supabase.server"
import { error, redirect } from "@sveltejs/kit"
import type Stripe from "stripe"
import { setError, superValidate } from "sveltekit-superforms/server"

export const load = async (event) => {
Expand Down Expand Up @@ -96,8 +95,6 @@ export const actions = {
)
}

let session: Stripe.Checkout.Session

const { data, error: productError } = await supabaseServer
.schema("scripts")
.from("products")
Expand All @@ -114,35 +111,56 @@ export const actions = {
}

const stripeUser = data.user_id !== PUBLIC_SUPER_USER_ID ? data.stripe_user : null

try {
session = await stripe.checkout.sessions.create({
line_items: [{ price: selectedPrice.id, quantity: 1 }],
customer: profile.customer_id,
customer_update: { address: "auto", shipping: "auto" },
mode: "subscription",
billing_address_collection: "auto",
automatic_tax: { enabled: stripeUser == null },
payment_method_collection: "always",
allow_promotion_codes: true,
subscription_data: {
on_behalf_of: stripeUser ?? undefined,
application_fee_percent: stripeUser ? 20 : undefined,
transfer_data: stripeUser ? { destination: stripeUser } : undefined,
metadata: { user_id: profile.id }
},
success_url: origin + "/api/stripe/checkout/success?session_id={CHECKOUT_SESSION_ID}",
cancel_url: origin + "/api/stripe/checkout/cancel?session_id={CHECKOUT_SESSION_ID}"
})
} catch (err: any) {
console.error(err)
return setError(form, "", err)
}

if (session && session.url) throw redirect(303, session.url)
const url = await createCheckoutSession(
profile.id,
profile.customer_id,
stripeUser,
selectedPrice.id,
origin
)

if (url) throw redirect(303, url)
return setError(form, "", "Something went wrong!")
},

portal: async ({ locals: { supabaseServer, getSession, getProfile }, url: { origin } }) => {
const promises = await Promise.all([getSession(), getProfile()])
const profile = promises[1]

if (!promises[0] || !profile) {
return await doLogin(supabaseServer, origin, new URLSearchParams("login&provider=discord"))
}

if (!profile.customer_id) {
const customerSearch = await stripe.customers.search({ query: 'name: "' + profile.id + '"' })

if (customerSearch.data.length !== 1)
throw error(
404,
`You don't seem to have a customer_id assign for some reason. This shouldn't happen and has to be fixed manually.
Refresh the page, if that doesn't solve the issue please contact [email protected] and send the following:
id: ${profile.id} discord_id: ${profile.discord} registered_email: ${profile.private.email} username: ${profile.username}`
)

profile.customer_id = customerSearch.data[0].id

const updateCustomer = await updateCustomerID(profile.id, profile.customer_id)

if (!updateCustomer)
return error(
404,
`You don't seem to have a customer_id assign for some reason and one couldn't be created. This shouldn't happen and has to be fixed manually.
Refresh the page, if that doesn't solve the issue please contact [email protected] and send the following:
id: ${profile.id} discord_id: ${profile.discord} registered_email: ${profile.private.email} username: ${profile.username}`
)
}

const url = await createCustomerPortal(profile.customer_id, origin)

if (url) throw redirect(303, url)
throw error(404, "Something went wrong!")
},

subscriptions: async ({
request,
locals: { supabaseServer, getSession, getProfile },
Expand Down
4 changes: 4 additions & 0 deletions src/routes/subscriptions/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,10 @@
</table>
</div>
</form>

<form method="POST" action="?/portal" class="mt-8 mb-32 grid place-items-center">
<button class="btn variant-filled-secondary">Customer portal</button>
</form>
{/if}

<form method="POST" class="my-8 items-center justify-center" use:checkoutEnhance>
Expand Down

0 comments on commit 0eefa22

Please sign in to comment.